Skip to content

Commit 2a6ad21

Browse files
authored
v2: swap linters (#440)
This updates the CLI to use the new parser & linter. No more pg query.
1 parent c215895 commit 2a6ad21

29 files changed

+966
-1144
lines changed

Cargo.lock

Lines changed: 10 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ needless_return = "allow"
6363
doc_markdown = "deny"
6464
manual_let_else = "deny"
6565
explicit_iter_loop = "deny"
66+
too_many_arguments = "allow"
6667

6768
[profile.dev]
6869
debug = 0

README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,6 @@ FLAGS:
7474
-h, --help
7575
Prints help information
7676
77-
--list-rules
78-
List all available rules
79-
8077
-V, --version
8178
Prints version information
8279
@@ -88,8 +85,8 @@ OPTIONS:
8885
-c, --config <config-path>
8986
Path to the squawk config file (.squawk.toml)
9087
91-
--dump-ast <ast-format>
92-
Output AST in JSON [possible values: Raw, Parsed, Debug]
88+
--debug <format>
89+
Output debug info [possible values: Lex, Parse]
9390
9491
--exclude-path <excluded-path>...
9592
Paths to exclude
@@ -102,8 +99,6 @@ OPTIONS:
10299
Exclude specific warnings
103100
104101
For example: --exclude=require-concurrent-index-creation,ban-drop-database
105-
--explain <rule>
106-
Provide documentation on the given rule
107102
108103
--pg-version <pg-version>
109104
Specify postgres version

crates/cli/Cargo.toml

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
[package]
22
name = "squawk"
33
version = "1.6.1"
4-
authors = ["Steve Dignam <[email protected]>"]
5-
edition = "2018"
6-
license = "GPL-3.0"
4+
default-run = "squawk"
5+
6+
authors.workspace = true
7+
edition.workspace = true
8+
license.workspace = true
79
description = "Linter for Postgresql focused on database migrations."
810
repository = "https://github.com/sbdchd/squawk"
911
documentation = "https://github.com/sbdchd/squawk/blob/master/README.md"
@@ -23,11 +25,19 @@ atty.workspace = true
2325
base64.workspace = true
2426
simplelog.workspace = true
2527
log.workspace = true
26-
squawk-parser.workspace = true
27-
squawk-linter.workspace = true
28+
enum-iterator.workspace = true
29+
squawk_syntax.workspace = true
30+
squawk_linter.workspace = true
31+
squawk_lexer.workspace = true
2832
squawk-github.workspace = true
2933
toml.workspace = true
3034
glob.workspace = true
35+
anyhow.workspace = true
36+
annotate-snippets.workspace = true
37+
line-index.workspace = true
3138

3239
[dev-dependencies]
3340
insta.workspace = true
41+
42+
[lints]
43+
workspace = true

crates/cli/src/config.rs

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,11 @@
1+
use anyhow::{Context, Result};
12
use log::info;
23
use serde::Deserialize;
3-
use squawk_linter::{versions::Version, violations::RuleViolationKind};
4-
use std::{env, io, path::Path, path::PathBuf};
4+
use squawk_linter::{Rule, Version};
5+
use std::{env, path::Path, path::PathBuf};
56

67
const FILE_NAME: &str = ".squawk.toml";
78

8-
#[derive(Debug)]
9-
pub enum ConfigError {
10-
LookupError(io::Error),
11-
ReadError(io::Error),
12-
ParseError(toml::de::Error),
13-
}
14-
15-
impl std::convert::From<io::Error> for ConfigError {
16-
fn from(e: io::Error) -> Self {
17-
Self::ReadError(e)
18-
}
19-
}
20-
21-
impl std::convert::From<toml::de::Error> for ConfigError {
22-
fn from(e: toml::de::Error) -> Self {
23-
Self::ParseError(e)
24-
}
25-
}
26-
27-
impl std::fmt::Display for ConfigError {
28-
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
29-
match *self {
30-
Self::LookupError(ref err) => {
31-
write!(f, "Error when finding configuration file: {err}")
32-
}
33-
Self::ReadError(ref err) => write!(f, "Failed to read configuration file: {err}"),
34-
Self::ParseError(ref err) => write!(f, "Failed to parse configuration file: {err}"),
35-
}
36-
}
37-
}
38-
399
#[derive(Debug, Default, Deserialize)]
4010
pub struct UploadToGitHubConfig {
4111
#[serde(default)]
@@ -47,7 +17,7 @@ pub struct Config {
4717
#[serde(default)]
4818
pub excluded_paths: Vec<String>,
4919
#[serde(default)]
50-
pub excluded_rules: Vec<RuleViolationKind>,
20+
pub excluded_rules: Vec<Rule>,
5121
#[serde(default)]
5222
pub pg_version: Option<Version>,
5323
#[serde(default)]
@@ -57,7 +27,7 @@ pub struct Config {
5727
}
5828

5929
impl Config {
60-
pub fn parse(custom_path: Option<PathBuf>) -> Result<Option<Self>, ConfigError> {
30+
pub fn parse(custom_path: Option<PathBuf>) -> Result<Option<Self>> {
6131
let path = if let Some(path) = custom_path {
6232
Some(path)
6333
} else {
@@ -90,8 +60,9 @@ fn recurse_directory(directory: &Path, file_name: &str) -> Result<Option<PathBuf
9060
}
9161
}
9262

93-
fn find_by_traversing_back() -> Result<Option<PathBuf>, ConfigError> {
94-
recurse_directory(&env::current_dir()?, FILE_NAME).map_err(ConfigError::LookupError)
63+
fn find_by_traversing_back() -> Result<Option<PathBuf>> {
64+
recurse_directory(&env::current_dir()?, FILE_NAME)
65+
.context("Error when finding configuration file")
9566
}
9667

9768
#[cfg(test)]

crates/cli/src/debug.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use std::{io, path::PathBuf};
2+
3+
use annotate_snippets::{Level, Message, Renderer, Snippet};
4+
use anyhow::Result;
5+
use squawk_syntax::syntax_error::SyntaxError;
6+
7+
use crate::{
8+
file::{sql_from_path, sql_from_stdin},
9+
DebugOption,
10+
};
11+
12+
pub(crate) fn debug<W: io::Write>(
13+
f: &mut W,
14+
paths: &[PathBuf],
15+
read_stdin: bool,
16+
dump_ast: &DebugOption,
17+
verbose: bool,
18+
) -> Result<()> {
19+
let process_dump_ast = |sql: &str, filename: &str, f: &mut W| -> Result<()> {
20+
match dump_ast {
21+
DebugOption::Lex => {
22+
let tokens = squawk_lexer::tokenize(sql);
23+
let mut start = 0;
24+
for token in tokens {
25+
if verbose {
26+
let content = &sql[start as usize..(start + token.len) as usize];
27+
start += token.len;
28+
writeln!(f, "{:?} @ {:?}", content, token.kind)?;
29+
} else {
30+
writeln!(f, "{:?}", token)?;
31+
}
32+
}
33+
}
34+
DebugOption::Parse => {
35+
let parse = squawk_syntax::SourceFile::parse(sql);
36+
if verbose {
37+
writeln!(f, "{}\n---", parse.syntax_node())?;
38+
}
39+
writeln!(f, "{:#?}", parse.syntax_node())?;
40+
let errors = parse.errors();
41+
if !errors.is_empty() {
42+
let mut snap = "---".to_string();
43+
for syntax_error in &errors {
44+
let range = syntax_error.range();
45+
let text = syntax_error.message();
46+
// split into there own lines so that we can just grep
47+
// for error without hitting this part
48+
snap += "\n";
49+
snap += "ERROR";
50+
if range.start() == range.end() {
51+
snap += &format!("@{:?} {:?}", range.start(), text);
52+
} else {
53+
snap += &format!("@{:?}:{:?} {:?}", range.start(), range.end(), text);
54+
}
55+
}
56+
writeln!(f, "{}", snap)?;
57+
let renderer = Renderer::styled();
58+
render_syntax_errors(&errors, filename, sql, |message| {
59+
writeln!(f, "{}", renderer.render(message))?;
60+
Ok(())
61+
})?;
62+
}
63+
}
64+
}
65+
Ok(())
66+
};
67+
if read_stdin {
68+
let sql = sql_from_stdin()?;
69+
process_dump_ast(&sql, "stdin", f)?;
70+
return Ok(());
71+
}
72+
73+
for path in paths {
74+
let sql = sql_from_path(path)?;
75+
process_dump_ast(&sql, &path.to_string_lossy(), f)?;
76+
}
77+
Ok(())
78+
}
79+
80+
fn render_syntax_errors(
81+
errors: &[SyntaxError],
82+
filename: &str,
83+
sql: &str,
84+
mut render: impl FnMut(Message<'_>) -> Result<()>,
85+
) -> Result<()> {
86+
for err in errors {
87+
let text = err.message();
88+
let span = err.range().into();
89+
let message = Level::Warning.title(text).id("syntax-error").snippet(
90+
Snippet::source(sql)
91+
.origin(filename)
92+
.fold(true)
93+
.annotation(Level::Error.span(span)),
94+
);
95+
render(message)?;
96+
}
97+
Ok(())
98+
}

crates/cli/src/file.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use std::{
2+
fs::File,
3+
io::{self, Read},
4+
path::PathBuf,
5+
};
6+
7+
use anyhow::Result;
8+
9+
pub(crate) fn sql_from_stdin() -> Result<String> {
10+
let mut buffer = String::new();
11+
let stdin = io::stdin();
12+
let mut handle = stdin.lock();
13+
handle.read_to_string(&mut buffer)?;
14+
Ok(buffer)
15+
}
16+
17+
pub(crate) fn sql_from_path(path: &PathBuf) -> Result<String> {
18+
let mut file = File::open(path)?;
19+
let mut contents = String::new();
20+
file.read_to_string(&mut contents)?;
21+
Ok(contents)
22+
}

crates/cli/src/file_finding.rs

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,10 @@
1+
use anyhow::Result;
12
use std::path::PathBuf;
23

34
use log::info;
45

5-
#[derive(Debug)]
6-
pub enum FindFilesError {
7-
PatternError(glob::PatternError),
8-
GlobError(glob::GlobError),
9-
}
10-
11-
impl std::fmt::Display for FindFilesError {
12-
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
13-
match *self {
14-
Self::PatternError(ref err) => {
15-
write!(f, "Failed to build pattern: {err}")
16-
}
17-
Self::GlobError(ref err) => {
18-
write!(f, "Failed to read file: {err}")
19-
}
20-
}
21-
}
22-
}
23-
24-
impl std::convert::From<glob::PatternError> for FindFilesError {
25-
fn from(e: glob::PatternError) -> Self {
26-
Self::PatternError(e)
27-
}
28-
}
29-
impl std::convert::From<glob::GlobError> for FindFilesError {
30-
fn from(e: glob::GlobError) -> Self {
31-
Self::GlobError(e)
32-
}
33-
}
34-
356
/// Given a list of patterns or paths, along with exclusion patterns, find matching files.
36-
pub fn find_paths(
37-
path_patterns: &[String],
38-
exclude_patterns: &[String],
39-
) -> Result<Vec<PathBuf>, FindFilesError> {
7+
pub fn find_paths(path_patterns: &[String], exclude_patterns: &[String]) -> Result<Vec<PathBuf>> {
408
let mut matched_paths = vec![];
419
let exclude_paths: Vec<_> = exclude_patterns
4210
.iter()

0 commit comments

Comments
 (0)