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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
//! Contract tests for Rust: a test that names the promise it proves, checked
//! as it compiles.
//!
//! A *Contract* is the list of promises a piece of code makes, written down in
//! `contract/<primitive>/README.md` as numbered *obligations*. A contract test
//! names the obligations it proves with [`#[impish::contract]`](macro@contract):
//!
//! ```rust,ignore
//! #[impish::contract("slugify/A1")]
//! fn lowercases_letters_and_digits() {
//! assert_eq!(slug::slugify("Rust 2024"), "rust-2024");
//! }
//! ```
//!
//! The attribute reads `contract/slugify/README.md` while the test compiles. If
//! `A1` isn't declared there, or is retired, the test doesn't compile, so a
//! typo or a broken promise can't slip into a green build.
//!
//! This crate is the attribute only. The [`cargo-impish`] command checks that
//! every obligation has a test (`cargo impish check`) and that the tests catch
//! real bugs (`cargo impish mutate`). The [README] explains the method.
//!
//! # Getting started
//!
//! Add the crate as a dev-dependency, and the command once per machine:
//!
//! ```sh
//! cargo add --dev impish
//! cargo install cargo-impish
//! ```
//!
//! Write the Contract for a primitive, here `slugify`, in
//! `contract/slugify/README.md`:
//!
//! ````markdown
//! # slugify
//!
//! Turns a title into a URL slug.
//!
//! ## Obligations
//!
//! ### A1 — Letters and digits are kept, lowercased
//! - **must:** keep ASCII letters and digits, lowercased
//! - **kind:** functional
//! - **status:** active
//!
//! ```covers
//! src/lib.rs::slugify
//! ```
//! ````
//!
//! Prove it in `tests/contract/slugify.rs`, and list the primitive in
//! `tests/contract/main.rs` (`mod slugify;`), which Cargo builds as the
//! `contract` test target:
//!
//! ```rust,ignore
//! #[impish::contract("slugify/A1")]
//! fn lowercases_letters_and_digits() {
//! assert_eq!(slug::slugify("Rust 2024"), "rust-2024");
//! }
//! ```
//!
//! Then `cargo test` runs it, and `cargo impish check` confirms every
//! obligation is proved:
//!
//! ```text
//! The Contract holds: 1 obligations across 1 primitives.
//! ```
//!
//! # Existing tests
//!
//! The attribute doesn't replace a test attribute that is already there. Add
//! it on the line below (or above) `#[test]`, `#[tokio::test]` or any
//! attribute whose path ends in `test`, and the test still runs once:
//!
//! ```rust,ignore
//! #[tokio::test]
//! #[impish::contract("fetch/A2")]
//! async fn retries_after_a_timeout() {
//! // ...
//! }
//! ```
//!
//! On its own, the attribute makes the function a `#[test]`.
//!
//! # What is checked as the test compiles
//!
//! For each tag, the attribute reports a compile error on that tag when:
//!
//! | Error | Meaning and fix |
//! | --- | --- |
//! | ``name the obligations this test proves: #[impish::contract("<primitive>/A<n>")]`` | The attribute has no tags. Name at least one. Tags are string literals. |
//! | `` `x` isn't <primitive>/A<n> `` | A tag is not a primitive and an obligation number, such as `"slugify/A1"`. |
//! | `` `x`: the primitive must be letters, digits, `-` and `_` `` | A tag tried to name a path outside `contract/`. Nothing is read. |
//! | `` `x`: contract/p/README.md can't be read `` | The primitive has no Contract yet. Create its README, or fix the name. |
//! | ``…isn't a valid Contract; `cargo impish check` names its problems`` | The README doesn't parse. Run `cargo impish check` for the line. |
//! | `` `x`: contract/p/README.md doesn't declare A9 `` | No such obligation. Add it to the Contract first, or fix the number. |
//! | `` `x`: contract/p/README.md declares A2 retired `` | The promise was withdrawn. Delete the test, or name the obligation that replaced it. |
//!
//! The README is part of the test's build, so editing it checks the tags again
//! on the next `cargo test`.
//!
//! # Where it belongs
//!
//! Use the attribute only in `tests/contract/<primitive>.rs`, the tests of
//! that primitive's Contract; `cargo impish check` reports it anywhere else.
//! Tags are resolved against `contract/` in the crate being tested
//! (`CARGO_MANIFEST_DIR`). Several tags on one test are fine:
//! `#[impish::contract("cli/A1", "cli/A2")]`.
//!
//! The Contract format, every `check` and `mutate` message, and how to adopt
//! impish in an existing crate are in the [README].
//!
//! [`cargo-impish`]: https://crates.io/crates/cargo-impish
//! [README]: https://github.com/Abarbesgaard/impish#readme
use PathBuf;
use TokenStream;
use Span;
use quote;
use Parser;
use Punctuated;
use ;
/// Marks a contract test with the obligations it proves.
///
/// ```text
/// #[impish::contract("<primitive>/A<n>", …)]
/// ```
///
/// Each argument is a string literal naming one obligation: the primitive (a
/// directory under `contract/`) and the obligation's number in its README.
/// Every tag is checked against `contract/<primitive>/README.md` as the test
/// compiles. A tag that is malformed, unknown or retired is a compile error on
/// that tag; the [crate documentation](crate#what-is-checked-as-the-test-compiles)
/// lists each error and its fix.
///
/// The function becomes a `#[test]`, unless it already has a test attribute
/// (`#[test]`, `#[tokio::test]` or any path ending in `test`), above or below
/// this one, which it keeps:
///
/// ```rust,ignore
/// #[impish::contract("parse/A1", "parse/A3")]
/// fn rejects_an_empty_line() {
/// assert!(parse("").is_err());
/// }
///
/// #[tokio::test]
/// #[impish::contract("fetch/A2")]
/// async fn retries_after_a_timeout() {
/// // ...
/// }
/// ```
///
/// `#[ignore]` works as usual, but an ignored test proves nothing:
/// `cargo impish check` reports its obligations as unproved.
/// Whether `item` already carries a test attribute: `#[test]`, or one whose
/// path ends in `test`. When one sits above this attribute, rustc expands it
/// first and passes the bare function on, so the source file is asked too.
/// Whether the file this attribute is written in has `item`, the same
/// signature and body, with a test attribute.
/// The README that declares `tag`'s obligation, or why it doesn't.