Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

150 changes: 1 addition & 149 deletions crates/squawk/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,13 @@
use anyhow::{Context, Result};
use log::info;
use serde::Deserialize;
use squawk_linter::config::{ConfigFile, UploadToGitHubConfig};
use squawk_linter::{Rule, Version};
use std::{
env,
io::{self, IsTerminal},
path::{Path, PathBuf},
process,
};

use crate::{Command, DebugOption, Opts, Reporter, UploadToGithubArgs};

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

#[derive(Debug, Default, Deserialize)]
pub struct UploadToGitHubConfig {
#[serde(default)]
pub fail_on_violations: Option<bool>,
}

#[derive(Debug, Default, Deserialize)]
pub struct ConfigFile {
#[serde(default)]
pub excluded_paths: Vec<String>,
#[serde(default)]
pub excluded_rules: Vec<Rule>,
#[serde(default)]
pub pg_version: Option<Version>,
#[serde(default)]
pub assume_in_transaction: Option<bool>,
#[serde(default)]
pub upload_to_github: UploadToGitHubConfig,
}

impl ConfigFile {
pub fn parse(custom_path: Option<PathBuf>) -> Result<Option<Self>> {
let path = if let Some(path) = custom_path {
Some(path)
} else {
find_by_traversing_back()?
};

if let Some(p) = path {
info!("using config file path: {}", p.display());

let file_content = std::fs::read_to_string(p)?;
return Ok(Some(toml::from_str(&file_content)?));
}

info!("no config file found");
Ok(None)
}
}

pub struct Config {
pub excluded_paths: Vec<String>,
pub excluded_rules: Vec<Rule>,
Expand Down Expand Up @@ -153,106 +108,3 @@ impl Config {
}
}
}

fn recurse_directory(directory: &Path, file_name: &str) -> Result<Option<PathBuf>, std::io::Error> {
for entry in directory.read_dir()? {
let entry = entry?;
if entry.file_name() == file_name {
return Ok(Some(entry.path()));
}
}
if let Some(parent) = directory.parent() {
recurse_directory(parent, file_name)
} else {
Ok(None)
}
}

fn find_by_traversing_back() -> Result<Option<PathBuf>> {
recurse_directory(&env::current_dir()?, FILE_NAME)
.context("Error when finding configuration file")
}

#[cfg(test)]
mod test_config {
use std::fs;
use tempfile::NamedTempFile;

use insta::assert_debug_snapshot;

use super::*;

#[test]
fn load_cfg_full() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r#"
pg_version = "19.1"
excluded_paths = ["example.sql"]
excluded_rules = ["require-concurrent-index-creation"]
assume_in_transaction = true

"#;
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_pg_version() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r#"
pg_version = "19.1"

"#;
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_excluded_rules() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r#"
excluded_rules = ["require-concurrent-index-creation"]

"#;
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_excluded_paths() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r#"
excluded_paths = ["example.sql"]

"#;
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_assume_in_transaction() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r"
assume_in_transaction = false

";
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_fail_on_violations() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r"
[upload_to_github]
fail_on_violations = true
";
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_excluded_rules_with_alias() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r#"
excluded_rules = ["prefer-timestamp-tz", "prefer-timestamptz"]

"#;
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
}
6 changes: 6 additions & 0 deletions crates/squawk_linter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ line-index.workspace = true
serde_plain.workspace = true
annotate-snippets.workspace = true
rustc-hash.workspace = true
toml.workspace = true
anyhow.workspace = true
log.workspace = true

[dev-dependencies]
tempfile.workspace = true

[lints]
workspace = true
163 changes: 163 additions & 0 deletions crates/squawk_linter/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use anyhow::{Context, Result};
use log::info;
use serde::Deserialize;
use std::{
env,
path::{Path, PathBuf},
};

use crate::{Rule, Version};

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

#[derive(Debug, Default, Deserialize)]
pub struct UploadToGitHubConfig {
#[serde(default)]
pub fail_on_violations: Option<bool>,
}

#[derive(Debug, Default, Deserialize)]
pub struct ConfigFile {
#[serde(default)]
pub excluded_paths: Vec<String>,
#[serde(default)]
pub excluded_rules: Vec<Rule>,
#[serde(default)]
pub pg_version: Option<Version>,
#[serde(default)]
pub assume_in_transaction: Option<bool>,
#[serde(default)]
pub upload_to_github: UploadToGitHubConfig,
}

impl ConfigFile {
pub fn parse(custom_path: Option<PathBuf>) -> Result<Option<Self>> {
let path = if let Some(path) = custom_path {
Some(path)
} else {
find_by_traversing_back()?
};
Self::load(path)
}

/// Search for `.squawk.toml` starting from the given root directory,
/// traversing up to parent directories.
pub fn find_and_parse(root: &Path) -> Result<Option<Self>> {
let path =
recurse_directory(root, FILE_NAME).context("Error when finding configuration file")?;
Self::load(path)
}

fn load(path: Option<PathBuf>) -> Result<Option<Self>> {
if let Some(p) = path {
info!("using config file path: {}", p.display());
let file_content = std::fs::read_to_string(p)?;
return Ok(Some(toml::from_str(&file_content)?));
}
info!("no config file found");
Ok(None)
}
}

fn recurse_directory(directory: &Path, file_name: &str) -> Result<Option<PathBuf>, std::io::Error> {
for entry in directory.read_dir()? {
let entry = entry?;
if entry.file_name() == file_name {
return Ok(Some(entry.path()));
}
}
if let Some(parent) = directory.parent() {
recurse_directory(parent, file_name)
} else {
Ok(None)
}
}

fn find_by_traversing_back() -> Result<Option<PathBuf>> {
recurse_directory(&env::current_dir()?, FILE_NAME)
.context("Error when finding configuration file")
}

#[cfg(test)]
mod test_config {
use std::fs;
use tempfile::NamedTempFile;

use insta::assert_debug_snapshot;

use super::*;

#[test]
fn load_cfg_full() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r#"
pg_version = "19.1"
excluded_paths = ["example.sql"]
excluded_rules = ["require-concurrent-index-creation"]
assume_in_transaction = true

"#;
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_pg_version() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r#"
pg_version = "19.1"

"#;
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_excluded_rules() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r#"
excluded_rules = ["require-concurrent-index-creation"]

"#;
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_excluded_paths() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r#"
excluded_paths = ["example.sql"]

"#;
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_assume_in_transaction() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r"
assume_in_transaction = false

";
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_fail_on_violations() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r"
[upload_to_github]
fail_on_violations = true
";
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
#[test]
fn load_excluded_rules_with_alias() {
let squawk_toml = NamedTempFile::new().expect("generate tempFile");
let file = r#"
excluded_rules = ["prefer-timestamp-tz", "prefer-timestamptz"]

"#;
fs::write(&squawk_toml, file).expect("Unable to write file");
assert_debug_snapshot!(ConfigFile::parse(Some(squawk_toml.path().to_path_buf())));
}
}
1 change: 1 addition & 0 deletions crates/squawk_linter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use squawk_syntax::{Parse, SourceFile};
pub use version::Version;

pub mod analyze;
pub mod config;
pub mod ignore;
mod ignore_index;
mod version;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/squawk/src/config.rs
source: crates/squawk_linter/src/config.rs
assertion_line: 148
expression: "ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))"
---
Ok(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
source: crates/squawk/src/config.rs
source: crates/squawk_linter/src/config.rs
assertion_line: 108
expression: "ConfigFile::parse(Some(squawk_toml.path().to_path_buf()))"
---
Ok(
Expand Down
Loading
Loading