diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -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 @@
     '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,17 +81,26 @@
     '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',
     'ArcanistFilenameLinter' => 'lint/linter/ArcanistFilenameLinter.php',
     'ArcanistFilenameLinterTestCase' => 'lint/linter/__tests__/ArcanistFilenameLinterTestCase.php',
     'ArcanistFlagWorkflow' => 'workflow/ArcanistFlagWorkflow.php',
     'ArcanistFlake8Linter' => 'lint/linter/ArcanistFlake8Linter.php',
     'ArcanistFlake8LinterTestCase' => 'lint/linter/__tests__/ArcanistFlake8LinterTestCase.php',
+    'ArcanistFormattedStringXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistFormattedStringXHPASTLinterRule.php',
     'ArcanistFutureLinter' => 'lint/linter/ArcanistFutureLinter.php',
     'ArcanistGeneratedLinter' => 'lint/linter/ArcanistGeneratedLinter.php',
     'ArcanistGeneratedLinterTestCase' => 'lint/linter/__tests__/ArcanistGeneratedLinterTestCase.php',
@@ -90,7 +117,14 @@
     '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',
@@ -100,7 +134,10 @@
     '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',
@@ -116,19 +153,30 @@
     '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',
@@ -140,6 +188,8 @@
     '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',
@@ -149,40 +199,57 @@
     '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',
@@ -191,6 +258,7 @@
     'ArcanistXHPASTLintNamingHookTestCase' => 'lint/linter/xhpast/__tests__/ArcanistXHPASTLintNamingHookTestCase.php',
     'ArcanistXHPASTLintSwitchHook' => 'lint/linter/xhpast/ArcanistXHPASTLintSwitchHook.php',
     'ArcanistXHPASTLinter' => 'lint/linter/ArcanistXHPASTLinter.php',
+    'ArcanistXHPASTLinterRule' => 'lint/linter/ArcanistXHPASTLinterRule.php',
     'ArcanistXHPASTLinterTestCase' => 'lint/linter/__tests__/ArcanistXHPASTLinterTestCase.php',
     'ArcanistXMLLinter' => 'lint/linter/ArcanistXMLLinter.php',
     'ArcanistXMLLinterTestCase' => 'lint/linter/__tests__/ArcanistXMLLinterTestCase.php',
@@ -211,13 +279,19 @@
   ),
   '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',
@@ -226,43 +300,64 @@
     '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',
     'ArcanistFilenameLinter' => 'ArcanistLinter',
     'ArcanistFilenameLinterTestCase' => 'ArcanistLinterTestCase',
     'ArcanistFlagWorkflow' => 'ArcanistWorkflow',
     'ArcanistFlake8Linter' => 'ArcanistExternalLinter',
     'ArcanistFlake8LinterTestCase' => 'ArcanistExternalLinterTestCase',
+    'ArcanistFormattedStringXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistFutureLinter' => 'ArcanistLinter',
     'ArcanistGeneratedLinter' => 'ArcanistLinter',
     'ArcanistGeneratedLinterTestCase' => 'ArcanistLinterTestCase',
@@ -277,7 +372,14 @@
     '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',
@@ -287,7 +389,10 @@
     'ArcanistJSONLinterTestCase' => 'ArcanistLinterTestCase',
     'ArcanistJscsLinter' => 'ArcanistExternalLinter',
     'ArcanistJscsLinterTestCase' => 'ArcanistExternalLinterTestCase',
+    'ArcanistKeywordCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
+    'ArcanistLambdaFuncFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistLandWorkflow' => 'ArcanistWorkflow',
+    'ArcanistLanguageConstructParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistLesscLinter' => 'ArcanistExternalLinter',
     'ArcanistLesscLinterTestCase' => 'ArcanistExternalLinterTestCase',
     'ArcanistLiberateWorkflow' => 'ArcanistWorkflow',
@@ -296,18 +401,29 @@
     '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',
@@ -319,6 +435,8 @@
     'ArcanistPhutilLibraryLinter' => 'ArcanistLinter',
     'ArcanistPhutilXHPASTLinter' => 'ArcanistBaseXHPASTLinter',
     'ArcanistPhutilXHPASTLinterTestCase' => 'ArcanistLinterTestCase',
+    'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
+    'ArcanistPregQuoteMisuseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistPuppetLintLinter' => 'ArcanistExternalLinter',
     'ArcanistPuppetLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
     'ArcanistPyFlakesLinter' => 'ArcanistExternalLinter',
@@ -327,35 +445,52 @@
     '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',
diff --git a/src/lint/linter/ArcanistBaseXHPASTLinter.php b/src/lint/linter/ArcanistBaseXHPASTLinter.php
--- a/src/lint/linter/ArcanistBaseXHPASTLinter.php
+++ b/src/lint/linter/ArcanistBaseXHPASTLinter.php
@@ -201,7 +201,7 @@
   }
 
 
