1use crate::dialect::Dialect;
10use crate::error::ErrorCode;
11
12#[derive(Debug, Clone, Copy)]
14pub struct MatrixRow {
15 pub id: &'static str,
17 pub status: [&'static str; 5],
19 pub code: Option<ErrorCode>,
21}
22
23const fn row(id: &'static str, status: [&'static str; 5], code: Option<ErrorCode>) -> MatrixRow {
24 MatrixRow { id, status, code }
25}
26
27const SSSSS: [&str; 5] = ["S", "S", "S", "S", "S"];
28const RSV: &str = "S in recent versions";
29
30pub const MATRIX: &[MatrixRow] = &[
32 row("literals-escapes", SSSSS, None),
33 row("any", SSSSS, None),
34 row("concat-alt", SSSSS, None),
35 row("classes", SSSSS, None),
36 row("class-escapes", SSSSS, None),
37 row("posix-classes", ["–", "–", "S", "S", "S"], None),
38 row("class-set-ops", ["–", "–", "–", "S", "–"], None),
39 row(
40 "unicode-properties",
41 ["R", "–", "S", "S", "S"],
42 Some(ErrorCode::U007),
43 ),
44 row("astral-escapes", ["R", "S", "S", "S", "S"], None),
45 row("quantifiers", SSSSS, None),
46 row("counted-quantifiers", SSSSS, None),
47 row(
48 "possessive",
49 ["–", RSV, "–", "–", "U"],
50 Some(ErrorCode::U004),
51 ),
52 row("groups", SSSSS, None),
53 row("named-groups", SSSSS, None),
54 row("inline-flag-groups", ["–", "S", "S", "S", "S"], None),
55 row("atomic", ["–", RSV, "–", "–", "U"], Some(ErrorCode::U004)),
56 row(
57 "conditional",
58 ["–", "U", "–", "–", "U"],
59 Some(ErrorCode::U003),
60 ),
61 row(
62 "branch-reset",
63 ["–", "–", "–", "–", "U"],
64 Some(ErrorCode::U007),
65 ),
66 row(
67 "recursion",
68 ["–", "–", "–", "–", "U"],
69 Some(ErrorCode::U002),
70 ),
71 row("callouts", ["–", "–", "–", "–", "U"], Some(ErrorCode::U006)),
72 row("anchors", SSSSS, None),
73 row("text-anchors", ["–", "S", "S", "S", "S"], None),
74 row("word-boundary", ["R", "R", "R", "R", "R"], None),
75 row(
76 "lookahead",
77 ["R", "R", "–", "–", "R"],
78 Some(ErrorCode::U005),
79 ),
80 row(
81 "lookbehind",
82 ["R", "R", "–", "–", "R"],
83 Some(ErrorCode::U005),
84 ),
85 row(
86 "backreferences",
87 ["U", "U", "–", "–", "U"],
88 Some(ErrorCode::U001),
89 ),
90 row("flag-i", SSSSS, None),
91 row("flag-m", SSSSS, None),
92 row("flag-s", SSSSS, None),
93 row(
94 "flag-u",
95 ["S", "implicit", "implicit", "implicit", "S"],
96 None,
97 ),
98 row("flag-x", ["–", "S", "–", "S", "S"], None),
99 row("flag-a", ["–", "S", "–", "S", "–"], None),
100 row(
101 "flags-gyd-v",
102 ["R", "–", "–", "–", "–"],
103 Some(ErrorCode::U007),
104 ),
105];
106
107pub const REJECTION_SAMPLES: &[(Dialect, &str, ErrorCode)] = &[
110 (Dialect::Ecma, r"(a)\1", ErrorCode::U001),
111 (Dialect::Ecma, r"(?<n>a)\k<n>", ErrorCode::U001),
112 (Dialect::Python, r"(a)\1", ErrorCode::U001),
113 (Dialect::Python, r"(?P<n>a)(?P=n)", ErrorCode::U001),
114 (Dialect::Python, "(?>a+)b", ErrorCode::U004),
115 (Dialect::Python, "a*+", ErrorCode::U004),
116 (Dialect::Python, "(a)(?(1)b|c)", ErrorCode::U003),
117];
118
119#[cfg(test)]
120#[allow(clippy::expect_used)]
121mod tests {
122 use super::*;
123 use crate::flags::Flags;
124 use crate::parser::Pattern;
125
126 #[test]
127 fn rows_match_documentation_status_cells() {
128 let path = concat!(
129 env!("CARGO_MANIFEST_DIR"),
130 "/../../docs/13-syntax-support-matrix.md"
131 );
132 let doc = std::fs::read_to_string(path).expect("docs/13 readable");
133 let mut documented: Vec<Vec<String>> = Vec::new();
134 for line in doc.lines() {
135 let line = line.replace("\\|", "\u{1}");
137 let cells: Vec<String> = line.split('|').map(|c| c.trim().to_string()).collect();
138 if cells.len() == 9
140 && !cells[1].is_empty()
141 && cells[1] != "Element"
142 && cells[1] != "Flag"
143 && !cells[1].starts_with("---")
144 {
145 documented.push(cells[2..7].to_vec());
146 }
147 }
148 let data: Vec<Vec<String>> = MATRIX
149 .iter()
150 .map(|r| r.status.iter().map(|s| s.to_string()).collect())
151 .collect();
152 assert_eq!(documented, data);
153 }
154
155 #[test]
156 fn ids_are_unique() {
157 let mut ids: Vec<_> = MATRIX.iter().map(|r| r.id).collect();
158 ids.sort_unstable();
159 ids.dedup();
160 assert_eq!(ids.len(), MATRIX.len());
161 }
162
163 #[test]
164 fn every_rejection_sample_yields_its_code() {
165 for (d, text, code) in REJECTION_SAMPLES {
166 let e = Pattern::parse(text, *d, Flags::none()).expect_err(text);
167 assert_eq!(e.kind, *code, "{} / {text}", d.name());
168 }
169 }
170}