From 57a316545efd54cb590d41b24e644fc6e6e7736a Mon Sep 17 00:00:00 2001 From: Febrian Yuwono Date: Thu, 16 Nov 2023 23:38:19 +0700 Subject: [PATCH] Add SQL Linter using Squawk (#94) --- README.md | 13 +++ __phutil_library_map__.php | 2 + src/SquawkLinter.php | 157 +++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 src/SquawkLinter.php diff --git a/README.md b/README.md index 9abd427..56dcabf 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Pinterest. - [Python isort](#python-isort) - [Python Requirements](#python-requirements) - [Spectral](#spectral) +- [Squawk](#squawk) - [ThriftCheck](#thriftcheck) - [YamlLinter](#yamllinter) @@ -310,6 +311,18 @@ Lints OpenAPI documents using [Spectral](https://stoplight.io/spectral). } ``` +### Squawk + +Lints SQL files using [Squawk](https://squawkhq.com/). + +```json +{ + "type": "squawk", + "include": "(\\.sql)", + "squawk.config": ".squawk.toml" +} +``` + ### ThriftCheck Lints Thrift IDL files using [ThriftCheck](https://github.com/pinterest/thriftcheck). diff --git a/__phutil_library_map__.php b/__phutil_library_map__.php index 2acd297..6d5e8ee 100644 --- a/__phutil_library_map__.php +++ b/__phutil_library_map__.php @@ -31,6 +31,7 @@ 'PythonIsortLinter' => 'src/PythonIsortLinter.php', 'PythonRequirementsLinter' => 'src/PythonRequirementsLinter.php', 'SpectralLinter' => 'src/SpectralLinter.php', + 'SquawkLinter' => 'src/SquawkLinter.php', 'ThriftCheckLinter' => 'src/ThriftCheckLinter.php', 'YamlLinter' => 'src/YamlLinter.php', ), @@ -58,6 +59,7 @@ 'PythonIsortLinter' => 'PythonExternalLinter', 'PythonRequirementsLinter' => 'ArcanistLinter', 'SpectralLinter' => 'NodeExternalLinter', + 'SquawkLinter' => 'NodeExternalLinter', 'ThriftCheckLinter' => 'PinterestExternalLinter', 'YamlLinter' => 'ArcanistLinter', ), diff --git a/src/SquawkLinter.php b/src/SquawkLinter.php new file mode 100644 index 0000000..bba88c6 --- /dev/null +++ b/src/SquawkLinter.php @@ -0,0 +1,157 @@ +getExecutableCommand()); + return trim($stdout); + } + + public function getLinterConfigurationOptions() { + $options = array( + 'squawk.config' => array( + 'type' => 'string', + 'help' => pht('Configuration file to use'), + ), + ); + return $options + parent::getLinterConfigurationOptions(); + } + + public function setLinterConfigurationValue($key, $value) { + switch ($key) { + case 'squawk.config': + $this->config = $value; + return; + } + return parent::setLinterConfigurationValue($key, $value); + } + + public function getNodeBinary() { + return 'squawk'; + } + + public function getNpmPackageName() { + return 'squawk-cli'; + } + + protected function getMandatoryFlags() { + return array('-c', $this->config); + } + + protected function getDefaultFlags() { + return array('--reporter', 'Json'); + } + + public function shouldExpectCommandErrors() { + return true; + } + + public function getLintSeverityMap() { + return array( + 'Error' => ArcanistLintSeverity::SEVERITY_ERROR, + 'Warning' => ArcanistLintSeverity::SEVERITY_WARNING + ); + } + + protected function parseLinterOutput($path, $err, $stdout, $stderr) { + /* + [ + { + "file": "path/to.sql", + "line": 0, + "column": 0, + "level": "Error", + "messages": [ + { + "Note": "Postgres failed to parse query: syntax error at or near \"some_random_stuffs\"" + }, + { + "Help": "Modify your Postgres statement to use valid syntax." + } + ], + "rule_name": "invalid-statement" + }, + { + "file": "path/to_second.sql", + "line": 1, + "column": 0, + "level": "Warning", + "messages": [ + { + "Note": "Dropping a column may break existing clients." + } + ], + "rule_name": "ban-drop-column" + } + ] + */ + $json = json_decode($stdout, true); + + $messages = array(); + foreach ($json as $item) { + $level = $item['level']; + $rulename = $item['rule_name']; + $description = "$level ($rulename): "; + + foreach($item['messages'] as $message) { + $value = ''; + + if (array_key_exists('Note', $message)) { + $value = $message['Note']; + } + + if (array_key_exists('Help', $message)) { + $value = $message['Help']; + } + + $description.="\n$value"; + } + + $line = $item['line']; + $column = $item['column']; + + // $line=0 means the file has broken syntax + // otherwise sum the $line and $ column to get the first row of error + if ($line == '0') { + $line = 1; + } else if ($column != '0') { + $line = $line + $column - 1; + } + + $messages[] = id(new ArcanistLintMessage()) + ->setName($this->getLinterName()) + ->setCode($rulename) + ->setPath(idx($item, 'file', $path)) + ->setLine($line) + ->setChar(1) + ->setDescription($description) + ->setSeverity($this->getLintMessageSeverity($level)); + } + + return $messages; + } +}