-/* -(  Utility  )------------------------------------------------------------ */
+/* -(  Deprecated  )--------------------------------------------------------- */
 
   /**
    * Retrieve all calls to some specified function(s).
diff --git a/src/lint/linter/ArcanistXHPASTLinter.php b/src/lint/linter/ArcanistXHPASTLinter.php
--- a/src/lint/linter/ArcanistXHPASTLinter.php
+++ b/src/lint/linter/ArcanistXHPASTLinter.php
@@ -5,80 +5,20 @@
  */
 final class ArcanistXHPASTLinter extends ArcanistBaseXHPASTLinter {
 
-  const LINT_PHP_SYNTAX_ERROR           = 1;
-  const LINT_UNABLE_TO_PARSE            = 2;
-  const LINT_VARIABLE_VARIABLE          = 3;
-  const LINT_EXTRACT_USE                = 4;
-  const LINT_UNDECLARED_VARIABLE        = 5;
-  const LINT_PHP_SHORT_TAG              = 6;
-  const LINT_PHP_ECHO_TAG               = 7;
-  const LINT_PHP_CLOSE_TAG              = 8;
-  const LINT_NAMING_CONVENTIONS         = 9;
-  const LINT_IMPLICIT_CONSTRUCTOR       = 10;
-  const LINT_DYNAMIC_DEFINE             = 12;
-  const LINT_STATIC_THIS                = 13;
-  const LINT_PREG_QUOTE_MISUSE          = 14;
-  const LINT_PHP_OPEN_TAG               = 15;
-  const LINT_TODO_COMMENT               = 16;
-  const LINT_EXIT_EXPRESSION            = 17;
-  const LINT_COMMENT_STYLE              = 18;
-  const LINT_CLASS_FILENAME_MISMATCH    = 19;
-  const LINT_TAUTOLOGICAL_EXPRESSION    = 20;
-  const LINT_PLUS_OPERATOR_ON_STRINGS   = 21;
-  const LINT_DUPLICATE_KEYS_IN_ARRAY    = 22;
-  const LINT_REUSED_ITERATORS           = 23;
-  const LINT_BRACE_FORMATTING           = 24;
-  const LINT_PARENTHESES_SPACING        = 25;
-  const LINT_CONTROL_STATEMENT_SPACING  = 26;
-  const LINT_BINARY_EXPRESSION_SPACING  = 27;
-  const LINT_ARRAY_INDEX_SPACING        = 28;
-  const LINT_IMPLICIT_FALLTHROUGH       = 30;
-  const LINT_REUSED_AS_ITERATOR         = 32;
-  const LINT_COMMENT_SPACING            = 34;
-  const LINT_SLOWNESS                   = 36;
-  const LINT_CLOSING_CALL_PAREN         = 37;
-  const LINT_CLOSING_DECL_PAREN         = 38;
-  const LINT_REUSED_ITERATOR_REFERENCE  = 39;
-  const LINT_KEYWORD_CASING             = 40;
-  const LINT_DOUBLE_QUOTE               = 41;
-  const LINT_ELSEIF_USAGE               = 42;
-  const LINT_SEMICOLON_SPACING          = 43;
-  const LINT_CONCATENATION_OPERATOR     = 44;
-  const LINT_PHP_COMPATIBILITY          = 45;
-  const LINT_LANGUAGE_CONSTRUCT_PAREN   = 46;
-  const LINT_EMPTY_STATEMENT            = 47;
-  const LINT_ARRAY_SEPARATOR            = 48;
-  const LINT_CONSTRUCTOR_PARENTHESES    = 49;
-  const LINT_DUPLICATE_SWITCH_CASE      = 50;
-  const LINT_BLACKLISTED_FUNCTION       = 51;
-  const LINT_IMPLICIT_VISIBILITY        = 52;
-  const LINT_CALL_TIME_PASS_BY_REF      = 53;
-  const LINT_FORMATTED_STRING           = 54;
-  const LINT_UNNECESSARY_FINAL_MODIFIER = 55;
-  const LINT_UNNECESSARY_SEMICOLON      = 56;
-  const LINT_SELF_MEMBER_REFERENCE      = 57;
-  const LINT_LOGICAL_OPERATORS          = 58;
-  const LINT_INNER_FUNCTION             = 59;
-  const LINT_DEFAULT_PARAMETERS         = 60;
-  const LINT_LOWERCASE_FUNCTIONS        = 61;
-  const LINT_CLASS_NAME_LITERAL         = 62;
-  const LINT_USELESS_OVERRIDING_METHOD  = 63;
-  const LINT_NO_PARENT_SCOPE            = 64;
-  const LINT_ALIAS_FUNCTION             = 65;
-  const LINT_CAST_SPACING               = 66;
-  const LINT_TOSTRING_EXCEPTION         = 67;
-  const LINT_LAMBDA_FUNC_FUNCTION       = 68;
-  const LINT_INSTANCEOF_OPERATOR        = 69;
-  const LINT_INVALID_DEFAULT_PARAMETER  = 70;
-  const LINT_MODIFIER_ORDERING          = 71;
-  const LINT_INVALID_MODIFIERS          = 72;
+  private $rules = array();
 
-  private $blacklistedFunctions = array();
-  private $naminghook;
-  private $printfFunctions = array();
-  private $switchhook;
-  private $version;
-  private $windowsVersion;
+  public function __construct() {
+    $this->rules = $this->getLinterRules();
+  }
+
+  public function __clone() {
+    $rules = $this->rules;
+
+    $this->rules = array();
+    foreach ($rules as $rule) {
+      $this->rules[] = clone $rule;
+    }
+  }
 
   public function getInfoName() {
     return pht('XHPAST Lint');
@@ -88,145 +28,6 @@
     return pht('Use XHPAST to enforce coding conventions on PHP source files.');
   }
 
-  public function getLintNameMap() {
-    return array(
-      self::LINT_PHP_SYNTAX_ERROR
-        => pht('PHP Syntax Error!'),
-      self::LINT_UNABLE_TO_PARSE
-        => pht('Unable to Parse'),
-      self::LINT_VARIABLE_VARIABLE
-        => pht('Use of Variable Variable'),
-      self::LINT_EXTRACT_USE
-        => pht('Use of %s', 'extract()'),
-      self::LINT_UNDECLARED_VARIABLE
-        => pht('Use of Undeclared Variable'),
-      self::LINT_PHP_SHORT_TAG
-        => pht('Use of Short Tag "%s"', '<?'),
-      self::LINT_PHP_ECHO_TAG
-        => pht('Use of Echo Tag "%s"', '<?='),
-      self::LINT_PHP_CLOSE_TAG
-        => pht('Use of Close Tag "%s"', '?>'),
-      self::LINT_NAMING_CONVENTIONS
-        => pht('Naming Conventions'),
-      self::LINT_IMPLICIT_CONSTRUCTOR
-        => pht('Implicit Constructor'),
-      self::LINT_DYNAMIC_DEFINE
-        => pht('Dynamic %s', 'define()'),
-      self::LINT_STATIC_THIS
-        => pht('Use of %s in Static Context', '$this'),
-      self::LINT_PREG_QUOTE_MISUSE
-        => pht('Misuse of %s', 'preg_quote()'),
-      self::LINT_PHP_OPEN_TAG
-        => pht('Expected Open Tag'),
-      self::LINT_TODO_COMMENT
-        => pht('TODO Comment'),
-      self::LINT_EXIT_EXPRESSION
-        => pht('Exit Used as Expression'),
-      self::LINT_COMMENT_STYLE
-        => pht('Comment Style'),
-      self::LINT_CLASS_FILENAME_MISMATCH
-        => pht('Class-Filename Mismatch'),
-      self::LINT_TAUTOLOGICAL_EXPRESSION
-        => pht('Tautological Expression'),
-      self::LINT_PLUS_OPERATOR_ON_STRINGS
-        => pht('Not String Concatenation'),
-      self::LINT_DUPLICATE_KEYS_IN_ARRAY
-        => pht('Duplicate Keys in Array'),
-      self::LINT_REUSED_ITERATORS
-        => pht('Reuse of Iterator Variable'),
-      self::LINT_BRACE_FORMATTING
-        => pht('Brace Placement'),
-      self::LINT_PARENTHESES_SPACING
-        => pht('Spaces Inside Parentheses'),
-      self::LINT_CONTROL_STATEMENT_SPACING
-        => pht('Space After Control Statement'),
-      self::LINT_BINARY_EXPRESSION_SPACING
-        => pht('Space Around Binary Operator'),
-      self::LINT_ARRAY_INDEX_SPACING
-        => pht('Spacing Before Array Index'),
-      self::LINT_IMPLICIT_FALLTHROUGH
-        => pht('Implicit Fallthrough'),
-      self::LINT_REUSED_AS_ITERATOR
-        => pht('Variable Reused As Iterator'),
-      self::LINT_COMMENT_SPACING
-        => pht('Comment Spaces'),
-      self::LINT_SLOWNESS
-        => pht('Slow Construct'),
-      self::LINT_CLOSING_CALL_PAREN
-        => pht('Call Formatting'),
-      self::LINT_CLOSING_DECL_PAREN
-        => pht('Declaration Formatting'),
-      self::LINT_REUSED_ITERATOR_REFERENCE
-        => pht('Reuse of Iterator References'),
-      self::LINT_KEYWORD_CASING
-        => pht('Keyword Conventions'),
-      self::LINT_DOUBLE_QUOTE
-        => pht('Unnecessary Double Quotes'),
-      self::LINT_ELSEIF_USAGE
-        => pht('ElseIf Usage'),
-      self::LINT_SEMICOLON_SPACING
-        => pht('Semicolon Spacing'),
-      self::LINT_CONCATENATION_OPERATOR
-        => pht('Concatenation Spacing'),
-      self::LINT_PHP_COMPATIBILITY
-        => pht('PHP Compatibility'),
-      self::LINT_LANGUAGE_CONSTRUCT_PAREN
-        => pht('Language Construct Parentheses'),
-      self::LINT_EMPTY_STATEMENT
-        => pht('Empty Block Statement'),
-      self::LINT_ARRAY_SEPARATOR
-        => pht('Array Separator'),
-      self::LINT_CONSTRUCTOR_PARENTHESES
-        => pht('Constructor Parentheses'),
-      self::LINT_DUPLICATE_SWITCH_CASE
-        => pht('Duplicate Case Statements'),
-      self::LINT_BLACKLISTED_FUNCTION
-        => pht('Use of Blacklisted Function'),
-      self::LINT_IMPLICIT_VISIBILITY
-        => pht('Implicit Method Visibility'),
-      self::LINT_CALL_TIME_PASS_BY_REF
-        => pht('Call-Time Pass-By-Reference'),
-      self::LINT_FORMATTED_STRING
-        => pht('Formatted String'),
-      self::LINT_UNNECESSARY_FINAL_MODIFIER
-        => pht('Unnecessary Final Modifier'),
-      self::LINT_UNNECESSARY_SEMICOLON
-        => pht('Unnecessary Semicolon'),
-      self::LINT_SELF_MEMBER_REFERENCE
-        => pht('Self Member Reference'),
-      self::LINT_LOGICAL_OPERATORS
-        => pht('Logical Operators'),
-      self::LINT_INNER_FUNCTION
-        => pht('Inner Functions'),
-      self::LINT_DEFAULT_PARAMETERS
-        => pht('Default Parameters'),
-      self::LINT_LOWERCASE_FUNCTIONS
-        => pht('Lowercase Functions'),
-      self::LINT_CLASS_NAME_LITERAL
-        => pht('Class Name Literal'),
-      self::LINT_USELESS_OVERRIDING_METHOD
-        => pht('Useless Overriding Method'),
-      self::LINT_NO_PARENT_SCOPE
-        => pht('No Parent Scope'),
-      self::LINT_ALIAS_FUNCTION
-        => pht('Alias Functions'),
-      self::LINT_CAST_SPACING
-        => pht('Cast Spacing'),
-      self::LINT_TOSTRING_EXCEPTION
-        => pht('Throwing Exception in %s Method', '__toString'),
-      self::LINT_LAMBDA_FUNC_FUNCTION
-        => pht('%s Function', '__lambda_func'),
-      self::LINT_INSTANCEOF_OPERATOR
-        => pht('%s Operator', 'instanceof'),
-      self::LINT_INVALID_DEFAULT_PARAMETER
-        => pht('Invalid Default Parameter'),
-      self::LINT_MODIFIER_ORDERING
-        => pht('Modifier Ordering'),
-      self::LINT_INVALID_MODIFIERS
-        => pht('Invalid Modifiers'),
-    );
-  }
-
   public function getLinterName() {
     return 'XHP';
   }
@@ -235,117 +36,60 @@
     return 'xhpast';
   }
 
