Page MenuHomePhabricator

D10105.diff
No OneTemporary

D10105.diff

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
@@ -83,6 +83,8 @@
'ArcanistGitHookPreReceiveWorkflow' => 'workflow/ArcanistGitHookPreReceiveWorkflow.php',
'ArcanistGoLintLinter' => 'lint/linter/ArcanistGoLintLinter.php',
'ArcanistGoLintLinterTestCase' => 'lint/linter/__tests__/ArcanistGoLintLinterTestCase.php',
+ 'ArcanistHackLinter' => 'lint/linter/ArcanistHackLinter.php',
+ 'ArcanistHackLinterTestCase' => 'lint/linter/__tests__/ArcanistHackLinterTestCase.php',
'ArcanistHelpWorkflow' => 'workflow/ArcanistHelpWorkflow.php',
'ArcanistHgClientChannel' => 'hgdaemon/ArcanistHgClientChannel.php',
'ArcanistHgProxyClient' => 'hgdaemon/ArcanistHgProxyClient.php',
@@ -273,6 +275,8 @@
'ArcanistGitHookPreReceiveWorkflow' => 'ArcanistWorkflow',
'ArcanistGoLintLinter' => 'ArcanistExternalLinter',
'ArcanistGoLintLinterTestCase' => 'ArcanistArcanistLinterTestCase',
+ 'ArcanistHackLinter' => 'ArcanistLinter',
+ 'ArcanistHackLinterTestCase' => 'ArcanistArcanistLinterTestCase',
'ArcanistHelpWorkflow' => 'ArcanistWorkflow',
'ArcanistHgClientChannel' => 'PhutilProtocolChannel',
'ArcanistHgServerChannel' => 'PhutilProtocolChannel',
diff --git a/src/lint/linter/ArcanistHackLinter.php b/src/lint/linter/ArcanistHackLinter.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/ArcanistHackLinter.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * Calls `hh_client` and parses messages issued by the hack type checker
+ */
+final class ArcanistHackLinter extends ArcanistLinter {
+
+ public function getInfoName() {
+ return 'Hack for HipHop';
+ }
+
+ public function getInfoURI() {
+ return 'http://hacklang.org/';
+ }
+
+ public function getInfoDescription() {
+ return pht('The Hack type checker for <?hh code.');
+ }
+
+ public function getLinterName() {
+ return 'HACK';
+ }
+
+ public function getLinterConfigurationName() {
+ return 'hh_client';
+ }
+
+ public function getCacheGranularity() {
+ return self::GRANULARITY_REPOSITORY;
+ }
+
+ public function getVersion() {
+ list($err, $stdout, $stderr) = exec_manual(
+ 'hh_client --json check %s',
+ $this->getEngine()->getWorkingCopy()->getProjectRoot());
+
+ if ($err !== 0 && $err !== 2) {
+ return false;
+ }
+
+ $output = last(explode("\n", trim($stderr)));
+ return idx(phutil_json_decode($output), 'version', false);
+ }
+
+ public function willLintPaths(array $paths) {
+ list($err, $stdout, $stderr) =
+ id(new ExecFuture('hh_client --json check'))
+ ->setCWD($this->getEngine()->getWorkingCopy()->getProjectRoot())
+ ->resolve();
+
+ $result = $this->parseLinterOutput($err, $stdout, $stderr);
+
+ if ($result === false) {
+ $this->stopAllLinters();
+ return;
+ }
+
+ }
+
+ public function lintPath($path) {
+ // already did all the work in `willLintPaths`
+ return;
+ }
+
+ private function parseLinterOutput($err, $stdout, $stderr) {
+ // `hh_client` uses 0 for success, 1 for failure, 2 for type errors
+ if ($err !== 0 && $err !== 2) {
+ throw new Exception(pht(
+ 'You have enabled the Hack type checker, but it failed to '.
+ 'return normally.'));
+
+ return false;
+ }
+
+ $json = last(explode("\n", trim($stderr)));
+
+ $results = phutil_json_decode($json);
+ $passed = idx($results, 'passed', false);
+ $errors = idx($results, 'errors', array());
+
+ if ($passed) {
+ // everything typechecks!
+ return array();
+ }
+
+ $paths = array();
+ foreach ($this->paths as $current_path) {
+ $paths[$this->getEngine()->getFilePathOnDisk($current_path)] = true;
+ }
+
+ $lint_messages = array();
+ foreach ($errors as $current_error) {
+ // Each Hack error consists of a list of messages; each message
+ // has its own position info. We report the error at the first
+ // location that matches a path affect by the current diff.
+ //
+ // Location keys are:
+ // - descr : string
+ // - path : string
+ // - line : int
+ // - start : int
+ // - end : int
+
+ $message = $current_error['message'];
+ foreach ($message as $location) {
+ $location_path = $location['path'];
+ if (array_key_exists($location_path, $paths)) {
+ $warning = array(
+ 'path' => $location_path,
+ 'line' => $location['line'],
+ 'char' => $location['start'],
+ 'name' => pht('Hack type error'),
+ 'code' => $this->getLinterName(),
+ 'description' => $this->formatDescription($message),
+ 'severity' => ArcanistLintSeverity::SEVERITY_ERROR
+ );
+
+ $this->addLintMessage(
+ ArcanistLintMessage::newFromDictionary($warning));
+ break;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private function formatDescription(array $locations) {
+ $result = array();
+ $result[] = pht('Type error:');
+ foreach ($locations as $location) {
+ $description = pht(
+ "%s line %d:%d-%d\n %s",
+ $location['path'],
+ $location['line'],
+ $location['start'],
+ $location['end'],
+ $location['descr']);
+
+ $result[] = $description;
+ }
+
+ return implode("\n", $result);
+ }
+}
diff --git a/src/lint/linter/__tests__/ArcanistHackLinterTestCase.php b/src/lint/linter/__tests__/ArcanistHackLinterTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/__tests__/ArcanistHackLinterTestCase.php
@@ -0,0 +1,20 @@
+<?php
+
+final class ArcanistHackLinterTestCase extends ArcanistArcanistLinterTestCase {
+
+ public function testHackLinter() {
+ $testdir = dirname(__FILE__).'/hack/';
+ $this->executeTestsInDirectory(
+ $testdir,
+ new ArcanistHackLinter());
+ }
+
+ protected function didCreateTemporaryDirectory($path) {
+ $hhconfig = Filesystem::resolvePath('.hhconfig', $path);
+ Filesystem::writeFile($hhconfig, "\n");
+ }
+
+ protected function getFilenameSuffix() {
+ return '.php';
+ }
+}
diff --git a/src/lint/linter/__tests__/ArcanistLinterTestCase.php b/src/lint/linter/__tests__/ArcanistLinterTestCase.php
--- a/src/lint/linter/__tests__/ArcanistLinterTestCase.php
+++ b/src/lint/linter/__tests__/ArcanistLinterTestCase.php
@@ -22,6 +22,14 @@
pht('Expected to find some .lint-test tests in directory %s!', $root));
}
+ protected function didCreateTemporaryDirectory($path) {
+ return;
+ }
+
+ protected function getFilenameSuffix() {
+ return '';
+ }
+
private function lintFile($file, ArcanistLinter $linter) {
$linter = clone $linter;
@@ -59,11 +67,12 @@
$caught_exception = false;
try {
- $tmp = new TempFile($basename);
+ $tmp = new TempFile($basename.$this->getFilenameSuffix());
Filesystem::writeFile($tmp, $data);
$full_path = (string)$tmp;
$dir = dirname($full_path);
+ $this->didCreateTemporaryDirectory($dir);
$path = basename($full_path);
$config_file = null;
$arcconfig = idx($config, 'arcconfig');
diff --git a/src/lint/linter/__tests__/hack/01_basic_error.lint-test b/src/lint/linter/__tests__/hack/01_basic_error.lint-test
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/__tests__/hack/01_basic_error.lint-test
@@ -0,0 +1,11 @@
+<?hh // strict
+
+class Test {
+
+ public function foo(string $x): int {
+ return "five";
+ }
+
+}
+~~~~~~~~~~
+error:6:12
diff --git a/src/lint/linter/__tests__/hack/02_no_hack_error.lint-test b/src/lint/linter/__tests__/hack/02_no_hack_error.lint-test
new file mode 100644
--- /dev/null
+++ b/src/lint/linter/__tests__/hack/02_no_hack_error.lint-test
@@ -0,0 +1,10 @@
+<?hh // strict
+
+class Test {
+
+ public function foo(string $x): int {
+ return 5;
+ }
+
+}
+~~~~~~~~~~

File Metadata

Mime Type
text/plain
Expires
Fri, Oct 18, 10:43 AM (3 w, 4 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6726459
Default Alt Text
D10105.diff (7 KB)

Event Timeline