tcss_cli/commands/
check.rs

1//! Check command implementation
2
3use anyhow::{Context, Result};
4use colored::*;
5use std::fs;
6use std::path::Path;
7use std::time::Instant;
8use tcss_core::{Lexer, Parser};
9
10use crate::output;
11
12/// Check TCSS files for errors without compiling
13pub fn check_files(files: &[impl AsRef<Path>], _verbose: bool) -> Result<()> {
14    let start_time = Instant::now();
15    let mut total_files = 0;
16    let mut error_count = 0;
17
18    output::header("Checking TCSS Files");
19    output::debug(&format!("Checking {} file(s)", files.len()));
20    println!();
21
22    for file in files {
23        let file = file.as_ref();
24        total_files += 1;
25
26        output::verbose(&format!("Checking: {}", file.display()));
27        output::debug(&format!("File {}/{}: {}", total_files, files.len(), file.display()));
28
29        match check_file(file, _verbose) {
30            Ok(_) => {
31                output::success(&file.display().to_string());
32            }
33            Err(e) => {
34                error_count += 1;
35                output::error(&file.display().to_string());
36                eprintln!("  {}", e.to_string().bright_red());
37                output::debug(&format!("Error details: {:?}", e));
38            }
39        }
40    }
41
42    println!();
43    output::timing("Check", start_time.elapsed().as_millis());
44
45    // Summary
46    if error_count == 0 {
47        output::success(&format!("All {} file(s) passed!", total_files));
48        output::debug("No errors found");
49        Ok(())
50    } else {
51        println!(
52            "{} {} file(s) with errors, {} file(s) passed",
53            "✗".bright_red().bold(),
54            error_count.to_string().bright_red(),
55            (total_files - error_count).to_string().bright_green()
56        );
57        output::debug(&format!("Found {} error(s) in {} file(s)", error_count, total_files));
58        anyhow::bail!("Check failed with {} error(s)", error_count)
59    }
60}
61
62/// Check a single TCSS file for errors
63fn check_file(file: &Path, _verbose: bool) -> Result<()> {
64    // Read file
65    let source = fs::read_to_string(file)
66        .with_context(|| format!("Failed to read file: {}", file.display()))?;
67
68    // Tokenize
69    let mut lexer = Lexer::new(&source);
70    let tokens = lexer
71        .tokenize()
72        .map_err(|e| anyhow::anyhow!("Lexer error: {}", e))?;
73
74    // Parse
75    let mut parser = Parser::new(tokens);
76    parser
77        .parse()
78        .map_err(|e| anyhow::anyhow!("Parser error: {}", e))?;
79
80    Ok(())
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use std::io::Write;
87    use tempfile::NamedTempFile;
88
89    #[test]
90    fn test_check_valid_file() {
91        let mut file = NamedTempFile::new().unwrap();
92        writeln!(
93            file,
94            r#"
95@var primary: #3498db
96
97.button:
98    background: primary
99"#
100        )
101        .unwrap();
102
103        let result = check_file(file.path(), false);
104        assert!(result.is_ok());
105    }
106
107    #[test]
108    fn test_check_invalid_file() {
109        let mut file = NamedTempFile::new().unwrap();
110        writeln!(
111            file,
112            r#"
113@var primary: #3498db
114
115.button
116    background: primary
117"#
118        )
119        .unwrap();
120
121        let result = check_file(file.path(), false);
122        assert!(result.is_err());
123    }
124}
125