-  public function getLintSeverityMap() {
-    $disabled = ArcanistLintSeverity::SEVERITY_DISABLED;
-    $advice   = ArcanistLintSeverity::SEVERITY_ADVICE;
-    $warning  = ArcanistLintSeverity::SEVERITY_WARNING;
+  public function getLintNameMap() {
+    return mpull($this->rules, 'getLintName', 'getLintID');
+  }
 
-    return array(
-      self::LINT_TODO_COMMENT               => $disabled,
-      self::LINT_UNABLE_TO_PARSE            => $warning,
-      self::LINT_NAMING_CONVENTIONS         => $warning,
-      self::LINT_PREG_QUOTE_MISUSE          => $advice,
-      self::LINT_BRACE_FORMATTING           => $warning,
-      self::LINT_PARENTHESES_SPACING        => $warning,
-      self::LINT_CONTROL_STATEMENT_SPACING  => $warning,
-      self::LINT_BINARY_EXPRESSION_SPACING  => $warning,
-      self::LINT_ARRAY_INDEX_SPACING        => $warning,
-      self::LINT_IMPLICIT_FALLTHROUGH       => $warning,
-      self::LINT_SLOWNESS                   => $warning,
-      self::LINT_COMMENT_SPACING            => $advice,
-      self::LINT_CLOSING_CALL_PAREN         => $warning,
-      self::LINT_CLOSING_DECL_PAREN         => $warning,
-      self::LINT_REUSED_ITERATOR_REFERENCE  => $warning,
-      self::LINT_KEYWORD_CASING             => $warning,
-      self::LINT_DOUBLE_QUOTE               => $advice,
-      self::LINT_ELSEIF_USAGE               => $advice,
-      self::LINT_SEMICOLON_SPACING          => $advice,
-      self::LINT_CONCATENATION_OPERATOR     => $warning,
-      self::LINT_LANGUAGE_CONSTRUCT_PAREN   => $warning,
-      self::LINT_EMPTY_STATEMENT            => $advice,
-      self::LINT_ARRAY_SEPARATOR            => $advice,
-      self::LINT_CONSTRUCTOR_PARENTHESES    => $advice,
-      self::LINT_IMPLICIT_VISIBILITY        => $advice,
-      self::LINT_UNNECESSARY_FINAL_MODIFIER => $advice,
-      self::LINT_UNNECESSARY_SEMICOLON      => $advice,
-      self::LINT_SELF_MEMBER_REFERENCE      => $advice,
-      self::LINT_LOGICAL_OPERATORS          => $advice,
-      self::LINT_INNER_FUNCTION             => $warning,
-      self::LINT_DEFAULT_PARAMETERS         => $warning,
-      self::LINT_LOWERCASE_FUNCTIONS        => $advice,
-      self::LINT_CLASS_NAME_LITERAL         => $advice,
-      self::LINT_USELESS_OVERRIDING_METHOD  => $advice,
-      self::LINT_ALIAS_FUNCTION             => $advice,
-      self::LINT_CAST_SPACING               => $advice,
-      self::LINT_MODIFIER_ORDERING          => $advice,
-    );
+  public function getLintSeverityMap() {
+    return mpull($this->rules, 'getLintSeverity', 'getLintID');
   }
 
   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.'),
-      ),
-      'xhpast.naminghook' => array(
-        'type' => 'optional string',
-        'help' => pht(
-          'Name of a concrete subclass of ArcanistXHPASTLintNamingHook which '.
-          'enforces more granular naming convention rules for symbols.'),
-      ),
-      '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()'),
-      ),
-      'xhpast.switchhook' => array(
-        'type' => 'optional string',
-        'help' => pht(
-          'Name of a concrete subclass of ArcanistXHPASTLintSwitchHook which '.
-          'tunes the analysis of switch() statements for this linter.'),
-      ),
-      '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.'),
-      ),
-    );
+    return parent::getLinterConfigurationOptions() + array_mergev(
+      mpull($this->rules, 'getLinterConfigurationOptions'));
   }
 
   public function setLinterConfigurationValue($key, $value) {
-    switch ($key) {
-      case 'xhpast.blacklisted.function':
-        $this->blacklistedFunctions = $value;
-        return;
-      case 'xhpast.naminghook':
-        $this->naminghook = $value;
-        return;
-      case 'xhpast.printf-functions':
-        $this->printfFunctions = $value;
-        return;
-      case 'xhpast.switchhook':
-        $this->switchhook = $value;
-        return;
-      case 'xhpast.php-version':
-        $this->version = $value;
-        return;
-      case 'xhpast.php-version.windows':
-        $this->windowsVersion = $value;
-        return;
+    foreach ($this->rules as $rule) {
+      foreach ($rule->getLinterConfigurationOptions() as $k => $spec) {
+        if ($k == $key) {
+          return $rule->setLinterConfigurationValue($key, $value);
+        }
+      }
     }
 
     return parent::setLinterConfigurationValue($key, $value);
   }
 
   public function getVersion() {
-    // The version number should be incremented whenever a new rule is added.
-    return '34';
+    // TODO: Improve this.
+    return count($this->rules);
+  }
+
+  public function getLinterRules() {
+    $rules = array();
+
+    $symbols = id(new PhutilSymbolLoader())
+      ->setAncestorClass('ArcanistXHPASTLinterRule')
+      ->loadObjects();
+
+    foreach ($symbols as $class => $rule) {
+      $id = $rule->getLintID();
+
+      if (isset($rules[$id])) {
+        throw new Exception(
+          pht(
+            'Two linter rules (`%s`, `%s`) share the same lint ID (%d). '.
+            'Each linter rule must have a unique ID.',
+            $class,
+            get_class($rules[$id]),
+            $id));
+      }
+
+      $rules[$id] = $rule;
+    }
+
+    return $rules;
   }
 
   protected function resolveFuture($path, Future $future) {
@@ -356,4119 +100,24 @@
         $this->raiseLintAtLine(
           $ex->getErrorLine(),
           1,
-          self::LINT_PHP_SYNTAX_ERROR,
+          ArcanistSyntaxErrorXHPASTLinterRule::ID,
           pht(
             'This file contains a syntax error: %s',
             $ex->getMessage()));
       } else if ($ex instanceof Exception) {
-        $this->raiseLintAtPath(self::LINT_UNABLE_TO_PARSE, $ex->getMessage());
+        $this->raiseLintAtPath(
+          ArcanistUnableToParseXHPASTLinterRule::ID,
+          $ex->getMessage());
       }
       return;
     }
 
     $root = $tree->getRootNode();
 
