litcheck_filecheck/
lib.rs

1#![cfg_attr(test, feature(assert_matches))]
2pub mod ast;
3pub mod check;
4mod context;
5mod cursor;
6mod env;
7pub mod errors;
8pub mod expr;
9mod input;
10pub mod parse;
11pub mod pattern;
12pub mod rules;
13mod test;
14
15pub use self::errors::{CheckFailedError, RelatedCheckError, RelatedError, TestFailed};
16#[cfg(test)]
17pub use self::test::TestContext;
18pub use self::test::{Test, TestResult};
19
20use clap::{builder::ValueParser, ArgAction, Args, ColorChoice, ValueEnum};
21use std::sync::Arc;
22
23pub(crate) mod common {
24    pub use std::{
25        borrow::Cow,
26        fmt,
27        ops::{ControlFlow, RangeBounds},
28        sync::Arc,
29    };
30
31    pub use either::Either::{self, Left, Right};
32    pub use litcheck::{
33        diagnostics::{
34            ArcSource, Diag, DiagResult, Diagnostic, Label, NamedSourceFile, Report, Source,
35            SourceFile, SourceSpan, Span, Spanned,
36        },
37        range::{self, Range},
38        text::{self, Newline},
39        StringInterner, Symbol,
40    };
41    pub use regex_automata::{meta::Regex, util::look::LookMatcher};
42    pub use smallvec::{smallvec, SmallVec};
43
44    pub use crate::ast::{Check, Constraint};
45    pub use crate::context::{Context, ContextExt, ContextGuard, MatchContext};
46    pub use crate::cursor::{Cursor, CursorGuard, CursorPosition};
47    pub use crate::env::{Env, LexicalScope, LexicalScopeExtend, LexicalScopeMut, ScopeGuard};
48    pub use crate::errors::{
49        CheckFailedError, RelatedCheckError, RelatedError, RelatedLabel, TestFailed,
50    };
51    pub use crate::expr::{BinaryOp, Expr, Number, NumberFormat, Value, VariableName};
52    pub use crate::input::Input;
53    pub use crate::pattern::{
54        AnyMatcher, CaptureInfo, MatchInfo, MatchResult, MatchType, Matcher, MatcherMut, Matches,
55        Pattern, PatternIdentifier, PatternSearcher, Searcher,
56    };
57    pub use crate::rules::{DynRule, Rule};
58    #[cfg(test)]
59    pub(crate) use crate::test::TestContext;
60    pub use crate::test::TestResult;
61    pub use crate::Config;
62}
63
64pub const DEFAULT_CHECK_PREFIXES: &[&str] = &["CHECK"];
65pub const DEFAULT_COMMENT_PREFIXES: &[&str] = &["COM", "RUN"];
66
67/// FileCheck reads two files, one from standard input, and one specified on
68/// the command line; and uses one to verify the other.
69#[derive(Debug, Args)]
70pub struct Config {
71    /// Allow checking empty input. By default, empty input is rejected.
72    #[arg(
73        default_value_t = false,
74        action(clap::ArgAction::SetTrue),
75        help_heading = "Input"
76    )]
77    pub allow_empty: bool,
78    /// Which prefixes to treat as directives.
79    ///
80    /// For example, in the directive `CHECK-SAME`, `CHECK` is the prefix.
81    #[arg(
82        long = "check-prefix",
83        value_name = "PREFIX",
84        default_value = "CHECK",
85        action(clap::ArgAction::Append),
86        value_parser(re_value_parser("^[A-Za-z][A-Za-z0-9_]*")),
87        help_heading = "Syntax"
88    )]
89    pub check_prefixes: Vec<Arc<str>>,
90    /// Which prefixes to treat as comments.
91    ///
92    /// All content on a line following a comment directive is ignored,
93    /// up to the next newline.
94    #[arg(
95        long = "comment-prefix",
96        value_name = "PREFIX",
97        default_value = "COM,RUN",
98        action(clap::ArgAction::Append),
99        value_parser(re_value_parser("^[A-Za-z][A-Za-z0-9_]*")),
100        help_heading = "Syntax"
101    )]
102    pub comment_prefixes: Vec<Arc<str>>,
103    /// If specifying multiple check prefixes, this controls whether or not
104    /// to raise an error if one of the prefixes is missing in the test file.
105    #[arg(long, default_value_t = false, help_heading = "Syntax")]
106    pub allow_unused_prefixes: bool,
107    /// Disable default canonicalization of whitespace.
108    ///
109    /// By default, FileCheck canonicalizes horizontal whitespace (spaces and tabs)
110    /// which causes it to ignore these differences (a space will match a tab).
111    ///
112    /// This flag disables horizontal whitespace canonicalization.
113    ///
114    /// Newlines are always canonicalized to LF regardless of this setting.
115    #[arg(
116        long = "strict-whitespace",
117        default_value_t = false,
118        help_heading = "Matching"
119    )]
120    pub strict_whitespace: bool,
121    /// This flag changes the default matching behavior to require all positive
122    /// matches to cover an entire line. Leading/trailing whitespace is ignored
123    /// unless `--strict-whitespace` is also specified.
124    ///
125    /// By default, FileCheck allows matches of anywhere on a line, so by setting
126    /// this, you effectively insert `{{^.*}}` or `{{^}}` before, and `{{[*$]}}`
127    /// or `{{$}}` after every positive check pattern.
128    ///
129    /// NOTE Negative matches, i.e `CHECK-NOT` are not affected by this option.
130    #[arg(long, default_value_t = false, help_heading = "Matching")]
131    pub match_full_lines: bool,
132    /// Disable case-sensitive matching
133    #[arg(long, default_value_t = false, help_heading = "Matching")]
134    pub ignore_case: bool,
135    /// Adds implicit negative checks for the specified patterns between positive checks.
136    ///
137    /// This option allows writing stricter tests without polluting them with CHECK-NOTs.
138    ///
139    /// For example, `--implicit-check-not warning:` can be useful when testing diagnostic
140    /// messages from tools that don’t have an option similar to `clang -verify`. With this
141    /// option FileCheck will verify that input does not contain warnings not covered by any
142    /// `CHECK:` patterns.
143    #[arg(long, value_name = "CHECK", help_heading = "Matching")]
144    pub implicit_check_not: Vec<String>,
145    /// Dump input to stderr, adding annotations representing currently enabled diagnostics.
146    #[arg(long, value_enum, value_name = "TYPE", default_value_t = Dump::Fail, help_heading = "Output")]
147    pub dump_input: Dump,
148    /// Specify the parts of the input to dump when `--dump-input` is set.
149    ///
150    /// When specified, print only input lines of KIND, plus any context specified by `--dump-input-context`.
151    ///
152    /// Defaults to `error` when `--dump-input=fail`, and `all` when `--dump-input=always`
153    #[arg(
154        long,
155        value_enum,
156        value_name = "KIND",
157        default_value_t = DumpFilter::Error,
158        default_value_ifs([("dump-input", "fail", Some("error")), ("dump-input", "always", Some("all"))]),
159        help_heading = "Output"
160    )]
161    pub dump_input_filter: DumpFilter,
162    /// Enables scope for regex variables
163    ///
164    /// Variables with names that start with `$` are considered global, and remain set throughout the file
165    ///
166    /// All other variables get undefined after each encountered `CHECK-LABEL`
167    #[arg(long, default_value_t = false, help_heading = "Variables")]
168    pub enable_var_scope: bool,
169    /// Set a pattern variable VAR with value VALUE that can be used in `CHECK:` lines
170    ///
171    /// You must specify each one in `key=value` format
172    #[arg(
173        long = "define",
174        short = 'D',
175        value_name = "NAME=VALUE",
176        help_heading = "Variables"
177    )]
178    pub variables: Vec<expr::CliVariable>,
179    /// Set the verbosity level.
180    ///
181    /// If specified a single time, it causes filecheck to print good directive pattern matches
182    ///
183    /// If specified multiple times, filecheck will emit internal diagnostics to aid in troubleshooting.
184    ///
185    /// If `--dump-input=fail` or `--dump-input=always`, add information as input annotations instead.
186    #[arg(long, short = 'v', action = ArgAction::Count, help_heading = "Output")]
187    pub verbose: u8,
188    /// Whether, and how, to color terminal output
189    #[arg(
190        global(true),
191        value_enum,
192        long,
193        default_value_t = ColorChoice::Auto,
194        default_missing_value = "auto",
195        help_heading = "Output"
196    )]
197    pub color: ColorChoice,
198}
199impl Default for Config {
200    fn default() -> Self {
201        Self {
202            allow_empty: false,
203            check_prefixes: vec![Arc::from("CHECK".to_string().into_boxed_str())],
204            comment_prefixes: vec![
205                Arc::from("COM".to_string().into_boxed_str()),
206                Arc::from("RUN".to_string().into_boxed_str()),
207            ],
208            allow_unused_prefixes: false,
209            strict_whitespace: false,
210            match_full_lines: false,
211            ignore_case: false,
212            implicit_check_not: vec![],
213            dump_input: Default::default(),
214            dump_input_filter: Default::default(),
215            enable_var_scope: false,
216            variables: vec![],
217            verbose: 0,
218            color: Default::default(),
219        }
220    }
221}
222
223#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, ValueEnum)]
224pub enum Dump {
225    /// Explain input dump and quit
226    Help,
227    /// Always dump input
228    Always,
229    /// Dump input on failure
230    #[default]
231    Fail,
232    /// Never dump input
233    Never,
234}
235
236#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, ValueEnum)]
237pub enum DumpFilter {
238    /// All input lines
239    All,
240    /// Input lines with annotations
241    AnnotationFull,
242    /// Input lines with starting points of annotations
243    Annotation,
244    /// Input lines with starting points of error annotations
245    #[default]
246    Error,
247}
248
249fn re_value_parser(r: &'static str) -> ValueParser {
250    use clap::{error::ErrorKind, Error};
251
252    ValueParser::from(move |s: &str| -> Result<Arc<str>, clap::Error> {
253        let re = regex::Regex::new(r).unwrap();
254        if re.is_match(s) {
255            Ok(Arc::from(s.to_owned().into_boxed_str()))
256        } else {
257            Err(Error::raw(
258                ErrorKind::ValueValidation,
259                "'{s}' does not match expected pattern `{r}`",
260            ))
261        }
262    })
263}