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
@@ -90,6 +90,7 @@
     'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDuplicateSwitchCaseXHPASTLinterRule.php',
     'ArcanistDynamicDefineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php',
     'ArcanistElseIfUsageXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php',
+    'ArcanistEmptyFileXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php',
     'ArcanistEmptyStatementXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyStatementXHPASTLinterRule.php',
     'ArcanistEventType' => 'events/constant/ArcanistEventType.php',
     'ArcanistExitExpressionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistExitExpressionXHPASTLinterRule.php',
@@ -375,6 +376,7 @@
     'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistDynamicDefineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistElseIfUsageXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
+    'ArcanistEmptyFileXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistEmptyStatementXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
     'ArcanistEventType' => 'PhutilEventType',
     'ArcanistExitExpressionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
diff --git a/src/lint/linter/ArcanistTextLinter.php b/src/lint/linter/ArcanistTextLinter.php
--- a/src/lint/linter/ArcanistTextLinter.php
+++ b/src/lint/linter/ArcanistTextLinter.php
@@ -13,6 +13,7 @@
   const LINT_TRAILING_WHITESPACE  = 6;
   const LINT_BOF_WHITESPACE       = 8;
   const LINT_EOF_WHITESPACE       = 9;
+  const LINT_EMPTY_FILE           = 10;
 
   private $maxLineLength = 80;
 
@@ -85,16 +86,23 @@
       self::LINT_TRAILING_WHITESPACE => pht('Trailing Whitespace'),
       self::LINT_BOF_WHITESPACE      => pht('Leading Whitespace at BOF'),
       self::LINT_EOF_WHITESPACE      => pht('Trailing Whitespace at EOF'),
+      self::LINT_EMPTY_FILE          => pht('Empty File'),
     );
   }
 
   public function lintPath($path) {
+    $this->lintEmptyFile($path);
+
     if (!strlen($this->getData($path))) {
       // If the file is empty, don't bother; particularly, don't require
       // the user to add a newline.
       return;
     }
 
+    if ($this->didStopAllLinters()) {
+      return;
+    }
+
     $this->lintNewlines($path);
     $this->lintTabs($path);
 
@@ -116,6 +124,29 @@
     $this->lintEOFWhitespace($path);
   }
 
+  protected function lintEmptyFile($path) {
+    $data = $this->getData($path);
+
+    // It is reasonable for certain file types to be completely empty,
+    // so they are excluded here.
+    switch ($filename = basename($this->getActivePath())) {
+      case '__init__.py':
+        return;
+
+      default:
+        if (strlen($filename) && $filename[0] == '.') {
+          return;
+        }
+    }
+
+    if (preg_match('/^\s*$/', $data)) {
+      $this->raiseLintAtPath(
+        self::LINT_EMPTY_FILE,
+        pht("Empty files usually don't serve any useful purpose."));
+      $this->stopAllLinters();
+    }
+  }
+
   protected function lintNewlines($path) {
     $data = $this->getData($path);
     $pos  = strpos($this->getData($path), "\r");
diff --git a/src/lint/linter/__tests__/text/empty-file.lint-test b/src/lint/linter/__tests__/text/empty.lint-test
rename from src/lint/linter/__tests__/text/empty-file.lint-test
rename to src/lint/linter/__tests__/text/empty.lint-test
--- a/src/lint/linter/__tests__/text/empty-file.lint-test
+++ b/src/lint/linter/__tests__/text/empty.lint-test
@@ -1 +1,4 @@
+
+
 ~~~~~~~~~~
+error::
diff --git a/src/lint/linter/__tests__/xhpast/empty.lint-test b/src/lint/linter/__tests__/xhpast/empty.lint-test
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/__tests__/xhpast/empty.lint-test
@@ -0,0 +1,3 @@
+<?php ?>
+~~~~~~~~~~
+warning::
diff --git a/src/lint/linter/__tests__/xhpast/not-empty.lint-test b/src/lint/linter/__tests__/xhpast/not-empty.lint-test
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/__tests__/xhpast/not-empty.lint-test
@@ -0,0 +1,4 @@
+<?php
+
+// This file is not empty.
+~~~~~~~~~~
diff --git a/src/lint/linter/__tests__/xhpast/php-tags-short.lint-test b/src/lint/linter/__tests__/xhpast/php-tags-short.lint-test
--- a/src/lint/linter/__tests__/xhpast/php-tags-short.lint-test
+++ b/src/lint/linter/__tests__/xhpast/php-tags-short.lint-test
@@ -1,6 +1,7 @@
 <?
 
 ~~~~~~~~~~
+warning::
 error:1:1
 ~~~~~~~~~~
 <?php
diff --git a/src/lint/linter/xhpast/ArcanistXHPASTLinterRule.php b/src/lint/linter/xhpast/ArcanistXHPASTLinterRule.php
--- a/src/lint/linter/xhpast/ArcanistXHPASTLinterRule.php
+++ b/src/lint/linter/xhpast/ArcanistXHPASTLinterRule.php
@@ -146,6 +146,10 @@
       $replace);
   }
 
+  final protected function raiseLintAtPath($desc) {
+    return $this->linter->raiseLintAtPath($this->getLintID(), $desc);
+  }
+
   final protected function raiseLintAtToken(
     XHPASTToken $token,
     $desc,
diff --git a/src/lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php b/src/lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php
@@ -0,0 +1,35 @@
+<?php
+
+final class ArcanistEmptyFileXHPASTLinterRule
+  extends ArcanistXHPASTLinterRule {
+
+  const ID = 82;
+
+  public function getLintName() {
+    return pht('Empty File');
+  }
+
+  public function getLintSeverity() {
+    return ArcanistLintSeverity::SEVERITY_WARNING;
+  }
+
+  public function process(XHPASTNode $root) {
+    $tokens = $root->getTokens();
+
+    foreach ($tokens as $token) {
+      switch ($token->getTypeName()) {
+        case 'T_OPEN_TAG':
+        case 'T_CLOSE_TAG':
+        case 'T_WHITESPACE':
+          break;
+
+        default:
+          return;
+      }
+    }
+
+    $this->raiseLintAtPath(
+      pht("Empty files usually don't serve any useful purpose."));
+  }
+
+}