-    $method_codes = array(
-      'lintStrstrUsedForCheck' => self::LINT_SLOWNESS,
-      'lintStrposUsedForStart' => self::LINT_SLOWNESS,
-      'lintImplicitFallthrough' => self::LINT_IMPLICIT_FALLTHROUGH,
-      'lintBraceFormatting' => self::LINT_BRACE_FORMATTING,
-      'lintTautologicalExpressions' => self::LINT_TAUTOLOGICAL_EXPRESSION,
-      'lintCommentSpaces' => self::LINT_COMMENT_SPACING,
-      'lintHashComments' => self::LINT_COMMENT_STYLE,
-      'lintReusedIterators' => self::LINT_REUSED_ITERATORS,
-      'lintReusedIteratorReferences' => self::LINT_REUSED_ITERATOR_REFERENCE,
-      'lintVariableVariables' => self::LINT_VARIABLE_VARIABLE,
-      'lintUndeclaredVariables' => array(
-        self::LINT_EXTRACT_USE,
-        self::LINT_REUSED_AS_ITERATOR,
-        self::LINT_UNDECLARED_VARIABLE,
-      ),
-      'lintPHPTagUse' => array(
-        self::LINT_PHP_SHORT_TAG,
-        self::LINT_PHP_ECHO_TAG,
-        self::LINT_PHP_OPEN_TAG,
-        self::LINT_PHP_CLOSE_TAG,
-      ),
-      'lintNamingConventions' => self::LINT_NAMING_CONVENTIONS,
-      'lintSurpriseConstructors' => self::LINT_IMPLICIT_CONSTRUCTOR,
-      'lintParenthesesShouldHugExpressions' => self::LINT_PARENTHESES_SPACING,
-      'lintSpaceAfterControlStatementKeywords' =>
-        self::LINT_CONTROL_STATEMENT_SPACING,
-      'lintSpaceAroundBinaryOperators' => self::LINT_BINARY_EXPRESSION_SPACING,
-      'lintDynamicDefines' => self::LINT_DYNAMIC_DEFINE,
-      'lintUseOfThisInStaticMethods' => self::LINT_STATIC_THIS,
-      'lintPregQuote' => self::LINT_PREG_QUOTE_MISUSE,
-      'lintExitExpressions' => self::LINT_EXIT_EXPRESSION,
-      'lintArrayIndexWhitespace' => self::LINT_ARRAY_INDEX_SPACING,
-      'lintTodoComments' => self::LINT_TODO_COMMENT,
-      'lintPrimaryDeclarationFilenameMatch' =>
-        self::LINT_CLASS_FILENAME_MISMATCH,
-      'lintPlusOperatorOnStrings' => self::LINT_PLUS_OPERATOR_ON_STRINGS,
-      'lintDuplicateKeysInArray' => self::LINT_DUPLICATE_KEYS_IN_ARRAY,
-      'lintClosingCallParen' => self::LINT_CLOSING_CALL_PAREN,
-      'lintClosingDeclarationParen' => self::LINT_CLOSING_DECL_PAREN,
-      'lintKeywordCasing' => self::LINT_KEYWORD_CASING,
-      'lintStrings' => self::LINT_DOUBLE_QUOTE,
-      'lintElseIfStatements' => self::LINT_ELSEIF_USAGE,
-      'lintSemicolons' => self::LINT_SEMICOLON_SPACING,
-      'lintSpaceAroundConcatenationOperators' =>
-        self::LINT_CONCATENATION_OPERATOR,
-      'lintPHPCompatibility' => self::LINT_PHP_COMPATIBILITY,
-      'lintLanguageConstructParentheses' => self::LINT_LANGUAGE_CONSTRUCT_PAREN,
-      'lintEmptyBlockStatements' => self::LINT_EMPTY_STATEMENT,
-      'lintArraySeparator' => self::LINT_ARRAY_SEPARATOR,
-      'lintConstructorParentheses' => self::LINT_CONSTRUCTOR_PARENTHESES,
-      'lintSwitchStatements' => self::LINT_DUPLICATE_SWITCH_CASE,
-      'lintBlacklistedFunction' => self::LINT_BLACKLISTED_FUNCTION,
-      'lintMethodVisibility' => self::LINT_IMPLICIT_VISIBILITY,
-      'lintPropertyVisibility' => self::LINT_IMPLICIT_VISIBILITY,
-      'lintCallTimePassByReference' => self::LINT_CALL_TIME_PASS_BY_REF,
-      'lintFormattedString' => self::LINT_FORMATTED_STRING,
-      'lintUnnecessaryFinalModifier' => self::LINT_UNNECESSARY_FINAL_MODIFIER,
-      'lintUnnecessarySemicolons' => self::LINT_UNNECESSARY_SEMICOLON,
-      'lintConstantDefinitions' => self::LINT_NAMING_CONVENTIONS,
-      'lintSelfMemberReference' => self::LINT_SELF_MEMBER_REFERENCE,
-      'lintLogicalOperators' => self::LINT_LOGICAL_OPERATORS,
-      'lintInnerFunctions' => self::LINT_INNER_FUNCTION,
-      'lintDefaultParameters' => self::LINT_DEFAULT_PARAMETERS,
-      'lintLowercaseFunctions' => self::LINT_LOWERCASE_FUNCTIONS,
-      'lintClassNameLiteral' => self::LINT_CLASS_NAME_LITERAL,
-      'lintUselessOverridingMethods' => self::LINT_USELESS_OVERRIDING_METHOD,
-      'lintNoParentScope' => self::LINT_NO_PARENT_SCOPE,
-      'lintAliasFunctions' => self::LINT_ALIAS_FUNCTION,
-      'lintCastSpacing' => self::LINT_CAST_SPACING,
-      'lintThrowExceptionInToStringMethod' => self::LINT_TOSTRING_EXCEPTION,
-      'lintLambdaFuncFunction' => self::LINT_LAMBDA_FUNC_FUNCTION,
-      'lintInstanceOfOperator' => self::LINT_INSTANCEOF_OPERATOR,
-      'lintInvalidDefaultParameters' => self::LINT_INVALID_DEFAULT_PARAMETER,
-      'lintMethodModifierOrdering' => self::LINT_MODIFIER_ORDERING,
-      'lintPropertyModifierOrdering' => self::LINT_MODIFIER_ORDERING,
-      'lintInvalidModifiers' => self::LINT_INVALID_MODIFIERS,
-    );
-
-    foreach ($method_codes as $method => $codes) {
-      foreach ((array)$codes as $code) {
-        if ($this->isCodeEnabled($code)) {
-          call_user_func(array($this, $method), $root);
-          break;
-        }
-      }
-    }
-  }
-
-  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,
-          self::LINT_SLOWNESS,
-          pht(
-            'Use %s for checking if the string contains something.',
-            'strpos()'));
-      } else if ($name === 'stristr') {
-        $this->raiseLintAtNode(
-          $strstr,
-          self::LINT_SLOWNESS,
-          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,
-          self::LINT_SLOWNESS,
-          pht(
-            'Use %s for checking if the string starts with something.',
-            'strncmp()'));
-      } else if ($name === 'stripos') {
-        $this->raiseLintAtNode(
-          $strpos,
-          self::LINT_SLOWNESS,
-          pht(
-            'Use %s for checking if the string starts with something.',
-            'strncasecmp()'));
-      }
-    }
-  }
-
-  private function lintPHPCompatibility(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,
-          self::LINT_PHP_COMPATIBILITY,
-          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,
-          self::LINT_PHP_COMPATIBILITY,
-          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,
-              self::LINT_PHP_COMPATIBILITY,
-              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,
-            self::LINT_PHP_COMPATIBILITY,
-            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,
-            self::LINT_PHP_COMPATIBILITY,
-            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,
-          self::LINT_PHP_COMPATIBILITY,
-          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,
-          self::LINT_PHP_COMPATIBILITY,
-          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,
-          self::LINT_PHP_COMPATIBILITY,
-          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,
-          self::LINT_PHP_COMPATIBILITY,
-          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,
-            self::LINT_PHP_COMPATIBILITY,
-            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,
-        self::LINT_PHP_COMPATIBILITY,
-        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,
-        self::LINT_PHP_COMPATIBILITY,
-        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,
-          self::LINT_PHP_COMPATIBILITY,
-          pht(
-            'This codebase targets PHP %s, but `static::` was not '.
-            'introduced until PHP 5.3.',
-            $this->version));
-      }
-    }
-
-    $ternaries = $root->selectDescendantsOfType('n_TERNARY_EXPRESSION');
-    foreach ($ternaries as $ternary) {
-      $yes = $ternary->getChildByIndex(1);
-      if ($yes->getTypeName() === 'n_EMPTY') {
-        $this->raiseLintAtNode(
-          $ternary,
-          self::LINT_PHP_COMPATIBILITY,
-          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,
-          self::LINT_PHP_COMPATIBILITY,
-          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),
-            self::LINT_PHP_COMPATIBILITY,
-            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),
-            self::LINT_PHP_COMPATIBILITY,
-            pht(
-              'The `%s` and `%s` statements no longer accept '.
-              'variable arguments.',
-              'break',
-              'continue'));
-          break;
-      }
-    }
-  }
-
-  private function lintImplicitFallthrough(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),
-            self::LINT_IMPLICIT_FALLTHROUGH,
-            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'));
-        }
-      }
-    }
-  }
-
-  private function lintBraceFormatting(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,
-          self::LINT_BRACE_FORMATTING,
-          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,
-            self::LINT_BRACE_FORMATTING,
-            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,
-          self::LINT_BRACE_FORMATTING,
-          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,
-          self::LINT_BRACE_FORMATTING,
-          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,
-            self::LINT_BRACE_FORMATTING,
-            pht(
-              'Put opening braces on the same line as control statements and '.
-              'declarations, with a single space before them.'),
-            ' ');
-        }
-      }
-    }
-  }
-
-  private function lintTautologicalExpressions(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,
-            self::LINT_TAUTOLOGICAL_EXPRESSION,
-            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,
-            self::LINT_TAUTOLOGICAL_EXPRESSION,
-            pht(
-              'The logical value of this expression is static. '.
-              'Did you forget to remove some debugging code?'));
-        }
-      }
-    }
-  }
-
-  /**
-   * 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.
-   */
-  private function evaluateStaticBoolean($string) {
-    switch (strtolower($string)) {
-      case '0':
-      case 'null':
-      case 'false':
-        return false;
-      case '1':
-      case 'true':
-        return true;
-    }
-    return null;
-  }
-
-  protected function lintCommentSpaces(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(),
-            self::LINT_COMMENT_SPACING,
-            pht('Put space after comment start.'),
-            $match[1],
-            $match[1].' ');
-        }
-      }
-    }
-  }
-
-  protected function lintHashComments(XHPASTNode $root) {
-    foreach ($root->selectTokensOfType('T_COMMENT') as $comment) {
-      $value = $comment->getValue();
-      if ($value[0] !== '#') {
-        continue;
-      }
-
-      $this->raiseLintAtOffset(
-        $comment->getOffset(),
-        self::LINT_COMMENT_STYLE,
-        pht('Use "%s" single-line comments, not "%s".', '//', '#'),
-        '#',
-        (preg_match('/^#\S/', $value) ? '// ' : '//'));
-    }
-  }
-
-  /**
-   * 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
-   *      // ...
-   *    }
-   *  }
-   *
-   */
-  private function lintReusedIterators(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),
-            self::LINT_REUSED_ITERATORS,
-            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);
-        }
-      }
-    }
-  }
-
-  /**
-   * 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
-   *
-   */
-  protected function lintReusedIteratorReferences(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,
-            self::LINT_REUSED_ITERATOR_REFERENCE,
-            pht(
-              'This variable was used already as a by-reference iterator '.
-              'variable. Such variables survive outside the foreach loop, '.
-              'do not reuse.'));
-        }
-      }
-
-    }
-  }
-
-  protected function lintVariableVariables(XHPASTNode $root) {
-    $vvars = $root->selectDescendantsOfType('n_VARIABLE_VARIABLE');
-    foreach ($vvars as $vvar) {
-      $this->raiseLintAtNode(
-        $vvar,
-        self::LINT_VARIABLE_VARIABLE,
-        pht(
-          'Rewrite this code to use an array. Variable variables are unclear '.
-          'and hinder static analysis.'));
-    }
-  }
-
-  private function lintUndeclaredVariables(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') {
-          // 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());
-        $this->raiseLintAtNode(
-          $call,
-          self::LINT_EXTRACT_USE,
-          pht(
-            'Avoid %s. It is confusing and hinders static analysis.',
-            'extract()'));
-      }
-
-      // 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,
-                self::LINT_REUSED_AS_ITERATOR,
-                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;
-      }
-
-      // 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
-      // exlcuded 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,
-          self::LINT_UNDECLARED_VARIABLE,
-          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;
-      }
-    }
-  }
-
-  private function getConcreteVariableString(XHPASTNode $var) {
-    $concrete = $var->getConcreteString();
-    // Strip off curly braces as in $obj->{$property}.
-    $concrete = trim($concrete, '{}');
-    return $concrete;
-  }
-
-  private function lintPHPTagUse(XHPASTNode $root) {
-    $tokens = $root->getTokens();
-    foreach ($tokens as $token) {
-      if ($token->getTypeName() === 'T_OPEN_TAG') {
-        if (trim($token->getValue()) === '<?') {
-          $this->raiseLintAtToken(
-            $token,
-            self::LINT_PHP_SHORT_TAG,
-            pht(
-              'Use the full form of the PHP open tag, "%s".',
-              '<?php'),
-            "<?php\n");
-        }
-        break;
-      } else if ($token->getTypeName() === 'T_OPEN_TAG_WITH_ECHO') {
-        $this->raiseLintAtToken(
-          $token,
-          self::LINT_PHP_ECHO_TAG,
-          pht('Avoid the PHP echo short form, "%s".', '<?='));
-        break;
-      } else {
-        if (!preg_match('/^#!/', $token->getValue())) {
-          $this->raiseLintAtToken(
-            $token,
-            self::LINT_PHP_OPEN_TAG,
-            pht(
-              'PHP files should start with "%s", which may be preceded by '.
-              'a "%s" line for scripts.',
-              '<?php',
-              '#!'));
-        }
-        break;
-      }
-    }
-
-    foreach ($root->selectTokensOfType('T_CLOSE_TAG') as $token) {
-      $this->raiseLintAtToken(
-        $token,
-        self::LINT_PHP_CLOSE_TAG,
-        pht('Do not use the PHP closing tag, "%s".', '?>'));
-    }
-  }
-
-  private function lintNamingConventions(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,
-          self::LINT_NAMING_CONVENTIONS,
-          $result);
-      }
-    }
-  }
-
-  private function lintSurpriseConstructors(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,
-            self::LINT_IMPLICIT_CONSTRUCTOR,
-            pht(
-              'Name constructors %s explicitly. This method is a constructor '.
-              ' because it has the same name as the class it is defined in.',
-              '__construct()'));
-        }
-      }
-    }
-  }
-
-  private function lintParenthesesShouldHugExpressions(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(),
-          self::LINT_PARENTHESES_SPACING,
-          pht('Parentheses should hug their contents.'),
-          $string,
-          '');
-      }
-    }
-  }
-
-  private function lintSpaceAfterControlStatementKeywords(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,
-              self::LINT_CONTROL_STATEMENT_SPACING,
-              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,
-                self::LINT_CONTROL_STATEMENT_SPACING,
-                pht('Convention: put a single space after control statements.'),
-                ' ');
-            }
-          }
-          break;
-      }
-    }
-  }
-
-  private function lintSpaceAroundBinaryOperators(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,
-          self::LINT_BINARY_EXPRESSION_SPACING,
-          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,
-            self::LINT_BINARY_EXPRESSION_SPACING,
-            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,
-          self::LINT_BINARY_EXPRESSION_SPACING,
-          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,
-          self::LINT_BINARY_EXPRESSION_SPACING,
-          pht(
-            'Convention: logical and arithmetic operators should be '.
-            'surrounded by whitespace.'),
-          $replace);
-      }
-    }
-  }
-
-  private function lintSpaceAroundConcatenationOperators(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,
-          self::LINT_CONCATENATION_OPERATOR,
-          pht(
-            'Convention: no spaces around "%s" '.
-            '(string concatenation) operator.',
-            '.'),
-          '');
-      }
-    }
-  }
-
-  private function lintDynamicDefines(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,
-          self::LINT_DYNAMIC_DEFINE,
-          pht(
-            'First argument to %s must be a string literal.',
-            'define()'));
-      }
-    }
-  }
-
-  private function lintUseOfThisInStaticMethods(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,
-              self::LINT_STATIC_THIS,
-              pht(
-                'You can not reference `%s` inside a static method.',
-                '$this'));
-          }
-        }
-      }
-    }
-  }
-
-  /**
-   * 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.
-   */
-  private function lintPregQuote(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,
-          self::LINT_PREG_QUOTE_MISUSE,
-          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()'));
-      }
-    }
-  }
-
-  /**
-   * 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!
-   */
-  private function lintExitExpressions(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,
-            self::LINT_EXIT_EXPRESSION,
-            pht('Use `%s` as a statement, not an expression.', 'exit'));
-        }
-      }
-    }
-  }
-
-  private function lintArrayIndexWhitespace(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()),
-          self::LINT_ARRAY_INDEX_SPACING,
-          pht('Convention: no spaces before index access.'),
-          $trailing_text,
-          '');
-      }
-    }
-  }
-
-  private function lintTodoComments(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,
-          self::LINT_TODO_COMMENT,
-          pht('This comment has a TODO.'),
-          $string);
-      }
-    }
-  }
-
-  /**
-   * Lint that if the file declares exactly one interface or class,
-   * the name of the file matches the name of the class,
-   * unless the classname is funky like an XHP element.
-   */
-  private function lintPrimaryDeclarationFilenameMatch(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,
-      self::LINT_CLASS_FILENAME_MISMATCH,
-      pht(
-        "The name of this file differs from the name of the ".
-        "class or interface it declares. Rename the file to '%s'.",
-        $rename));
-  }
-
-  private function lintPlusOperatorOnStrings(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,
-          self::LINT_PLUS_OPERATOR_ON_STRINGS,
-          pht(
-            "In PHP, '%s' is the string concatenation operator, not '%s'. ".
-            "This expression uses '+' with a string literal as an operand.",
-            '.',
-            '+'));
-      }
-    }
-  }
-
-  /**
-   * 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.
-   */
-  private function lintDuplicateKeysInArray(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,
-          self::LINT_DUPLICATE_KEYS_IN_ARRAY,
-          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);
-      }
-    }
-  }
-
-  private function lintClosingCallParen(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),
-          self::LINT_CLOSING_CALL_PAREN,
-          pht('Convention: no spaces before closing parenthesis in calls.'),
-          $trailing_text,
-          '');
-      }
-    }
-  }
-
-  private function lintClosingDeclarationParen(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),
-          self::LINT_CLOSING_DECL_PAREN,
-          pht(
-            'Convention: no spaces before closing parenthesis in '.
-            'function and method declarations.'),
-          $trailing_text,
-          '');
-      }
-    }
-  }
-
-  private function lintKeywordCasing(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,
-          self::LINT_KEYWORD_CASING,
-          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,
-            self::LINT_KEYWORD_CASING,
-            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,
-          self::LINT_KEYWORD_CASING,
-          pht('Magic constants should be uppercase.'),
-          strtoupper($value));
-      }
-    }
-  }
-
-  private function lintStrings(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,
-            self::LINT_DOUBLE_QUOTE,
-            pht(
-              'String does not require double quotes. For consistency, '.
-              'prefer single quotes.'),
-            $fixes[$invalid_node->getID()]);
-        }
-      }
-    }
-  }
-
-  protected function lintElseIfStatements(XHPASTNode $root) {
-    $tokens = $root->selectTokensOfType('T_ELSEIF');
-
-    foreach ($tokens as $token) {
-      $this->raiseLintAtToken(
-        $token,
-        self::LINT_ELSEIF_USAGE,
-        pht('Usage of `%s` is preferred over `%s`.', 'else if', 'elseif'),
-        'else if');
-    }
-  }
-
-  protected function lintSemicolons(XHPASTNode $root) {
-    $tokens = $root->selectTokensOfType(';');
-
-    foreach ($tokens as $token) {
-      $prev = $token->getPrevToken();
-
-      if ($prev->isAnyWhitespace()) {
-        $this->raiseLintAtToken(
-          $prev,
-          self::LINT_SEMICOLON_SPACING,
-          pht('Space found before semicolon.'),
-          '');
-      }
-    }
-  }
-
-  protected function lintLanguageConstructParentheses(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,
-          self::LINT_LANGUAGE_CONSTRUCT_PAREN,
-          pht('Language constructs do not require parentheses.'),
-          $replace);
-      }
-    }
-  }
-
-  protected function lintEmptyBlockStatements(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,
-          self::LINT_EMPTY_STATEMENT,
-          pht(
-            "Braces for an empty block statement shouldn't ".
-            "contain only whitespace."),
-          '{}');
-      }
-    }
-  }
-
-  protected function lintArraySeparator(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(),
-          self::LINT_ARRAY_SEPARATOR,
-          pht('Multi-lined arrays should have trailing commas.'),
-          $original,
-          $replacement);
-        } else if ($value->getLineNumber() == $array->getEndLineNumber()) {
-          $close = last($array->getTokens());
-
-          $this->raiseLintAtToken(
-            $close,
-            self::LINT_ARRAY_SEPARATOR,
-            pht('Closing parenthesis should be on a new line.'),
-            "\n".$array->getIndentation().$close->getValue());
-        }
-      } else if ($after && $after->getValue() == ',') {
-        $this->raiseLintAtToken(
-          $after,
-          self::LINT_ARRAY_SEPARATOR,
-          pht('Single lined arrays should not have a trailing comma.'),
-          '');
-      }
-    }
-  }
-
-  private function lintConstructorParentheses(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,
-          self::LINT_CONSTRUCTOR_PARENTHESES,
-          pht('Use parentheses when invoking a constructor.'),
-          $class->getConcreteString().'()');
-      }
-    }
-  }
-
-  private function lintSwitchStatements(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,
-          self::LINT_DUPLICATE_SWITCH_CASE,
-          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);
-      }
-    }
-  }
-
-  private function lintBlacklistedFunction(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,
-          self::LINT_BLACKLISTED_FUNCTION,
-          $reason);
-      }
-    }
-  }
-
-  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,
-        self::LINT_IMPLICIT_VISIBILITY,
-        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,
-            self::LINT_IMPLICIT_VISIBILITY,
-            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,
-        self::LINT_IMPLICIT_VISIBILITY,
-        pht('Properties should have their visibility declared explicitly.'),
-        'public '.$node->getConcreteString());
-    }
-  }
-
-  private function lintCallTimePassByReference(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,
-          self::LINT_CALL_TIME_PASS_BY_REF,
-          pht('Call-time pass-by-reference calls are prohibited.'));
-      }
-    }
-  }
-
-  private function lintFormattedString(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,
-          self::LINT_FORMATTED_STRING,
-          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,
-          self::LINT_FORMATTED_STRING,
-          str_replace('xsprintf', $name, $ex->getMessage()));
-      } catch (InvalidArgumentException $ex) {
-        // Ignore.
-      }
-    }
-  }
-
-  private function lintUnnecessaryFinalModifier(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,
-              self::LINT_UNNECESSARY_FINAL_MODIFIER,
-              pht(
-                'Unnecessary %s modifier in %s class.',
-                'final',
-                'final'));
-          }
-        }
-      }
-    }
-  }
-
-  private function lintUnnecessarySemicolons(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,
-          self::LINT_UNNECESSARY_SEMICOLON,
-          pht('Unnecessary semicolons after statement.'),
-          '');
-      }
-    }
-  }
-
-  private function lintConstantDefinitions(XHPASTNode $root) {
-    $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,
-          self::LINT_NAMING_CONVENTIONS,
-          pht('Constants should be uppercase.'));
-      }
-    }
-  }
-
-  private function lintSelfMemberReference(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,
-            self::LINT_SELF_MEMBER_REFERENCE,
-            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,
-            self::LINT_SELF_MEMBER_REFERENCE,
-            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,
-            self::LINT_SELF_MEMBER_REFERENCE,
-            pht('Unnecessary whitespace around double colon operator.'),
-            '');
-        }
-      }
-    }
-  }
-
-  private function lintLogicalOperators(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,
-        self::LINT_LOGICAL_OPERATORS,
-        pht('Use `%s` instead of `%s`.', '&&', 'and'),
-        '&&');
-    }
-
-    foreach ($logical_ors as $logical_or) {
-      $this->raiseLintAtToken(
-        $logical_or,
-        self::LINT_LOGICAL_OPERATORS,
-        pht('Use `%s` instead of `%s`.', '||', 'or'),
-        '||');
-    }
-  }
-
-  private function lintInnerFunctions(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,
-          self::LINT_INNER_FUNCTION,
-          pht('Avoid the use of inner functions.'));
-      }
-    }
-  }
-
-  private function lintDefaultParameters(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,
-            self::LINT_DEFAULT_PARAMETERS,
-            pht(
-              'Arguments with default values must be at the end '.
-              'of the argument list.'));
-        }
-      }
-    }
-  }
-
-  private function lintLowercaseFunctions(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,
-          self::LINT_LOWERCASE_FUNCTIONS,
-          pht('Calls to built-in PHP functions should be lowercase.'),
-          strtolower($function_name));
-      }
-    }
-  }
-
-  private function lintClassNameLiteral(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,
-          self::LINT_CLASS_NAME_LITERAL,
-          pht(
-            "Don't hard-code class names, use %s instead.",
-            '__CLASS__'),
-          $replacement);
-      }
-    }
-  }
-
-  private function lintUselessOverridingMethods(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,
-        self::LINT_USELESS_OVERRIDING_METHOD,
-        pht('Useless overriding method.'));
-    }
-  }
-
-  private function lintNoParentScope(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,
-              self::LINT_NO_PARENT_SCOPE,
-              pht(
-                'Cannot access %s when current class scope has no parent.',
-                'parent::'));
-          }
-        }
-      }
-    }
-  }
-
-  private function lintAliasFunctions(XHPASTNode $root) {
-    static $aliases = 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',
-    );
-
-    $functions = $this->getFunctionCalls($root, array_keys($aliases));
-
-    foreach ($functions as $function) {
-      $function_name = $function->getChildByIndex(0);
-
-      $this->raiseLintAtNode(
-        $function_name,
-        self::LINT_ALIAS_FUNCTION,
-        pht('Alias functions should be avoided.'),
-        $aliases[phutil_utf8_strtolower($function_name->getConcreteString())]);
-    }
-  }
-
-  private function lintCastSpacing(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,
-          self::LINT_CAST_SPACING,
-          pht('A cast statement must not be followed by a space.'),
-          '');
-      }
-    }
-  }
-
-  private function lintThrowExceptionInToStringMethod(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,
-          self::LINT_TOSTRING_EXCEPTION,
-          pht(
-            'It is not possible to throw an %s from within the %s method.',
-            'Exception',
-            '__toString'));
-      }
-    }
-  }
-
-  private function lintLambdaFuncFunction(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,
-        self::LINT_LAMBDA_FUNC_FUNCTION,
-        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)'));
-    }
-  }
-
-  private function lintInstanceOfOperator(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,
-          self::LINT_INSTANCEOF_OPERATOR,
-          pht(
-            '%s expects an object instance, constant given.',
-            'instanceof'));
-      }
-    }
-  }
-
-  private function lintInvalidDefaultParameters(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,
-            self::LINT_INVALID_DEFAULT_PARAMETER,
-            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,
-            self::LINT_INVALID_DEFAULT_PARAMETER,
-            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,
-            self::LINT_INVALID_DEFAULT_PARAMETER,
-            pht(
-              'Default value for parameters with a class type hint '.
-              'can only be %s.',
-              'null'));
-          break;
-      }
-    }
-  }
-
-  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,
-          self::LINT_MODIFIER_ORDERING,
-          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,
-          self::LINT_MODIFIER_ORDERING,
-          pht('Non-conventional modifier ordering.'),
-          implode(' ', $expected_modifier_ordering));
-      }
-    }
-  }
-
-  private function lintInvalidModifiers(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,
-                self::LINT_INVALID_MODIFIERS,
-                pht(
-                  'Properties cannot be declared %s.',
-                  'abstract'));
-            }
-
-            if ($is_abstract) {
-              $this->raiseLintAtNode(
-                $modifier,
-                self::LINT_INVALID_MODIFIERS,
-                pht(
-                  'Multiple %s modifiers are not allowed.',
-                  'abstract'));
-            }
-
-            if ($is_final) {
-              $this->raiseLintAtNode(
-                $modifier,
-                self::LINT_INVALID_MODIFIERS,
-                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,
-                self::LINT_INVALID_MODIFIERS,
-                pht(
-                'Cannot use the %s modifier on an %s class member',
-                'final',
-                'abstract'));
-            }
-
-            if ($is_final) {
-              $this->raiseLintAtNode(
-                $modifier,
-                self::LINT_INVALID_MODIFIERS,
-                pht(
-                  'Multiple %s modifiers are not allowed.',
-                  'final'));
-            }
-
-            $is_final = true;
-            break;
-          case 'public':
-          case 'protected':
-          case 'private':
-            if ($visibility) {
-              $this->raiseLintAtNode(
-                $modifier,
-                self::LINT_INVALID_MODIFIERS,
-                pht('Multiple access type modifiers are not allowed.'));
-            }
-
-            $visibility = $modifier->getConcreteString();
-            break;
-
-          case 'static':
-            if ($is_static) {
-              $this->raiseLintAtNode(
-                $modifier,
-                self::LINT_INVALID_MODIFIERS,
-                pht(
-                  'Multiple %s modifiers are not allowed.',
-                  'static'));
-            }
-            break;
-        }
+    foreach ($this->rules as $rule) {
+      if ($this->isCodeEnabled($rule->getLintID())) {
+        $rule->setLinter($this);
+        $rule->process($root);
       }
     }
   }
