Skip to main content

max / quasi

9.1 KB · 237 lines History Blame Raw
1 //! Every class this renderer is responsible for, as a set rather than one name
2 //! at a time.
3 //!
4 //! The twin of [`makeover_webview::vocabulary`], one layer up, and it exists
5 //! for the same reason: a checker cannot ask "is this rule styling something
6 //! nothing emits" without the list, and this crate is one of the two places
7 //! that knows it.
8 //!
9 //! # Why an app needs this at all
10 //!
11 //! Until the described screens shipped, an app could answer the question by
12 //! reading its own markup: `makeover_build::check_vocabulary_use` took
13 //! `index.html` and the scripts beside it and asked which generated classes
14 //! appeared in none of them. goingson's swap (goingson@f4edefb) deleted all of
15 //! it, and the call was left out of that app's `build.rs` rather than run
16 //! against nothing --- an app cannot see its own document any more, because its
17 //! document is Rust in a dependency.
18 //!
19 //! That dependency is this crate. So the question moves here rather than
20 //! disappearing, which is what this module is.
21 //!
22 //! # The set is closed, and that is the useful part
23 //!
24 //! A description has no word for a class. Everything in the document comes
25 //! from this crate or from makeover.
26 //!
27 //! So [`covers`] is not a lower bound on what an app's stylesheet may
28 //! legitimately match. It is the whole of it, and a selector outside it is dead
29 //! rather than merely unaccounted for. That is what makes a drift check on the
30 //! stylesheet exact instead of advisory.
31 //!
32 //! # One family is open, and it is not ours
33 //!
34 //! `col-<name>`, from [`makeover_webview::list::push_column_class`]: a table
35 //! cell carries a class built from its column's name, which is app data. So the
36 //! set is closed over *names this tree writes* and open over *columns an app
37 //! declares*, and no list can hold the second half.
38 //!
39 //! That is why [`covers`] exists beside [`names`] and is the function to check
40 //! with. An app comparing selectors against [`names`] alone would call every
41 //! `col-` rule dead and delete the table styling.
42 //!
43 //! # What the two vocabularies hold
44 //!
45 //! ```text
46 //! makeover_webview::vocabulary::names() 73
47 //! this crate's own names 59
48 //! makeover names this crate spells 21 (button, list, table, run, ...)
49 //! open families 1 (col-*)
50 //! ```
51 //!
52 //! The 21 are not repeated in [`OWN`]: this crate spells them because it emits
53 //! them, and makeover writes their rules. What [`OWN`] holds is what makeover
54 //! has never heard of --- the frame, the chrome, the region kinds, the screen
55 //! arrangements and measures, the field wrappers, the row and table
56 //! affordances --- for which the app's own stylesheet is the only thing that
57 //! can carry a rule.
58 //!
59 //! # Why this list is written down and makeover's is scraped
60 //!
61 //! makeover generates the stylesheet, so it can parse what it just wrote and
62 //! have no second source to drift from. This crate generates no CSS: it emits
63 //! markup, and there is nothing to read back. A written list is therefore the
64 //! only available shape, and [`super::tests`] carries the guard that makes it
65 //! honest --- a render of every node kind, every region kind, every notice and
66 //! every row state, scraped, with every class asserted to be covered. That
67 //! guard is not decoration: it found fourteen names a careful read of the
68 //! emitters had missed, including the `col-` family above.
69
70 use std::collections::BTreeSet;
71
72 use makeover_webview::Emit;
73
74 /// The classes this crate emits that makeover does not define.
75 ///
76 /// Sorted, and kept that way: this is read as a set, and a list somebody
77 /// appends to is a list nobody can diff.
78 ///
79 /// Unprefixed. [`names`] applies the prefix, because a prefix is host
80 /// configuration and this is the vocabulary.
81 pub const OWN: &[&str] = &[
82 "act-submit",
83 // The popover container an `Outcome::Anchored` lands in, emitted beside
84 // every region, beside a named control and beside a screen's selection.
85 // Not `row-menu`: both are menus and only one of them belongs to a row.
86 "anchored",
87 "ask",
88 "ask-body",
89 "ask-open",
90 // A notice's two kinds. Not makeover's: it has `well` and `card` for
91 // standing content, and no word for something the app said unprompted.
92 "banner",
93 // The region kinds, from `RegionKind`. An arrangement of regions is a
94 // page-level fact, which is why makeover has no word for one.
95 "band",
96 "bespoke",
97 "chip-remove",
98 "chrome-band",
99 "chrome-brand",
100 "chrome-brand-mark",
101 "chrome-disclose",
102 "chrome-disclose-state",
103 "chrome-nav",
104 "chrome-panel",
105 "chrome-place",
106 "chrome-search",
107 "clock",
108 "field-consults",
109 "field-suggests",
110 "field-writes",
111 // `figures`, the strip these sit in, is makeover-webview's name and is
112 // emitted here rather than owned here. It named it from 0.59.0.
113 "figure-act",
114 "form",
115 "frame",
116 "frame-status",
117 "frame-verbs",
118 "group",
119 "heading",
120 // The screen arrangements, from `Arrangement`. Two, because our apps have
121 // two; see `Webview::arrangement_class` for why a third waits for a need.
122 "list-detail",
123 "list-detail-tabbed",
124 // How wide a screen runs, from `Measure`. makeover has no word for it
125 // because the answer is a page-level arrangement rather than anything it
126 // styles.
127 "measure-contained",
128 "measure-reading",
129 "measure-wide",
130 "modal",
131 "notices",
132 "pane",
133 "region",
134 "region-consults",
135 "rest",
136 "rest-next",
137 "rest-page",
138 "rest-page-here",
139 "rest-pages",
140 "rest-position",
141 "rest-previous",
142 "rich",
143 // A list row and its two states. makeover styles the row's *parts*
144 // (`row-primary`, `row-meta`) and not the row, which is this renderer's
145 // arrangement of them.
146 "row",
147 "row-activate",
148 // The outline: a row indented under another, a row with a disclosure, and
149 // the chevron itself. `ccaa7e4b`. The indent is a `--row-depth` custom
150 // property on the row and the rule that reads it is the app's, the way
151 // `row-select`'s box is.
152 "row-branch",
153 "row-disclose",
154 "row-nested",
155 // A live selection, which is neither `row-current` nor `row-selected`. See
156 // `Row::chosen`. `1894e95d`.
157 "row-chosen",
158 "row-current",
159 "row-menu",
160 "row-select",
161 "row-selected",
162 "selector",
163 "sidebar",
164 "sidebar-content",
165 "table-row-chosen",
166 "table-row-current",
167 "table-row-menu",
168 "table-row-selected",
169 "table-disclose",
170 "table-disclose-head",
171 "table-select",
172 "table-select-head",
173 "table-sort",
174 "tabgroup",
175 "text",
176 "toast",
177 "widget",
178 ];
179
180 /// The prefix-free stem of the one open family. See the module header.
181 const COLUMN_STEM: &str = "col-";
182
183 /// Every fixed class a document served by this renderer can contain.
184 ///
185 /// [`OWN`] plus [`makeover_webview::vocabulary::names`], prefixed the way
186 /// `opts` prefixes them. The union rather than either part: this crate emits
187 /// both, and an app checking against one would call the other dead.
188 ///
189 /// A `MAKEOVER_UNLISTED` constant stood beside [`OWN`] until
190 /// makeover-webview 0.59.0, holding the three classes that crate emitted and
191 /// did not name. It names its own unruled half now, so the workaround is gone
192 /// rather than kept as a habit.
193 ///
194 /// **Prefer [`covers`] for checking a name.** This set is the fixed half; a
195 /// `col-<name>` class is legitimate and is not in here, because it cannot be.
196 ///
197 /// State classes arrive through makeover's half (`chosen`, `latched`), which
198 /// are deliberately unprefixed there: they qualify a prefixed component rather
199 /// than standing on their own.
200 #[must_use]
201 pub fn names(opts: &Emit) -> BTreeSet<String> {
202 let mut all = makeover_webview::vocabulary::names(opts);
203 all.extend(OWN.iter().map(|name| makeover_webview::class(name, opts)));
204 all
205 }
206
207 /// Whether this class is one a document served by this renderer can contain.
208 ///
209 /// [`names`], plus the open `col-<name>` family that no set can hold. This is
210 /// the function an app's drift check should ask, and asking [`names`] instead
211 /// is how a check concludes that every table rule in a stylesheet is dead.
212 ///
213 /// # What an app does with it
214 ///
215 /// Two questions, opposite directions:
216 ///
217 /// - **Which emitted classes does the stylesheet never style?** Vocabulary that
218 /// is emitted and unstyled. Usually fine, occasionally a missing rule.
219 /// - **Which of the stylesheet's selectors match nothing that can be emitted?**
220 /// Dead CSS, and exactly dead rather than probably dead, because the set is
221 /// closed. This is the direction `check_vocabulary_use` could not answer once
222 /// the markup left the app, and it is how goingson finds out how much of a
223 /// 9,830-line stylesheet to delete.
224 #[must_use]
225 pub fn covers(class: &str, opts: &Emit) -> bool {
226 let Some(stem) = class.strip_prefix(opts.class_prefix) else {
227 return false;
228 };
229 // A column class is named for app data, so it is checked by shape. The stem
230 // alone is not enough: a bare `col-` names no column, and makeover reduces
231 // a column's name to identifier characters before it becomes one.
232 if let Some(column) = stem.strip_prefix(COLUMN_STEM) {
233 return !column.is_empty();
234 }
235 names(opts).contains(class)
236 }
237