Skip to content

Commit 21ac08a

Browse files
committed
feat: add CSpell linter integration
- Add packages/linting/src/linters/cspell.rs following markdown.rs pattern - Integrate CSpell command into CLI with 'spell' alias - Update copilot instructions to include CSpell in linter list - Add comprehensive spelling guide in docs/contributing/spelling.md - Configure cspell.json to ignore data/ and build/ folders - Add project-specific terms (torrust, clippy, reqwest) to dictionary - Auto-install CSpell via npm if not present - Include helpful error messages and fix instructions
1 parent c7c7544 commit 21ac08a

File tree

7 files changed

+311
-3
lines changed

7 files changed

+311
-3
lines changed

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ These principles should guide all development decisions, code reviews, and featu
7878
## 🧪 Build & Test
7979

8080
- **Lint**: `cargo run --bin linter all` (comprehensive - tests stable & nightly toolchains)
81-
- Individual linters: `cargo run --bin linter {markdown|yaml|toml|clippy|rustfmt|shellcheck}`
81+
- Individual linters: `cargo run --bin linter {markdown|yaml|toml|cspell|clippy|rustfmt|shellcheck}`
8282
- Alternative: `./scripts/lint.sh` (wrapper that calls the Rust binary)
8383
- **Dependencies**: `cargo machete` (mandatory before commits - no unused dependencies)
8484
- **Build**: `cargo build`

cspell.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
],
1414
"ignorePaths": [
1515
"target",
16+
"data",
17+
"build",
1618
"/project-words.txt"
1719
]
1820
}