diff --git a/src/lint/linter/ArcanistXHPASTLinterRule.php b/src/lint/linter/ArcanistXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/ArcanistXHPASTLinterRule.php
@@ -0,0 +1,179 @@
+<?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();
+  abstract public function getLintSeverity();
+
+  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) {
+
+    // TODO: This is an ugly hack.
+    $method = new ReflectionMethod($this->linter, 'raiseLintAtNode');
+    $method->setAccessible(true);
+
+    return $method->invoke(
+      $this->linter,
+      $node,
+      $this->getLintID(),
+      $desc,
+      $replace);
+  }
+
+  final protected function raiseLintAtOffset(
+    $offset,
+    $desc,
+    $text = null,
+    $replace = null) {
+
+    // TODO: This is an ugly hack.
+    $method = new ReflectionMethod($this->linter, 'raiseLintAtOffset');
+    $method->setAccessible(true);
+
+    return $method->invoke(
+      $this->linter,
+      $offset,
+      $this->getLintID(),
+      $desc,
+      $text,
+      $replace);
+  }
+
+  final protected function raiseLintAtToken(
+    XHPASTToken $token,
+    $desc,
+    $replace = null) {
+
+    // TODO: This is an ugly hack.
+    $method = new ReflectionMethod($this->linter, 'raiseLintAtToken');
+    $method->setAccessible(true);
+
+    return $method->invoke(
+      $this->linter,
+      $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',
+    );
+  }
+
+}
diff --git a/src/lint/linter/__tests__/xhpast/alias-functions.lint-test b/src/lint/linter/__tests__/xhpast/alias-functions.lint-test
--- a/src/lint/linter/__tests__/xhpast/alias-functions.lint-test
+++ b/src/lint/linter/__tests__/xhpast/alias-functions.lint-test
@@ -3,7 +3,7 @@
 $x = array();
 sizeof($x);
 die();
