1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use super::*;
use crate::bus_seed::{INSTALL_PREFIXES, ROLE_SUFFIXES};
// A registration call's own name is the only signal available before
// resolution, so the list-entry rule and the exact name boundary it draws are
// what decide whether a repository's handler vocabulary is found at all. A
// name the rule misses drops every hop that hangs off the bases the missed
// registration would have taught, and a name it admits too liberally costs a
// fragment byte until the resolver's own base check discards it. These cases
// therefore pin both sides at their current outcome: the spellings admitted
// today, and the ones that are not.
// One invoked member call on line 7 of a source that is otherwise fixed, so a
// fact's line is asserted against the layout rather than restated by hand.
fn source_with(call: &str) -> String {
format!(
"namespace App.Wiring;\n\nclass Startup\n{{\n void Configure()\n {{\n {call}\n }}\n}}\n"
)
}
fn facts_of(call: &str) -> Vec<(String, String, usize)> {
let e = extract_src(&source_with(call));
e.handler_registrations
.iter()
.map(|r| (r.handler.clone(), r.namespace.clone(), r.line))
.collect()
}
fn admitted_entry_facts(method: &str) -> Vec<(String, String, usize)> {
facts_of(&format!("registry.{method}<DeskHandler>();"))
}
#[test]
fn handler_registration_admits_each_list_entry() {
for prefix in INSTALL_PREFIXES {
// A lead word ending in no role suffix, so the prefix is the only
// reason the composed name can be admitted.
let method = format!("{prefix}Loom");
assert_eq!(
admitted_entry_facts(&method),
vec![("DeskHandler".to_string(), "App.Wiring".to_string(), 7)],
"{method}"
);
}
for suffix in ROLE_SUFFIXES {
// A lead word starting with no prefix, so the suffix alone carries it.
let method = format!("Kiln{suffix}");
assert_eq!(
admitted_entry_facts(&method),
vec![("DeskHandler".to_string(), "App.Wiring".to_string(), 7)],
"{method}"
);
}
}
#[test]
fn handler_registration_name_boundary() {
// The rule is a bare starts_with/ends_with with no word boundary, so a
// name that merely BEGINS with a prefix is admitted, and one whose first
// letter after the prefix is lower case is not: case is significant.
for method in ["Address", "User", "Mapping"] {
assert_eq!(
admitted_entry_facts(method).len(),
1,
"{method} only begins the prefix word"
);
}
// Further letters after a role suffix end the match: the suffix has to be
// the end of the name, not a word inside it.
for method in [
"addShelf",
"Shelfworker",
"ShelfWorkers",
"ShelfHandlerFactory",
"Configure",
"With",
"Endpoint",
] {
assert!(
admitted_entry_facts(method).is_empty(),
"{method}: {:?}",
admitted_entry_facts(method)
);
}
// Matching is byte-level, so what follows a match decides it however it is
// encoded: a multi-byte letter after an admitted prefix changes nothing,
// and one after a role suffix is a further letter like any other.
assert_eq!(
admitted_entry_facts("AddLoom\u{e9}").len(),
1,
"a prefix still begins the name whatever follows it"
);
assert!(
admitted_entry_facts("ShelfWorker\u{e9}").is_empty(),
"a role suffix followed by a multi-byte letter is still a suffix followed by more"
);
}
#[test]
fn handler_registration_argument_and_call_shapes() {
// Every refused shape sits in one source beside the two shapes that DO
// record, so a negative cannot pass because the source recorded nothing
// at all. Only the invoked member call and the naming of a method's own
// type parameter record, in source order.
let lines = [
"namespace App.Wiring;",
"",
"class Startup<TEnclosing>",
"{",
" void Configure<TMethod>()",
" {",
" registry.AddLoom();",
" registry.AddLoom(typeof(DeskHandler));",
" registry.AddLoom<DeskHandler, DeskState>();",
" registry.AddLoom<TEnclosing>();",
" var group = registry.AddLoom<DeskHandler>;",
" AddLoom<DeskHandler>();",
" registry?.AddLoom<DeskHandler>();",
" registry.AddLoom<TMethod>();",
" registry.AddLoom<DeskHandler>();",
" }",
"}",
];
let e = extract_src(&format!("{}\n", lines.join("\n")));
let facts: Vec<(String, usize)> = e
.handler_registrations
.iter()
.map(|r| (r.handler.clone(), r.line))
.collect();
assert_eq!(
facts,
vec![("TMethod".to_string(), 14), ("DeskHandler".to_string(), 15),],
"a type argument naming the enclosing method's own parameter is recorded under that \
name because the extraction scope carries only the enclosing type's parameters; the \
enclosing type's own parameter, a receiver-less call, a conditional-access call, a \
non-invoked method group and a zero- or two-argument list all record nothing"
);
assert!(
e.handler_registrations
.iter()
.all(|r| r.namespace == "App.Wiring"),
"{:?}",
e.handler_registrations
);
}