docs/contributing/spelling.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Spelling Guide
2+
3+
This document explains how we handle spelling in the Torrust Tracker Deploy project using CSpell.
4+
5+
## 🎯 Overview
6+
7+
We use [CSpell](https://cspell.org/) for spell checking across all project files including documentation, comments, and code identifiers. This helps maintain consistency and professionalism in our codebase.
8+
9+
## 📋 Configuration
10+
11+
### CSpell Configuration
12+
13+
Our spell checking is configured through `cspell.json` in the project root:
14+
15+
```json
16+
{
17+
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
18+
"version": "0.2",
19+
"dictionaryDefinitions": [
20+
{
21+
"name": "project-words",
22+
"path": "./project-words.txt",
23+
"addWords": true
24+
}
25+
],
26+
"dictionaries": ["project-words"],
27+
"ignorePaths": ["target", "/project-words.txt"]
28+
}
29+
```
30+
31+
### Project Dictionary
32+
33+
The `project-words.txt` file contains:
34+
35+
- Technical terms specific to our domain
36+
- Proper nouns (company names, product names)
37+
- Acronyms and abbreviations
38+
- Valid identifiers that aren't in standard dictionaries
39+
40+
## 🚀 Running Spell Checks
41+
42+
### Via Linting System
43+
44+
```bash
45+
# Run CSpell individually
46+
cargo run --bin linter cspell
47+
48+
# Run all linters including CSpell
49+
cargo run --bin linter all
50+
```
51+
52+
### Direct CSpell Commands
53+
54+
```bash
55+
# Check all files
56+
cspell .
57+
58+
# Check with suggestions and context
59+
cspell . --no-progress --show-suggestions --show-context
60+
61+
# Check specific files
62+
cspell "src/**/*.rs" "docs/**/*.md"
63+
64+
# Get list of unknown words
65+
cspell --words-only --unique .
66+
```
67+
68+
## 🔧 Fixing Spelling Issues
69+
70+
### 1. Fix Actual Misspellings
71+
72+
For genuine spelling errors, fix them directly in the source files.
73+
74+
### 2. Add Valid Terms to Dictionary
75+
76+
For legitimate technical terms, proper nouns, or domain-specific vocabulary:
77+
78+
```bash
79+
# Add words to project-words.txt (one per line, sorted alphabetically)
80+
echo "kubernetes" >> project-words.txt
81+
echo "dockerfile" >> project-words.txt
82+
sort -u project-words.txt -o project-words.txt
83+
```
84+
85+
### 3. Handle Special Cases
86+
87+
#### Code Identifiers
88+
89+
Valid variable names, function names, and identifiers should be added to the dictionary:
90+
91+
```text
92+
# Examples in project-words.txt
93+
ansible
94+
containerd
95+
rustfmt
96+
shellcheck
97+
torrust
98+
```
99+
100+
#### Tokens and Keys
101+
102+
For security tokens, API keys, and similar strings, there are several approaches:
103+
104+
1. **Add to Dictionary (Recommended)**:
105+
106+
```text
107+
# In project-words.txt - for test/fixture tokens only
108+
AAAAB
109+
EAAAADAQABAAABAQC
110+
```
111+
112+
2. **Use CSpell Ignore Comments**:
113+
114+
```rust
115+
// cspell:disable-next-line
116+
const API_TOKEN: &str = "abc123def456ghi789";
117+
118+
/* cspell:disable */
119+
const COMPLEX_TOKEN: &str = "very-long-generated-token-here";
120+
/* cspell:enable */
121+
```
122+
123+
3. **Configuration Patterns**:
124+
125+
```json
126+
{
127+
"ignoreRegExpList": ["/auth_token: .*/g", "/api[_-]key: .*/gi"]
128+
}
129+
```
130+
131+
## 📝 Best Practices
132+
133+
### Do Add to Dictionary
134+
135+
- ✅ Technical terms: `kubernetes`, `dockerfile`, `ansible`
136+
- ✅ Project names: `torrust`, `opentofu`
137+
- ✅ Tool names: `rustfmt`, `shellcheck`, `yamllint`
138+
- ✅ Domain concepts: `provisioning`, `infra`
139+
- ✅ Test fixture data (non-sensitive)
140+
141+
### Don't Add to Dictionary
142+
143+
- ❌ Actual misspellings
144+
- ❌ Typos in variable names
145+
- ❌ Real secrets or production tokens
146+
- ❌ Random strings that could mask real errors
147+
148+
### Guidelines for Adding Words
149+
150+
1. **Be Conservative**: Only add words you're confident are correct
151+
2. **Use Lowercase**: Add words in lowercase unless they're proper nouns
152+
3. **Check Alternatives**: Consider if there's a standard spelling first
153+
4. **Document Context**: Add comments in `project-words.txt` for unusual terms
154+
155+
Example `project-words.txt` structure:
156+
157+
```text
158+
# Infrastructure and DevOps
159+
ansible
160+
containerd
161+
dockerfile
162+
kubernetes
163+
multipass
164+
opentofu
165+
166+
# Project-specific terms
167+
torrust
168+
rustfmt
169+
shellcheck
170+
171+
# Test fixtures (non-sensitive)
172+
testkey
173+
mocksecret
174+
```
175+
176+
## 🔍 Troubleshooting
177+
178+
### Common Issues
179+
180+
1. **Too Many False Positives**
181+
182+
- Review and clean up the project dictionary
183+
- Use more specific ignore patterns
184+
- Consider CSpell ignore comments for edge cases
185+
186+
2. **Missing Technical Terms**
187+
188+
- Add them to `project-words.txt`
189+
- Keep them lowercase unless proper nouns
190+
- Sort alphabetically for maintainability
191+
192+
3. **Generated or Binary Content**
193+
- Add paths to `ignorePaths` in `cspell.json`
194+
- Use glob patterns to exclude file types
195+
196+
### Getting Help
197+
198+
- Check CSpell suggestions: `cspell --show-suggestions <file>`
199+
- View CSpell documentation: [https://cspell.org/docs/](https://cspell.org/docs/)
200+
- Review existing patterns in `cspell.json`
201+
202+
## 🧪 Integration with Development Workflow
203+
204+
### Pre-commit Checks
205+
206+
CSpell is included in the mandatory pre-commit checks:
207+
208+
```bash
209+
cargo run --bin linter all # Includes CSpell
210+
```
211+
212+
### CI/CD Integration
213+
214+
The spell checker runs automatically in CI/CD pipelines as part of the linting process.
215+
216+
### IDE Integration
217+
218+
Consider installing CSpell extensions for your IDE:
219+
220+
- VS Code: "Code Spell Checker" extension
221+
- Other IDEs: Check CSpell documentation for integration options
222+
223+
## 🎯 Goals
224+
225+
- **Consistency**: Maintain professional spelling across all project content
226+
- **Quality**: Catch typos early in the development process
227+
- **Maintainability**: Keep a clean, well-organized project dictionary
228+
- **Developer Experience**: Provide clear guidance for handling spelling issues
229+
230+
By following these guidelines, we ensure that our codebase maintains high quality while accommodating the technical nature of our domain vocabulary.

packages/linting/src/cli.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use clap::{CommandFactory, Parser, Subcommand};
33
use tracing::{error, info, Level};
44

55
use crate::linters::{
6-
run_clippy_linter, run_markdown_linter, run_rustfmt_linter, run_shellcheck_linter,
7-
run_toml_linter, run_yaml_linter,
6+
run_clippy_linter, run_cspell_linter, run_markdown_linter, run_rustfmt_linter,
7+
run_shellcheck_linter, run_toml_linter, run_yaml_linter,
88
};
99

1010
/// Initialize tracing with default configuration
@@ -54,6 +54,10 @@ pub enum Commands {
5454
/// Run TOML linter using Taplo
5555
Toml,
5656

57+
/// Run `CSpell` spell checker
58+
#[command(alias = "spell")]
59+
Cspell,
60+
5761
/// Run Rust clippy linter
5862
Clippy,
5963

@@ -106,6 +110,15 @@ pub fn run_all_linters() -> Result<()> {
106110
}
107111
}
108112

113+
// Run CSpell spell checker
114+
match run_cspell_linter() {
115+
Ok(()) => {}
116+
Err(e) => {
117+
error!("Spell checking failed: {e}");
118+
failed = true;
119+
}
120+
}
121+
109122
// Run Rust clippy linter
110123
match run_clippy_linter() {
111124
Ok(()) => {}
@@ -157,6 +170,9 @@ pub fn execute_command(command: Option<&Commands>) -> Result<()> {
157170
Some(Commands::Toml) => {
158171
run_toml_linter()?;
159172
}
173+
Some(Commands::Cspell) => {
174+
run_cspell_linter()?;
175+
}
160176
Some(Commands::Clippy) => {
161177
run_clippy_linter()?;
162178
}
@@ -187,6 +203,7 @@ pub fn print_usage_examples() {
187203
println!(" cargo run --bin linter markdown # Run markdown linter");
188204
println!(" cargo run --bin linter yaml # Run YAML linter");
189205
println!(" cargo run --bin linter toml # Run TOML linter");
206+
println!(" cargo run --bin linter cspell # Run CSpell spell checker");
190207
println!(" cargo run --bin linter clippy # Run Rust clippy linter");
191208
println!(" cargo run --bin linter rustfmt # Run Rust formatter check");
192209
println!(" cargo run --bin linter shellcheck # Run ShellCheck linter");
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
use anyhow::Result;
2+
use std::process::Command;
3+
use tracing::{error, info};
4+
5+
use crate::utils::{install_npm_tool, is_command_available};
6+
7+
/// Run the `CSpell` spell checker linter
8+
///
9+
/// # Errors
10+
///
11+
/// Returns an error if `CSpell` is not available, cannot be installed,
12+
/// or if the spell checking fails.
13+
pub fn run_cspell_linter() -> Result<()> {
14+
// Check if cspell is installed
15+
if !is_command_available("cspell") {
16+
install_npm_tool("cspell")?;
17+
}
18+
19+
// Run the spell checker
20+
info!(target: "cspell", "Running spell check on all files...");
21+
22+
// Run cspell on the entire project (it will use cspell.json configuration)
23+
let mut cmd = Command::new("cspell");
24+
cmd.args([".", "--no-progress", "--show-context"]);
25+
26+
let output = cmd.output()?;
27+
28+
if output.status.success() {
29+
info!(target: "cspell", "All files passed spell checking!");
30+
Ok(())
31+
} else {
32+
let stderr = String::from_utf8_lossy(&output.stderr);
33+
let stdout = String::from_utf8_lossy(&output.stdout);
34+
35+
// Print the output from cspell (it usually goes to stdout)
36+
if !stdout.is_empty() {
37+
println!("{stdout}");
38+
}
39+
if !stderr.is_empty() {
40+
eprintln!("{stderr}");
41+
}
42+
43+
println!();
44+
println!("💡 To fix spelling issues:");
45+
println!(" 1. Fix actual misspellings in the files");
46+
println!(" 2. Add technical terms/proper nouns to project-words.txt");
47+
println!(" 3. Use cspell suggestions: cspell --show-suggestions <file>");
48+
println!();
49+
50+
error!(target: "cspell", "Spell checking failed. Please fix the issues above.");
51+
Err(anyhow::anyhow!("Spell checking failed"))
52+
}
53+
}

packages/linting/src/linters/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
pub mod clippy;
2+
pub mod cspell;
23
pub mod markdown;
34
pub mod rustfmt;
45
pub mod shellcheck;
56
pub mod toml;
67
pub mod yaml;
78

89
pub use clippy::*;
10+
pub use cspell::*;
911
pub use markdown::*;
1012
pub use rustfmt::*;
1113
pub use shellcheck::*;

project-words.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ architecting
33
autorestart
44
buildx
55
childlogdir
6+
clippy
67
cloneable
78
cloudinit
89
connrefused
@@ -61,6 +62,7 @@ Pulumi
6162
pytest
6263
RAII
6364
Repomix
65+
reqwest
6466
resolv
6567
rpcinterface
6668
runcmd
@@ -96,6 +98,8 @@ thiserror
9698
tlsv
9799
tmpfiles
98100
tmpfs
101+
torrust
102+
Torrust
99103
usermod
100104
usize
101105
utmp

0 commit comments

Comments
 (0)