-sizeOf($x);
+sizeOf($x); // fixme
 ~~~~~~~~~~
 advice:4:1
 advice:5:1
@@ -14,4 +14,4 @@
 $x = array();
 count($x);
 exit();
-count($x);
+sizeof($x); // fixme
diff --git a/src/lint/linter/__tests__/xhpast/blacklisted.lint-test b/src/lint/linter/__tests__/xhpast/blacklisted.lint-test
--- a/src/lint/linter/__tests__/xhpast/blacklisted.lint-test
+++ b/src/lint/linter/__tests__/xhpast/blacklisted.lint-test
@@ -4,4 +4,10 @@
 error:2:1
 ~~~~~~~~~~
 ~~~~~~~~~~
-{"config": {"xhpast.blacklisted.function": {"eval": "Evil function"}}}
+{
+  "config": {
+    "xhpast.blacklisted.function": {
+      "eval": "Evil function"
+    }
+  }
+}
diff --git a/src/lint/linter/__tests__/xhpast/conditional-usage.lint-test b/src/lint/linter/__tests__/xhpast/conditional-usage.lint-test
--- a/src/lint/linter/__tests__/xhpast/conditional-usage.lint-test
+++ b/src/lint/linter/__tests__/xhpast/conditional-usage.lint-test
@@ -26,4 +26,8 @@
 warning:16:3
 ~~~~~~~~~~
 ~~~~~~~~~~
