Skip to content

Commit

Permalink
Add SQL Linter using Squawk (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
febrianyuwono authored Nov 16, 2023
1 parent 1e90ffd commit 57a3165
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Pinterest.
- [Python isort](#python-isort)
- [Python Requirements](#python-requirements)
- [Spectral](#spectral)
- [Squawk](#squawk)
- [ThriftCheck](#thriftcheck)
- [YamlLinter](#yamllinter)

Expand Down Expand Up @@ -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).
Expand Down
2 changes: 2 additions & 0 deletions __phutil_library_map__.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
),
Expand Down Expand Up @@ -58,6 +59,7 @@
'PythonIsortLinter' => 'PythonExternalLinter',
'PythonRequirementsLinter' => 'ArcanistLinter',
'SpectralLinter' => 'NodeExternalLinter',
'SquawkLinter' => 'NodeExternalLinter',
'ThriftCheckLinter' => 'PinterestExternalLinter',
'YamlLinter' => 'ArcanistLinter',
),
Expand Down
157 changes: 157 additions & 0 deletions src/SquawkLinter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
<?php
/**
* Lints SQL files using Squawk
*/
final class SquawkLinter extends NodeExternalLinter {

private $config = null;

public function getInfoName() {
return 'Squawk';
}

public function getInfoURI() {
return 'https://squawkhq.com/';
}

public function getInfoDescription() {
return 'Lint SQL using Squawk';
}

public function getLinterName() {
return 'SQUAWK';
}

public function getLinterConfigurationName() {
return 'squawk';
}

public function getVersion() {
list($err, $stdout, $stderr) = exec_manual('%C --version', $this->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;
}
}

0 comments on commit 57a3165

Please sign in to comment.