compiletest/directives/line.rs
1use std::fmt;
2
3const COMPILETEST_DIRECTIVE_PREFIX: &str = "//@";
4
5/// If the given line begins with the appropriate comment prefix for a directive,
6/// returns a struct containing various parts of the directive.
7pub(crate) fn line_directive<'line>(
8 line_number: usize,
9 original_line: &'line str,
10) -> Option<DirectiveLine<'line>> {
11 // Ignore lines that don't start with the comment prefix.
12 let after_comment =
13 original_line.trim_start().strip_prefix(COMPILETEST_DIRECTIVE_PREFIX)?.trim_start();
14
15 let revision;
16 let raw_directive;
17
18 if let Some(after_open_bracket) = after_comment.strip_prefix('[') {
19 // A comment like `//@[foo]` only applies to revision `foo`.
20 let Some((line_revision, after_close_bracket)) = after_open_bracket.split_once(']') else {
21 panic!(
22 "malformed condition directive: expected `{COMPILETEST_DIRECTIVE_PREFIX}[foo]`, found `{original_line}`"
23 )
24 };
25
26 revision = Some(line_revision);
27 raw_directive = after_close_bracket.trim_start();
28 } else {
29 revision = None;
30 raw_directive = after_comment;
31 };
32
33 // The directive name ends at the first occurrence of colon, space, or end-of-string.
34 let name = raw_directive.split([':', ' ']).next().expect("split is never empty");
35
36 Some(DirectiveLine { line_number, revision, raw_directive, name })
37}
38
39/// The (partly) broken-down contents of a line containing a test directive,
40/// which `iter_directives` passes to its callback function.
41///
42/// For example:
43///
44/// ```text
45/// //@ compile-flags: -O
46/// ^^^^^^^^^^^^^^^^^ raw_directive
47/// ^^^^^^^^^^^^^ name
48///
49/// //@ [foo] compile-flags: -O
50/// ^^^ revision
51/// ^^^^^^^^^^^^^^^^^ raw_directive
52/// ^^^^^^^^^^^^^ name
53/// ```
54pub(crate) struct DirectiveLine<'ln> {
55 pub(crate) line_number: usize,
56
57 /// Some test directives start with a revision name in square brackets
58 /// (e.g. `[foo]`), and only apply to that revision of the test.
59 /// If present, this field contains the revision name (e.g. `foo`).
60 pub(crate) revision: Option<&'ln str>,
61
62 /// The main part of the directive, after removing the comment prefix
63 /// and the optional revision specifier.
64 ///
65 /// This is "raw" because the directive's name and colon-separated value
66 /// (if present) have not yet been extracted or checked.
67 raw_directive: &'ln str,
68
69 /// Name of the directive.
70 ///
71 /// Invariant: `self.raw_directive.starts_with(self.name)`
72 pub(crate) name: &'ln str,
73}
74
75impl<'ln> DirectiveLine<'ln> {
76 pub(crate) fn applies_to_test_revision(&self, test_revision: Option<&str>) -> bool {
77 self.revision.is_none() || self.revision == test_revision
78 }
79
80 /// Helper method used by `value_after_colon` and `remark_after_space`.
81 /// Don't call this directly.
82 fn rest_after_separator(&self, separator: u8) -> Option<&'ln str> {
83 let n = self.name.len();
84 if self.raw_directive.as_bytes().get(n) != Some(&separator) {
85 return None;
86 }
87
88 Some(&self.raw_directive[n + 1..])
89 }
90
91 /// If this directive uses `name: value` syntax, returns the part after
92 /// the colon character.
93 pub(crate) fn value_after_colon(&self) -> Option<&'ln str> {
94 self.rest_after_separator(b':')
95 }
96
97 /// If this directive uses `name remark` syntax, returns the part after
98 /// the separating space.
99 pub(crate) fn remark_after_space(&self) -> Option<&'ln str> {
100 self.rest_after_separator(b' ')
101 }
102
103 /// Allows callers to print `raw_directive` if necessary,
104 /// without accessing the field directly.
105 pub(crate) fn display(&self) -> impl fmt::Display {
106 self.raw_directive
107 }
108}