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 @@ -85,6 +85,8 @@ 'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php', 'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php', 'ArcanistGitHookPreReceiveWorkflow' => 'workflow/ArcanistGitHookPreReceiveWorkflow.php', + 'ArcanistGoFmtLinter' => 'lint/linter/ArcanistGoFmtLinter.php', + 'ArcanistGoFmtLinterTestCase' => 'lint/linter/__tests__/ArcanistGoFmtLinterTestCase.php', 'ArcanistGoLintLinter' => 'lint/linter/ArcanistGoLintLinter.php', 'ArcanistGoLintLinterTestCase' => 'lint/linter/__tests__/ArcanistGoLintLinterTestCase.php', 'ArcanistGoTestResultParser' => 'unit/parser/ArcanistGoTestResultParser.php', @@ -285,6 +287,8 @@ 'ArcanistGetConfigWorkflow' => 'ArcanistWorkflow', 'ArcanistGitAPI' => 'ArcanistRepositoryAPI', 'ArcanistGitHookPreReceiveWorkflow' => 'ArcanistWorkflow', + 'ArcanistGoFmtLinter' => 'ArcanistLinter', + 'ArcanistGoFmtLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistGoLintLinter' => 'ArcanistExternalLinter', 'ArcanistGoLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistGoTestResultParser' => 'ArcanistTestResultParser', diff --git a/src/lint/linter/ArcanistGoFmtLinter.php b/src/lint/linter/ArcanistGoFmtLinter.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/ArcanistGoFmtLinter.php @@ -0,0 +1,106 @@ +getBinary(); + if (!Filesystem::binaryExists($binary)) { + throw new ArcanistMissingLinterException( + sprintf( + "%s\n%s", + pht( + 'Unable to locate binary "%s" to run linter %s. You may need '. + 'to install the binary, or adjust your linter configuration.', + $binary, + get_class($this)), + pht( + 'TO INSTALL: %s', + $this->getInstallInstructions()))); + } + } + + public function getInstallInstructions() { + return pht( + 'Gofmt comes with Go, please '. + 'follow https://golang.org/doc/install to install go'); + } + + public function getLintNameMap() { + return array( + self::LINT_GO_UNFORMATTED => "File is not gofmt'd", + ); + } + + // Gofmt must run before any other linter for Go. + public function getLinterPriority() { + return 10; + } + + public function lintPath($path) { + $this->checkBinaryConfiguration(); + + $data = $this->getData($path); + $future = new ExecFuture('%C', $this->getBinary()); + $future->write($data); + list($err, $stdout, $stderr) = $future->resolve(); + if (empty($stdout) && $err) { + throw new Exception( + sprintf( + "%s\n\nSTDOUT\n%s\n\nSTDERR\n%s", + pht('Linter failed to parse output!'), + $stdout, + $stderr)); + } + + if ($stdout !== $data) { + $lines = explode("\n", $data); + $formatted_lines = explode("\n", $stdout); + foreach ($lines as $line_idx => $line) { + if ($line != $formatted_lines[$line_idx]) { + $lines = array_slice($lines, $line_idx); + $formatted_lines = array_slice($formatted_lines, $line_idx); + break; + } + } + + $this->raiseLintAtLine( + $line_idx + 1, + 1, + self::LINT_GO_UNFORMATTED, + pht( + '%s was not formatted correctly. Please setup your '. + 'editor to run gofmt on save', $path), + implode("\n", $lines), + implode("\n", $formatted_lines)); + } + } + +} diff --git a/src/lint/linter/__tests__/ArcanistGoFmtLinterTestCase.php b/src/lint/linter/__tests__/ArcanistGoFmtLinterTestCase.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/ArcanistGoFmtLinterTestCase.php @@ -0,0 +1,7 @@ +executeTestsInDirectory(dirname(__FILE__).'/gofmt/'); + } +} diff --git a/src/lint/linter/__tests__/gofmt/big-program-with-many-issues.lint-test b/src/lint/linter/__tests__/gofmt/big-program-with-many-issues.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/gofmt/big-program-with-many-issues.lint-test @@ -0,0 +1,32 @@ +package main + +import ("fmt") +type Animal struct{Type string + Color string} + +func main() {a := &Animal{Type: "Dog", Color: "Gold",} + + + + + +fmt.Printf("%#v", *a)} +~~~~~~~~~~ +error:3:1 +~~~~~~~~~~ +package main + +import ( + "fmt" +) + +type Animal struct { + Type string + Color string +} + +func main() { + a := &Animal{Type: "Dog", Color: "Gold"} + + fmt.Printf("%#v", *a) +} diff --git a/src/lint/linter/__tests__/gofmt/extra-new-line.lint-test b/src/lint/linter/__tests__/gofmt/extra-new-line.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/gofmt/extra-new-line.lint-test @@ -0,0 +1,18 @@ +package main + +import "fmt" + + +func main() { + fmt.Println("%d", 1) +} +~~~~~~~~~~ +error:5:1 +~~~~~~~~~~ +package main + +import "fmt" + +func main() { + fmt.Println("%d", 1) +} diff --git a/src/lint/linter/__tests__/gofmt/space-instead-of-tab.lint-test b/src/lint/linter/__tests__/gofmt/space-instead-of-tab.lint-test new file mode 100644 --- /dev/null +++ b/src/lint/linter/__tests__/gofmt/space-instead-of-tab.lint-test @@ -0,0 +1,17 @@ +package main + +import "fmt" + +func main() { + fmt.Println("%d", 1) +} +~~~~~~~~~~ +error:6:1 +~~~~~~~~~~ +package main + +import "fmt" + +func main() { + fmt.Println("%d", 1) +}