-{"config": {"xhpast.php-version": "5.3.0"}}
+{
+  "config": {
+    "xhpast.php-version": "5.3.0"
+  }
+}
diff --git a/src/lint/linter/__tests__/xhpast/index-function.lint-test b/src/lint/linter/__tests__/xhpast/index-function.lint-test
--- a/src/lint/linter/__tests__/xhpast/index-function.lint-test
+++ b/src/lint/linter/__tests__/xhpast/index-function.lint-test
@@ -4,4 +4,8 @@
 error:2:5
 ~~~~~~~~~~
 ~~~~~~~~~~
-{"config": {"xhpast.php-version": "5.3.0"}}
+{
+  "config": {
+    "xhpast.php-version": "5.3.0"
+  }
+}
diff --git a/src/lint/linter/__tests__/xhpast/nowdoc.lint-test b/src/lint/linter/__tests__/xhpast/nowdoc.lint-test
--- a/src/lint/linter/__tests__/xhpast/nowdoc.lint-test
+++ b/src/lint/linter/__tests__/xhpast/nowdoc.lint-test
@@ -6,4 +6,8 @@
 error:2:6
 ~~~~~~~~~~
 ~~~~~~~~~~
-{"config": {"xhpast.php-version": "5.2.3"}}
+{
+  "config": {
+    "xhpast.php-version": "5.2.3"
+  }
+}
diff --git a/src/lint/linter/__tests__/xhpast/php-compatibility.lint-test b/src/lint/linter/__tests__/xhpast/php-compatibility.lint-test
--- a/src/lint/linter/__tests__/xhpast/php-compatibility.lint-test
+++ b/src/lint/linter/__tests__/xhpast/php-compatibility.lint-test
@@ -7,4 +7,8 @@
 error:4:10
 ~~~~~~~~~~
 ~~~~~~~~~~
-{"config": {"xhpast.php-version": "5.4.0"}}
+{
+  "config": {
+    "xhpast.php-version": "5.4.0"
+  }
+}
diff --git a/src/lint/linter/__tests__/xhpast/php53-features.lint-test b/src/lint/linter/__tests__/xhpast/php53-features.lint-test
--- a/src/lint/linter/__tests__/xhpast/php53-features.lint-test
+++ b/src/lint/linter/__tests__/xhpast/php53-features.lint-test
@@ -22,4 +22,8 @@
 error:13:6
 ~~~~~~~~~~
 ~~~~~~~~~~
-{"config": {"xhpast.php-version": "5.2.3"}}
+{
+  "config": {
+    "xhpast.php-version": "5.2.3"
+  }
+}
diff --git a/src/lint/linter/__tests__/xhpast/php54-features.lint-test b/src/lint/linter/__tests__/xhpast/php54-features.lint-test
--- a/src/lint/linter/__tests__/xhpast/php54-features.lint-test
+++ b/src/lint/linter/__tests__/xhpast/php54-features.lint-test
@@ -11,4 +11,8 @@
 error:4:9
 ~~~~~~~~~~
 ~~~~~~~~~~
-{"config": {"xhpast.php-version": "5.3.0"}}
+{
+  "config": {
+    "xhpast.php-version": "5.3.0"
+  }
+}
diff --git a/src/lint/linter/__tests__/xhpast/switches.lint-test b/src/lint/linter/__tests__/xhpast/switches.lint-test
--- a/src/lint/linter/__tests__/xhpast/switches.lint-test
+++ b/src/lint/linter/__tests__/xhpast/switches.lint-test
@@ -93,4 +93,8 @@
 warning:75:3
 ~~~~~~~~~~
 ~~~~~~~~~~
