Skip to content

Commit e5078c5

Browse files
authored
fix(doc): reuse solar sema compiler (#11980)
* fix(doc): reuse solar sema compiler * fix: include only fn sig in docs * proper format docs, remove extra indent & append ; at enf of fn * changes after review: use get_ast_source, strip prefix always
1 parent 069d082 commit e5078c5

File tree

3 files changed

+164
-135
lines changed

3 files changed

+164
-135
lines changed

crates/doc/src/builder.rs

Lines changed: 147 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ impl DocBuilder {
9494
}
9595

9696
/// Parse the sources and build the documentation.
97-
pub fn build(self) -> eyre::Result<()> {
97+
pub fn build(self, compiler: &mut solar::sema::Compiler) -> eyre::Result<()> {
9898
fs::create_dir_all(self.root.join(&self.config.out))
9999
.wrap_err("failed to create output directory")?;
100100

@@ -124,135 +124,149 @@ impl DocBuilder {
124124
.collect::<Vec<_>>();
125125

126126
let out_dir = self.out_dir()?;
127-
let documents = combined_sources
128-
.par_iter()
129-
.enumerate()
130-
.map(|(i, (path, from_library))| {
131-
let path = *path;
132-
let from_library = *from_library;
133-
134-
// Read and parse source file
135-
let source = fs::read_to_string(path)?;
136-
let source =
137-
forge_fmt::format(&source, self.fmt.clone()).into_result().unwrap_or(source);
138-
139-
let (mut source_unit, comments) = match solang_parser::parse(&source, i) {
140-
Ok(res) => res,
141-
Err(err) => {
142-
if from_library {
143-
// Ignore failures for library files
144-
return Ok(Vec::new());
145-
} else {
146-
return Err(eyre::eyre!(
147-
"Failed to parse Solidity code for {}\nDebug info: {:?}",
148-
path.display(),
149-
err
150-
));
127+
let documents = compiler.enter_mut(|compiler| -> eyre::Result<Vec<Vec<Document>>> {
128+
let gcx = compiler.gcx();
129+
let documents = combined_sources
130+
.par_iter()
131+
.enumerate()
132+
.map(|(i, (path, from_library))| {
133+
let path = *path;
134+
let from_library = *from_library;
135+
let mut files = vec![];
136+
137+
// Read and parse source file
138+
if let Some((_, ast)) = gcx.get_ast_source(path)
139+
&& let Some(source) =
140+
forge_fmt::format_ast(gcx, ast, self.fmt.clone().into())
141+
{
142+
let (mut source_unit, comments) = match solang_parser::parse(&source, i) {
143+
Ok(res) => res,
144+
Err(err) => {
145+
if from_library {
146+
// Ignore failures for library files
147+
return Ok(files);
148+
} else {
149+
return Err(eyre::eyre!(
150+
"Failed to parse Solidity code for {}\nDebug info: {:?}",
151+
path.display(),
152+
err
153+
));
154+
}
155+
}
156+
};
157+
158+
// Visit the parse tree
159+
let mut doc = Parser::new(comments, source);
160+
source_unit
161+
.visit(&mut doc)
162+
.map_err(|err| eyre::eyre!("Failed to parse source: {err}"))?;
163+
164+
// Split the parsed items on top-level constants and rest.
165+
let (items, consts): (Vec<ParseItem>, Vec<ParseItem>) = doc
166+
.items()
167+
.into_iter()
168+
.partition(|item| !matches!(item.source, ParseSource::Variable(_)));
169+
170+
// Attempt to group overloaded top-level functions
171+
let mut remaining = Vec::with_capacity(items.len());
172+
let mut funcs: HashMap<String, Vec<ParseItem>> = HashMap::default();
173+
for item in items {
174+
if matches!(item.source, ParseSource::Function(_)) {
175+
funcs.entry(item.source.ident()).or_default().push(item);
176+
} else {
177+
// Put the item back
178+
remaining.push(item);
179+
}
151180
}
152-
}
153-
};
154-
155-
// Visit the parse tree
156-
let mut doc = Parser::new(comments, source);
157-
source_unit
158-
.visit(&mut doc)
159-
.map_err(|err| eyre::eyre!("Failed to parse source: {err}"))?;
160-
161-
// Split the parsed items on top-level constants and rest.
162-
let (items, consts): (Vec<ParseItem>, Vec<ParseItem>) = doc
163-
.items()
164-
.into_iter()
165-
.partition(|item| !matches!(item.source, ParseSource::Variable(_)));
166-
167-
// Attempt to group overloaded top-level functions
168-
let mut remaining = Vec::with_capacity(items.len());
169-
let mut funcs: HashMap<String, Vec<ParseItem>> = HashMap::default();
170-
for item in items {
171-
if matches!(item.source, ParseSource::Function(_)) {
172-
funcs.entry(item.source.ident()).or_default().push(item);
173-
} else {
174-
// Put the item back
175-
remaining.push(item);
176-
}
177-
}
178-
let (items, overloaded): (
179-
HashMap<String, Vec<ParseItem>>,
180-
HashMap<String, Vec<ParseItem>>,
181-
) = funcs.into_iter().partition(|(_, v)| v.len() == 1);
182-
remaining.extend(items.into_iter().flat_map(|(_, v)| v));
183-
184-
// Each regular item will be written into its own file.
185-
let mut files = remaining
186-
.into_iter()
187-
.map(|item| {
188-
let relative_path = path.strip_prefix(&self.root)?.join(item.filename());
189-
190-
let target_path = out_dir.join(Self::SRC).join(relative_path);
191-
let ident = item.source.ident();
192-
Ok(Document::new(
193-
path.clone(),
194-
target_path,
195-
from_library,
196-
self.config.out.clone(),
197-
)
198-
.with_content(DocumentContent::Single(item), ident))
199-
})
200-
.collect::<eyre::Result<Vec<_>>>()?;
201-
202-
// If top-level constants exist, they will be written to the same file.
203-
if !consts.is_empty() {
204-
let filestem = path.file_stem().and_then(|stem| stem.to_str());
205-
206-
let filename = {
207-
let mut name = "constants".to_owned();
208-
if let Some(stem) = filestem {
209-
name.push_str(&format!(".{stem}"));
181+
let (items, overloaded): (
182+
HashMap<String, Vec<ParseItem>>,
183+
HashMap<String, Vec<ParseItem>>,
184+
) = funcs.into_iter().partition(|(_, v)| v.len() == 1);
185+
remaining.extend(items.into_iter().flat_map(|(_, v)| v));
186+
187+
// Each regular item will be written into its own file.
188+
files = remaining
189+
.into_iter()
190+
.map(|item| {
191+
let relative_path =
192+
path.strip_prefix(&self.root)?.join(item.filename());
193+
194+
let target_path = out_dir.join(Self::SRC).join(relative_path);
195+
let ident = item.source.ident();
196+
Ok(Document::new(
197+
path.clone(),
198+
target_path,
199+
from_library,
200+
self.config.out.clone(),
201+
)
202+
.with_content(DocumentContent::Single(item), ident))
203+
})
204+
.collect::<eyre::Result<Vec<_>>>()?;
205+
206+
// If top-level constants exist, they will be written to the same file.
207+
if !consts.is_empty() {
208+
let filestem = path.file_stem().and_then(|stem| stem.to_str());
209+
210+
let filename = {
211+
let mut name = "constants".to_owned();
212+
if let Some(stem) = filestem {
213+
name.push_str(&format!(".{stem}"));
214+
}
215+
name.push_str(".md");
216+
name
217+
};
218+
let relative_path = path.strip_prefix(&self.root)?.join(filename);
219+
let target_path = out_dir.join(Self::SRC).join(relative_path);
220+
221+
let identity = match filestem {
222+
Some(stem) if stem.to_lowercase().contains("constants") => {
223+
stem.to_owned()
224+
}
225+
Some(stem) => format!("{stem} constants"),
226+
None => "constants".to_owned(),
227+
};
228+
229+
files.push(
230+
Document::new(
231+
path.clone(),
232+
target_path,
233+
from_library,
234+
self.config.out.clone(),
235+
)
236+
.with_content(DocumentContent::Constants(consts), identity),
237+
)
210238
}
211-
name.push_str(".md");
212-
name
213-
};
214-
let relative_path = path.strip_prefix(&self.root)?.join(filename);
215-
let target_path = out_dir.join(Self::SRC).join(relative_path);
216239

217-
let identity = match filestem {
218-
Some(stem) if stem.to_lowercase().contains("constants") => stem.to_owned(),
219-
Some(stem) => format!("{stem} constants"),
220-
None => "constants".to_owned(),
240+
// If overloaded functions exist, they will be written to the same file
241+
if !overloaded.is_empty() {
242+
for (ident, funcs) in overloaded {
243+
let filename =
244+
funcs.first().expect("no overloaded functions").filename();
245+
let relative_path = path.strip_prefix(&self.root)?.join(filename);
246+
247+
let target_path = out_dir.join(Self::SRC).join(relative_path);
248+
files.push(
249+
Document::new(
250+
path.clone(),
251+
target_path,
252+
from_library,
253+
self.config.out.clone(),
254+
)
255+
.with_content(
256+
DocumentContent::OverloadedFunctions(funcs),
257+
ident,
258+
),
259+
);
260+
}
261+
}
221262
};
222263

223-
files.push(
224-
Document::new(
225-
path.clone(),
226-
target_path,
227-
from_library,
228-
self.config.out.clone(),
229-
)
230-
.with_content(DocumentContent::Constants(consts), identity),
231-
)
232-
}
264+
Ok(files)
265+
})
266+
.collect::<eyre::Result<Vec<_>>>()?;
233267

234-
// If overloaded functions exist, they will be written to the same file
235-
if !overloaded.is_empty() {
236-
for (ident, funcs) in overloaded {
237-
let filename = funcs.first().expect("no overloaded functions").filename();
238-
let relative_path = path.strip_prefix(&self.root)?.join(filename);
239-
240-
let target_path = out_dir.join(Self::SRC).join(relative_path);
241-
files.push(
242-
Document::new(
243-
path.clone(),
244-
target_path,
245-
from_library,
246-
self.config.out.clone(),
247-
)
248-
.with_content(DocumentContent::OverloadedFunctions(funcs), ident),
249-
);
250-
}
251-
}
252-
253-
Ok(files)
254-
})
255-
.collect::<eyre::Result<Vec<_>>>()?;
268+
Ok(documents)
269+
})?;
256270

257271
// Flatten results and apply preprocessors to files
258272
let documents = self
@@ -262,15 +276,17 @@ impl DocBuilder {
262276
p.preprocess(docs)
263277
})?;
264278

265-
// Sort the results
266-
let documents = documents.into_iter().sorted_by(|doc1, doc2| {
267-
doc1.item_path.display().to_string().cmp(&doc2.item_path.display().to_string())
268-
});
279+
// Sort the results and filter libraries.
280+
let documents = documents
281+
.into_iter()
282+
.sorted_by(|doc1, doc2| {
283+
doc1.item_path.display().to_string().cmp(&doc2.item_path.display().to_string())
284+
})
285+
.filter(|d| !d.from_library || self.include_libraries)
286+
.collect_vec();
269287

270288
// Write mdbook related files
271-
self.write_mdbook(
272-
documents.filter(|d| !d.from_library || self.include_libraries).collect_vec(),
273-
)?;
289+
self.write_mdbook(documents)?;
274290

275291
// Build the book if requested
276292
if self.should_build {

crates/doc/src/parser/item.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,19 @@ impl ParseItem {
8181
///
8282
/// The parameter should be the full source file where this parse item originated from.
8383
pub fn with_code(mut self, source: &str) -> Self {
84-
self.code = source[self.source.range()].to_string();
84+
let mut code = source[self.source.range()].to_string();
85+
86+
// Special function case, add `;` at the end of definition.
87+
if let ParseSource::Function(_) = self.source {
88+
code.push(';');
89+
}
90+
91+
// Remove extra indent from source lines.
92+
self.code = code
93+
.lines()
94+
.map(|line| line.strip_prefix(" ").unwrap_or(line))
95+
.collect::<Vec<_>>()
96+
.join("\n");
8597
self
8698
}
8799

@@ -166,7 +178,7 @@ impl ParseSource {
166178
Self::Error(error) => error.loc,
167179
Self::Struct(structure) => structure.loc,
168180
Self::Enum(enumerable) => enumerable.loc,
169-
Self::Function(func) => func.loc,
181+
Self::Function(func) => func.loc_prototype,
170182
Self::Type(ty) => ty.loc,
171183
}
172184
.range()

crates/forge/src/cmd/doc/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ impl DocArgs {
7171
let root = &config.root;
7272
let project = config.project()?;
7373
let compiler = ProjectCompiler::new().quiet(true);
74-
let _output = compiler.compile(&project)?;
74+
let mut output = compiler.compile(&project)?;
75+
let compiler = output.parser_mut().solc_mut().compiler_mut();
7576

7677
let mut doc_config = config.doc;
7778
if let Some(out) = self.out {
@@ -118,7 +119,7 @@ impl DocArgs {
118119
builder = builder.with_preprocessor(Deployments { root: root.clone(), deployments });
119120
}
120121

121-
builder.build()?;
122+
builder.build(compiler)?;
122123

123124
if self.serve {
124125
Server::new(doc_config.out)

0 commit comments

Comments
 (0)