Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13970954
D10105.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
7 KB
Referenced Files
None
Subscribers
None
D10105.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D10105: [lint] add a basic hh_client-parsing linter
Attached
Detach File
Event Timeline
Log In to Comment