mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-08 10:51:15 -03:00
lint: add xtask for running ShellCheck
ShellCheck does not have a built-in way of detecting which files it should check, so we use ripgrep's `ignore` library to find files not ignored by our gitignore rules, and then look for a non-fish shebang in the first line of the file. The resulting shell scripts are then passed to ShellCheck. Part of #12661
This commit is contained in:
committed by
Johannes Altmanninger
parent
63c3306e6c
commit
ca443e2e54
43
Cargo.lock
generated
43
Cargo.lock
generated
@@ -183,6 +183,31 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -522,6 +547,22 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "ignore"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"globset",
|
||||
"log",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"same-file",
|
||||
"walkdir",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
@@ -1195,6 +1236,8 @@ dependencies = [
|
||||
"clap",
|
||||
"fish-build-helper",
|
||||
"fish-tempfile",
|
||||
"ignore",
|
||||
"pcre2",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ fish-wcstringutil = { path = "crates/wcstringutil" }
|
||||
fish-widecharwidth = { path = "crates/widecharwidth" }
|
||||
fish-widestring = { path = "crates/widestring" }
|
||||
fish-wgetopt = { path = "crates/wgetopt" }
|
||||
ignore = "0.4.25"
|
||||
itertools = "0.14.0"
|
||||
libc = "0.2.177"
|
||||
# lru pulls in hashbrown by default, which uses a faster (though less DoS resistant) hashing algo.
|
||||
|
||||
@@ -10,4 +10,6 @@ anstyle.workspace = true
|
||||
clap.workspace = true
|
||||
fish-build-helper.workspace = true
|
||||
fish-tempfile.workspace = true
|
||||
ignore.workspace = true
|
||||
pcre2.workspace = true
|
||||
walkdir.workspace = true
|
||||
|
||||
@@ -14,6 +14,7 @@ macro_rules! fail {
|
||||
}
|
||||
|
||||
pub mod format;
|
||||
pub mod shellcheck;
|
||||
|
||||
pub trait CommandExt {
|
||||
fn run_or_fail(&mut self);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use fish_build_helper::as_os_strs;
|
||||
use std::{path::PathBuf, process::Command};
|
||||
use xtask::{CommandExt as _, cargo, format::FormatArgs};
|
||||
use xtask::{CommandExt as _, cargo, format::FormatArgs, shellcheck::shellcheck};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(
|
||||
@@ -28,6 +28,9 @@ enum Task {
|
||||
},
|
||||
/// Build man pages
|
||||
ManPages,
|
||||
/// run ShellCheck on non-fish shell scripts
|
||||
#[command(name = "shellcheck")]
|
||||
ShellCheck,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -37,6 +40,7 @@ fn main() {
|
||||
Task::Format(format_args) => xtask::format::format(format_args),
|
||||
Task::HtmlDocs { fish_indent } => build_html_docs(fish_indent),
|
||||
Task::ManPages => cargo(["build", "--package", "fish-build-man-pages"]),
|
||||
Task::ShellCheck => shellcheck(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
49
crates/xtask/src/shellcheck.rs
Normal file
49
crates/xtask/src/shellcheck.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use fish_build_helper::workspace_root;
|
||||
use ignore::Walk;
|
||||
use pcre2::bytes::Regex;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
pub fn shellcheck() {
|
||||
let file_paths = files_to_check();
|
||||
match Command::new("shellcheck")
|
||||
.args(file_paths)
|
||||
.current_dir(workspace_root())
|
||||
.status()
|
||||
{
|
||||
Ok(status) => {
|
||||
std::process::exit(status.code().unwrap_or(1));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to run shellcheck: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_shell_script<P: AsRef<Path>>(path: P) -> bool {
|
||||
let file = File::open(&path).unwrap();
|
||||
let mut first_line = String::new();
|
||||
let Ok(_) = BufReader::new(file).read_line(&mut first_line) else {
|
||||
return false;
|
||||
};
|
||||
static SHEBANG_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||
SHEBANG_REGEX
|
||||
.get_or_init(|| Regex::new("^#!.*[^i]sh").unwrap())
|
||||
.is_match(first_line.trim().as_bytes())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn files_to_check() -> Vec<PathBuf> {
|
||||
Walk::new(workspace_root())
|
||||
.map(|path| path.unwrap_or_else(|e| fail!("Error traversing workspace: {e}")))
|
||||
.filter(|path| path.file_type().unwrap().is_file())
|
||||
.map(|path| path.into_path())
|
||||
.filter(|path| is_shell_script(path))
|
||||
.collect()
|
||||
}
|
||||
Reference in New Issue
Block a user