1 //! Defines `Fixture` -- a convenient way to describe the initial state of
2 //! rust-analyzer database from a single string.
4 //! Fixtures are strings containing rust source code with optional metadata.
5 //! A fixture without metadata is parsed into a single source file.
6 //! Use this to test functionality local to one file.
12 //! println!("Hello World")
17 //! Metadata can be added to a fixture after a `//-` comment.
18 //! The basic form is specifying filenames,
19 //! which is also how to define multiple files in a single test fixture
21 //! Example using two files in the same crate:
35 //! Example using two crates with one file each, with one crate depending on the other:
38 //! //- /main.rs crate:a deps:b
42 //! //- /lib.rs crate:b
44 //! println!("Hello World")
49 //! Metadata allows specifying all settings and variables
50 //! that are available in a real rust project:
51 //! - crate names via `crate:cratename`
52 //! - dependencies via `deps:dep1,dep2`
53 //! - configuration settings via `cfg:dbg=false,opt_level=2`
54 //! - environment variables via `env:PATH=/bin,RUST_LOG=debug`
56 //! Example using all available metadata:
59 //! //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
60 //! fn insert_source_code_here() {}
66 use rustc_hash
::FxHashMap
;
67 use stdx
::trim_indent
;
69 #[derive(Debug, Eq, PartialEq)]
73 pub krate
: Option
<String
>,
74 pub deps
: Vec
<String
>,
75 pub extern_prelude
: Option
<Vec
<String
>>,
76 pub cfg_atoms
: Vec
<String
>,
77 pub cfg_key_values
: Vec
<(String
, String
)>,
78 pub edition
: Option
<String
>,
79 pub env
: FxHashMap
<String
, String
>,
80 pub introduce_new_source_root
: Option
<String
>,
81 pub target_data_layout
: Option
<String
>,
85 activated_flags
: Vec
<String
>,
86 valid_flags
: Vec
<String
>,
90 /// Parses text which looks like this:
99 /// Fixture can also start with a proc_macros and minicore declaration(in that order):
102 /// //- proc_macros: identity
103 /// //- minicore: sized
106 /// That will include predefined proc macros and a subset of `libcore` into the fixture, see
107 /// `minicore.rs` for what's available.
108 pub fn parse(ra_fixture
: &str) -> (Option
<MiniCore
>, Vec
<String
>, Vec
<Fixture
>) {
109 let fixture
= trim_indent(ra_fixture
);
110 let mut fixture
= fixture
.as_str();
111 let mut mini_core
= None
;
112 let mut res
: Vec
<Fixture
> = Vec
::new();
113 let mut test_proc_macros
= vec
![];
115 if fixture
.starts_with("//- proc_macros:") {
116 let first_line
= fixture
.split_inclusive('
\n'
).next().unwrap();
117 test_proc_macros
= first_line
118 .strip_prefix("//- proc_macros:")
121 .map(|it
| it
.trim().to_string())
123 fixture
= &fixture
[first_line
.len()..];
126 if fixture
.starts_with("//- minicore:") {
127 let first_line
= fixture
.split_inclusive('
\n'
).next().unwrap();
128 mini_core
= Some(MiniCore
::parse(first_line
));
129 fixture
= &fixture
[first_line
.len()..];
132 let default = if fixture
.contains("//-") { None }
else { Some("//- /main.rs") }
;
134 for (ix
, line
) in default.into_iter().chain(fixture
.split_inclusive('
\n'
)).enumerate() {
135 if line
.contains("//-") {
137 line
.starts_with("//-"),
138 "Metadata line {ix} has invalid indentation. \
139 All metadata lines need to have the same indentation.\n\
140 The offending line: {line:?}"
144 if line
.starts_with("//-") {
145 let meta
= Fixture
::parse_meta_line(line
);
148 if line
.starts_with("// ")
149 && line
.contains('
:'
)
150 && !line
.contains("::")
151 && !line
.contains('
.'
)
152 && line
.chars().all(|it
| !it
.is_uppercase())
154 panic
!("looks like invalid metadata line: {line:?}");
157 if let Some(entry
) = res
.last_mut() {
158 entry
.text
.push_str(line
);
163 (mini_core
, test_proc_macros
, res
)
166 //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b env:OUTDIR=path/to,OTHER=foo
167 fn parse_meta_line(meta
: &str) -> Fixture
{
168 assert
!(meta
.starts_with("//-"));
169 let meta
= meta
["//-".len()..].trim();
170 let components
= meta
.split_ascii_whitespace().collect
::<Vec
<_
>>();
172 let path
= components
[0].to_string();
173 assert
!(path
.starts_with('
/'
), "fixture path does not start with `/`: {path:?}");
175 let mut krate
= None
;
176 let mut deps
= Vec
::new();
177 let mut extern_prelude
= None
;
178 let mut edition
= None
;
179 let mut cfg_atoms
= Vec
::new();
180 let mut cfg_key_values
= Vec
::new();
181 let mut env
= FxHashMap
::default();
182 let mut introduce_new_source_root
= None
;
183 let mut target_data_layout
= Some(
184 "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128".to_string(),
186 for component
in components
[1..].iter() {
188 component
.split_once('
:'
).unwrap_or_else(|| panic
!("invalid meta line: {meta:?}"));
190 "crate" => krate
= Some(value
.to_string()),
191 "deps" => deps
= value
.split('
,'
).map(|it
| it
.to_string()).collect(),
192 "extern-prelude" => {
193 if value
.is_empty() {
194 extern_prelude
= Some(Vec
::new());
197 Some(value
.split('
,'
).map(|it
| it
.to_string()).collect
::<Vec
<_
>>());
200 "edition" => edition
= Some(value
.to_string()),
202 for entry
in value
.split('
,'
) {
203 match entry
.split_once('
='
) {
204 Some((k
, v
)) => cfg_key_values
.push((k
.to_string(), v
.to_string())),
205 None
=> cfg_atoms
.push(entry
.to_string()),
210 for key
in value
.split('
,'
) {
211 if let Some((k
, v
)) = key
.split_once('
='
) {
212 env
.insert(k
.into(), v
.into());
216 "new_source_root" => introduce_new_source_root
= Some(value
.to_string()),
217 "target_data_layout" => target_data_layout
= Some(value
.to_string()),
218 _
=> panic
!("bad component: {component:?}"),
222 for prelude_dep
in extern_prelude
.iter().flatten() {
224 deps
.contains(prelude_dep
),
225 "extern-prelude {extern_prelude:?} must be a subset of deps {deps:?}"
239 introduce_new_source_root
,
246 fn has_flag(&self, flag
: &str) -> bool
{
247 self.activated_flags
.iter().any(|it
| it
== flag
)
251 fn assert_valid_flag(&self, flag
: &str) {
252 if !self.valid_flags
.iter().any(|it
| it
== flag
) {
253 panic
!("invalid flag: {flag:?}, valid flags: {:?}", self.valid_flags
);
257 fn parse(line
: &str) -> MiniCore
{
258 let mut res
= MiniCore { activated_flags: Vec::new(), valid_flags: Vec::new() }
;
260 let line
= line
.strip_prefix("//- minicore:").unwrap().trim();
261 for entry
in line
.split(", ") {
262 if res
.has_flag(entry
) {
263 panic
!("duplicate minicore flag: {entry:?}");
265 res
.activated_flags
.push(entry
.to_owned());
271 /// Strips parts of minicore.rs which are flagged by inactive flags.
273 /// This is probably over-engineered to support flags dependencies.
274 pub fn source_code(mut self) -> String
{
275 let mut buf
= String
::new();
276 let raw_mini_core
= include_str
!("./minicore.rs");
277 let mut lines
= raw_mini_core
.split_inclusive('
\n'
);
279 let mut implications
= Vec
::new();
281 // Parse `//!` preamble and extract flags and dependencies.
282 let trim_doc
: fn(&str) -> Option
<&str> = |line
| match line
.strip_prefix("//!") {
283 Some(it
) => Some(it
),
285 assert
!(line
.trim().is_empty(), "expected empty line after minicore header");
292 .skip_while(|line
| !line
.contains("Available flags:"))
295 let (flag
, deps
) = line
.split_once('
:'
).unwrap();
296 let flag
= flag
.trim();
298 self.valid_flags
.push(flag
.to_string());
301 .zip(deps
.split(", ").map(str::trim
).filter(|dep
| !dep
.is_empty())),
305 for (_
, dep
) in &implications
{
306 self.assert_valid_flag(dep
);
309 for flag
in &self.activated_flags
{
310 self.assert_valid_flag(flag
);
313 // Fixed point loop to compute transitive closure of flags.
315 let mut changed
= false;
316 for &(u
, v
) in &implications
{
317 if self.has_flag(u
) && !self.has_flag(v
) {
318 self.activated_flags
.push(v
.to_string());
327 let mut active_regions
= Vec
::new();
328 let mut seen_regions
= Vec
::new();
330 let trimmed
= line
.trim();
331 if let Some(region
) = trimmed
.strip_prefix("// region:") {
332 active_regions
.push(region
);
335 if let Some(region
) = trimmed
.strip_prefix("// endregion:") {
336 let prev
= active_regions
.pop().unwrap();
337 assert_eq
!(prev
, region
, "unbalanced region pairs");
341 let mut line_region
= false;
342 if let Some(idx
) = trimmed
.find("// :") {
344 active_regions
.push(&trimmed
[idx
+ "// :".len()..]);
348 for ®ion
in &active_regions
{
349 assert
!(!region
.starts_with(' '
), "region marker starts with a space: {region:?}");
350 self.assert_valid_flag(region
);
351 seen_regions
.push(region
);
352 keep
&= self.has_flag(region
);
359 active_regions
.pop().unwrap();
363 for flag
in &self.valid_flags
{
364 if !seen_regions
.iter().any(|it
| it
== flag
) {
365 panic
!("unused minicore flag: {flag:?}");
374 fn parse_fixture_checks_further_indented_metadata() {
388 fn parse_fixture_gets_full_meta() {
389 let (mini_core
, proc_macros
, parsed
) = Fixture
::parse(
391 //- proc_macros: identity
392 //- minicore: coerce_unsized
393 //- /lib.rs crate:foo deps:bar,baz cfg:foo=a,bar=b,atom env:OUTDIR=path/to,OTHER=foo
397 assert_eq
!(proc_macros
, vec
!["identity".to_string()]);
398 assert_eq
!(mini_core
.unwrap().activated_flags
, vec
!["coerce_unsized".to_string()]);
399 assert_eq
!(1, parsed
.len());
401 let meta
= &parsed
[0];
402 assert_eq
!("mod m;\n", meta
.text
);
404 assert_eq
!("foo", meta
.krate
.as_ref().unwrap());
405 assert_eq
!("/lib.rs", meta
.path
);
406 assert_eq
!(2, meta
.env
.len());