-{"config":{"xhpast.switchhook":"ArcanistTestXHPASTLintSwitchHook"}}
+{
+  "config":{
+    "xhpast.switchhook":"ArcanistTestXHPASTLintSwitchHook"
+  }
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php
@@ -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',
+    );
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistArrayIndexSpacingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistArrayIndexSpacingXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistArrayIndexSpacingXHPASTLinterRule.php
@@ -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,
+          '');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistArraySeparatorXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistArraySeparatorXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistArraySeparatorXHPASTLinterRule.php
@@ -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.'),
+          '');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistBinaryExpressionSpacingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistBinaryExpressionSpacingXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistBinaryExpressionSpacingXHPASTLinterRule.php
@@ -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);
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistBlacklistedFunctionXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistBlacklistedFunctionXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistBlacklistedFunctionXHPASTLinterRule.php
@@ -0,0 +1,53 @@
+<?php
+
+final class ArcanistBlacklistedFunctionXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 51;
+
+  private $blacklistedFunctions = array();
+
+  public function getLintName() {
+    return pht('Use of Blacklisted Function');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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);
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php
@@ -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.'),
+            ' ');
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistCallTimePassByReferenceXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistCallTimePassByReferenceXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistCallTimePassByReferenceXHPASTLinterRule.php
@@ -0,0 +1,30 @@
+<?php
+
+final class ArcanistCallTimePassByReferenceXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 53;
+
+  public function getLintName() {
+    return pht('Call-Time Pass-By-Reference');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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.'));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistCastSpacingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistCastSpacingXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistCastSpacingXHPASTLinterRule.php
@@ -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.'),
+          '');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistClassFilenameMismatchXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistClassFilenameMismatchXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistClassFilenameMismatchXHPASTLinterRule.php
@@ -0,0 +1,58 @@
+<?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 getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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));
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistClassNameLiteralXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistClassNameLiteralXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistClassNameLiteralXHPASTLinterRule.php
@@ -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);
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistClosingCallParenthesesXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistClosingCallParenthesesXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistClosingCallParenthesesXHPASTLinterRule.php
@@ -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,
+          '');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistClosingDeclarationParenthesesXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistClosingDeclarationParenthesesXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistClosingDeclarationParenthesesXHPASTLinterRule.php
@@ -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,
+          '');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistCommentSpacingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistCommentSpacingXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistCommentSpacingXHPASTLinterRule.php
@@ -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].' ');
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistCommentStyleXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistCommentStyleXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistCommentStyleXHPASTLinterRule.php
@@ -0,0 +1,32 @@
+<?php
+
+final class ArcanistCommentStyleXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 18;
+
+  public function getLintName() {
+    return pht('Comment Style');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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) ? '// ' : '//');
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistConcatenationOperatorXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistConcatenationOperatorXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistConcatenationOperatorXHPASTLinterRule.php
@@ -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.'),
+          '');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistConstructorParenthesesXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistConstructorParenthesesXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistConstructorParenthesesXHPASTLinterRule.php
@@ -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().'()');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistControlStatementSpacingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistControlStatementSpacingXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistControlStatementSpacingXHPASTLinterRule.php
@@ -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;
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistDefaultParametersXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistDefaultParametersXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistDefaultParametersXHPASTLinterRule.php
@@ -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.'));
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistDoubleQuoteXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistDoubleQuoteXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistDoubleQuoteXHPASTLinterRule.php
@@ -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()]);
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistDuplicateKeysInArrayXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistDuplicateKeysInArrayXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistDuplicateKeysInArrayXHPASTLinterRule.php
@@ -0,0 +1,78 @@
+<?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 getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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);
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistDuplicateSwitchCaseXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistDuplicateSwitchCaseXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistDuplicateSwitchCaseXHPASTLinterRule.php
@@ -0,0 +1,53 @@
+<?php
+
+final class ArcanistDuplicateSwitchCaseXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 50;
+
+  public function getLintName() {
+    return pht('Duplicate Case Statements');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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);
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php
@@ -0,0 +1,33 @@
+<?php
+
+final class ArcanistDynamicDefineXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 12;
+
+  public function getLintName() {
+    return pht('Dynamic %s', 'define()');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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()'));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php
@@ -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');
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistEmptyStatementXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistEmptyStatementXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistEmptyStatementXHPASTLinterRule.php
@@ -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."),
+          '{}');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistExitExpressionXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistExitExpressionXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistExitExpressionXHPASTLinterRule.php
@@ -0,0 +1,47 @@
+<?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 getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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'));
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistExtractUseXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistExtractUseXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistExtractUseXHPASTLinterRule.php
@@ -0,0 +1,28 @@
+<?php
+
+final class ArcanistExtractUseXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 4;
+
+  public function getLintName() {
+    return pht('Use of %s', 'extract()');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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()'));
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistFormattedStringXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistFormattedStringXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistFormattedStringXHPASTLinterRule.php
@@ -0,0 +1,107 @@
+<?php
+
+final class ArcanistFormattedStringXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 54;
+
+  private $printfFunctions = array();
+
+  public function getLintName() {
+    return pht('Formatted String');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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.
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistImplicitConstructorXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistImplicitConstructorXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistImplicitConstructorXHPASTLinterRule.php
@@ -0,0 +1,39 @@
+<?php
+
+final class ArcanistImplicitConstructorXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 10;
+
+  public function getLintName() {
+    return pht('Implicit Constructor');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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()'));
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistImplicitFallthroughXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistImplicitFallthroughXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistImplicitFallthroughXHPASTLinterRule.php
@@ -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'));
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistImplicitVisibilityXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistImplicitVisibilityXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistImplicitVisibilityXHPASTLinterRule.php
@@ -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());
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistInnerFunctionXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistInnerFunctionXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistInnerFunctionXHPASTLinterRule.php
@@ -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.'));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistInstanceOfOperatorXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistInstanceOfOperatorXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistInstanceOfOperatorXHPASTLinterRule.php
@@ -0,0 +1,39 @@
+<?php
+
+final class ArcanistInstanceOfOperatorXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 69;
+
+  public function getLintName() {
+    return pht('%s Operator', 'instanceof');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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'));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistInvalidDefaultParameterXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistInvalidDefaultParameterXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistInvalidDefaultParameterXHPASTLinterRule.php
@@ -0,0 +1,83 @@
+<?php
+
+final class ArcanistInvalidDefaultParameterXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 70;
+
+  public function getLintName() {
+    return pht('Invalid Default Parameter');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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;
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistInvalidModifiersXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistInvalidModifiersXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistInvalidModifiersXHPASTLinterRule.php
@@ -0,0 +1,107 @@
+<?php
+
+final class ArcanistInvalidModifiersXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 72;
+
+  public function getLintName() {
+    return pht('Invalid Modifiers');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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;
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistKeywordCasingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistKeywordCasingXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistKeywordCasingXHPASTLinterRule.php
@@ -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));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistLambdaFuncFunctionXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistLambdaFuncFunctionXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistLambdaFuncFunctionXHPASTLinterRule.php
@@ -0,0 +1,47 @@
+<?php
+
+final class ArcanistLambdaFuncFunctionXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 68;
+
+  public function getLintName() {
+    return pht('%s Function', '__lambda_func');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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)'));
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistLanguageConstructParenthesesXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistLanguageConstructParenthesesXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistLanguageConstructParenthesesXHPASTLinterRule.php
@@ -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);
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistLogicalOperatorsXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistLogicalOperatorsXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistLogicalOperatorsXHPASTLinterRule.php
@@ -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'),
+        '||');
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistLowercaseFunctionsXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistLowercaseFunctionsXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistLowercaseFunctionsXHPASTLinterRule.php
@@ -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));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistModifierOrderingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistModifierOrderingXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistModifierOrderingXHPASTLinterRule.php
@@ -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));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistNamingConventionsXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistNamingConventionsXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistNamingConventionsXHPASTLinterRule.php
@@ -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.'));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistNoParentScopeXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistNoParentScopeXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistNoParentScopeXHPASTLinterRule.php
@@ -0,0 +1,49 @@
+<?php
+
+final class ArcanistNoParentScopeXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 64;
+
+  public function getLintName() {
+    return pht('No Parent Scope');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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::'));
+          }
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistPHPCloseTagXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistPHPCloseTagXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistPHPCloseTagXHPASTLinterRule.php
@@ -0,0 +1,24 @@
+<?php
+
+final class ArcanistPHPCloseTagXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 8;
+
+  public function getLintName() {
+    return pht('Use of Close Tag "%s"', '?>');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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".', '?>'));
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistPHPCompatibilityXHPASTLinterRule.php
@@ -0,0 +1,439 @@
+<?php
+
+final class ArcanistPHPCompatibilityXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 45;
+
+  private $version;
+  private $windowsVersion;
+
+  public function getLintName() {
+    return pht('PHP Compatibility');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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;
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistPHPEchoTagXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistPHPEchoTagXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistPHPEchoTagXHPASTLinterRule.php
@@ -0,0 +1,28 @@
+<?php
+
+final class ArcanistPHPEchoTagXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 7;
+
+  public function getLintName() {
+    return pht('Use of Echo Tag "%s"', '<?=');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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".', '<?='));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistPHPOpenTagXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistPHPOpenTagXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistPHPOpenTagXHPASTLinterRule.php
@@ -0,0 +1,39 @@
+<?php
+
+final class ArcanistPHPOpenTagXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 15;
+
+  public function getLintName() {
+    return pht('Expected Open Tag');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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;
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistPHPShortTagXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistPHPShortTagXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistPHPShortTagXHPASTLinterRule.php
@@ -0,0 +1,34 @@
+<?php
+
+final class ArcanistPHPShortTagXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 6;
+
+  public function getLintName() {
+    return pht('Use of Short Tag "%s"', '<?');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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;
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistParenthesesSpacingXHPASTLinterRule.php
@@ -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,
+          '');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php
@@ -0,0 +1,41 @@
+<?php
+
+final class ArcanistPlusOperatorOnStringsXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 21;
+
+  public function getLintName() {
+    return pht('Not String Concatenation');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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.",
+            '.',
+            '+'));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistPregQuoteMisuseXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistPregQuoteMisuseXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistPregQuoteMisuseXHPASTLinterRule.php
@@ -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()'));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistReusedAsIteratorXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistReusedAsIteratorXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistReusedAsIteratorXHPASTLinterRule.php
@@ -0,0 +1,281 @@
+<?php
+
+final class ArcanistReusedAsIteratorXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 32;
+
+  public function getLintName() {
+    return pht('Variable Reused As Iterator');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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;
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistReusedIteratorReferenceXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistReusedIteratorReferenceXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistReusedIteratorReferenceXHPASTLinterRule.php
@@ -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'));
+        }
+      }
+
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistReusedIteratorXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistReusedIteratorXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistReusedIteratorXHPASTLinterRule.php
@@ -0,0 +1,102 @@
+<?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 getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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);
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistSelfMemberReferenceXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistSelfMemberReferenceXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistSelfMemberReferenceXHPASTLinterRule.php
@@ -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.'),
+            '');
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistSemicolonSpacingXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistSemicolonSpacingXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistSemicolonSpacingXHPASTLinterRule.php
@@ -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.'),
+          '');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistSlownessXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistSlownessXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistSlownessXHPASTLinterRule.php
@@ -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()'));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistStaticThisXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistStaticThisXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistStaticThisXHPASTLinterRule.php
@@ -0,0 +1,64 @@
+<?php
+
+final class ArcanistStaticThisXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 13;
+
+  public function getLintName() {
+    return pht('Use of %s in Static Context', '$this');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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'));
+          }
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistSyntaxErrorXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistSyntaxErrorXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistSyntaxErrorXHPASTLinterRule.php
@@ -0,0 +1,20 @@
+<?php
+
+final class ArcanistSyntaxErrorXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 1;
+
+  public function getLintName() {
+    return pht('PHP Syntax Error!');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  public function process(XHPASTNode $root) {
+    // This linter rule isn't used explicitly.
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistTautologicalExpressionXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistTautologicalExpressionXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistTautologicalExpressionXHPASTLinterRule.php
@@ -0,0 +1,74 @@
+<?php
+
+final class ArcanistTautologicalExpressionXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 20;
+
+  public function getLintName() {
+    return pht('Tautological Expression');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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?'));
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistToStringExceptionXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistToStringExceptionXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistToStringExceptionXHPASTLinterRule.php
@@ -0,0 +1,47 @@
+<?php
+
+final class ArcanistToStringExceptionXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 67;
+
+  public function getLintName() {
+    return pht('Throwing Exception in %s Method', '__toString');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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'));
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistTodoCommentXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistTodoCommentXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistTodoCommentXHPASTLinterRule.php
@@ -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);
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistUnableToParseXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistUnableToParseXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistUnableToParseXHPASTLinterRule.php
@@ -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.
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistUndeclaredVariableXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistUndeclaredVariableXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistUndeclaredVariableXHPASTLinterRule.php
@@ -0,0 +1,349 @@
+<?php
+
+final class ArcanistUndeclaredVariableXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 5;
+
+  public function getLintName() {
+    return pht('Use of Undeclared Variable');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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;
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistUnnecessaryFinalModifierXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistUnnecessaryFinalModifierXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistUnnecessaryFinalModifierXHPASTLinterRule.php
@@ -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'));
+          }
+        }
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistUnnecessarySemicolonXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistUnnecessarySemicolonXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistUnnecessarySemicolonXHPASTLinterRule.php
@@ -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.'),
+          '');
+      }
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistUselessOverridingMethodXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistUselessOverridingMethodXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistUselessOverridingMethodXHPASTLinterRule.php
@@ -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.'));
+    }
+  }
+
+}
diff --git a/src/lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php
@@ -0,0 +1,28 @@
+<?php
+
+final class ArcanistVariableVariableXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 3;
+
+  public function getLintName() {
+    return pht('Use of Variable Variable');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_ERROR;
+  }
+
+  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.'));
+    }
+  }
+
+}