microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
alex/pythontelem

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

compiler/qsc_doc_gen/src/generate_docs.rs

472lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#[cfg(test)]
5mod tests;
6
7use crate::display::{increase_header_level, parse_doc_for_summary};
8use crate::display::{CodeDisplay, Lookup};
9use qsc_ast::ast;
10use qsc_data_structures::language_features::LanguageFeatures;
11use qsc_data_structures::target::TargetCapabilityFlags;
12use qsc_frontend::compile::{self, compile, Dependencies, PackageStore, SourceMap};
13use qsc_frontend::resolve;
14use qsc_hir::hir::{CallableKind, Item, ItemKind, Package, PackageId, Visibility};
15use qsc_hir::{hir, ty};
16use rustc_hash::FxHashMap;
17use std::fmt::{Display, Formatter, Result};
18use std::rc::Rc;
19use std::sync::Arc;
20
21// Name, Metadata, Content
22type Files = Vec<(Arc<str>, Arc<str>, Arc<str>)>;
23type FilesWithMetadata = Vec<(Arc<str>, Arc<Metadata>, Arc<str>)>;
24
25/// Represents an immutable compilation state.
26#[derive(Debug)]
27struct Compilation {
28 /// Package store, containing the current package and all its dependencies.
29 package_store: PackageStore,
30 /// Current package id when provided.
31 current_package_id: Option<PackageId>,
32 /// Aliases for packages.
33 dependencies: FxHashMap<PackageId, Arc<str>>,
34}
35
36impl Compilation {
37 /// Creates a new `Compilation` by compiling standard library
38 /// and additional sources.
39 pub(crate) fn new(
40 additional_program: Option<(PackageStore, &Dependencies, SourceMap)>,
41 capabilities: Option<TargetCapabilityFlags>,
42 language_features: Option<LanguageFeatures>,
43 ) -> Self {
44 let actual_capabilities = capabilities.unwrap_or_default();
45 let actual_language_features = language_features.unwrap_or_default();
46
47 let mut current_package_id: Option<PackageId> = None;
48 let mut package_aliases: FxHashMap<PackageId, Arc<str>> = FxHashMap::default();
49
50 let package_store =
51 if let Some((mut package_store, dependencies, sources)) = additional_program {
52 let unit = compile(
53 &package_store,
54 dependencies,
55 sources,
56 actual_capabilities,
57 actual_language_features,
58 );
59 // We ignore errors here (unit.errors vector) and use whatever
60 // documentation we can produce. In future we may consider
61 // displaying the fact of error presence on documentation page.
62
63 for (package_id, package_alias) in dependencies {
64 if let Some(package_alias) = package_alias {
65 package_aliases.insert(*package_id, package_alias.clone());
66 }
67 }
68
69 current_package_id = Some(package_store.insert(unit));
70 package_store
71 } else {
72 let mut package_store = PackageStore::new(compile::core());
73 let std_unit = compile::std(&package_store, actual_capabilities);
74 package_store.insert(std_unit);
75 package_store
76 };
77
78 Self {
79 package_store,
80 current_package_id,
81 dependencies: package_aliases,
82 }
83 }
84}
85
86impl Lookup for Compilation {
87 fn get_ty(&self, _: ast::NodeId) -> Option<&ty::Ty> {
88 unimplemented!("Not needed for docs generation")
89 }
90
91 fn get_res(&self, _: ast::NodeId) -> Option<&resolve::Res> {
92 unimplemented!("Not needed for docs generation")
93 }
94
95 fn resolve_item_relative_to_user_package(
96 &self,
97 _: &hir::ItemId,
98 ) -> (&hir::Item, &hir::Package, hir::ItemId) {
99 unimplemented!("Not needed for docs generation")
100 }
101
102 /// Returns the hir `Item` node referred to by `res`.
103 /// `Res`s can resolve to external packages, and the references
104 /// are relative, so here we also need the
105 /// local `PackageId` that the `res` itself came from.
106 fn resolve_item_res(
107 &self,
108 local_package_id: PackageId,
109 res: &hir::Res,
110 ) -> (&hir::Item, hir::ItemId) {
111 match res {
112 hir::Res::Item(item_id) => {
113 let (item, _, resolved_item_id) = self.resolve_item(local_package_id, item_id);
114 (item, resolved_item_id)
115 }
116 _ => panic!("expected to find item"),
117 }
118 }
119
120 /// Returns the hir `Item` node referred to by `item_id`.
121 /// `ItemId`s can refer to external packages, and the references
122 /// are relative, so here we also need the local `PackageId`
123 /// that the `ItemId` originates from.
124 fn resolve_item(
125 &self,
126 local_package_id: PackageId,
127 item_id: &hir::ItemId,
128 ) -> (&hir::Item, &hir::Package, hir::ItemId) {
129 // If the `ItemId` contains a package id, use that.
130 // Lack of a package id means the item is in the
131 // same package as the one this `ItemId` reference
132 // came from. So use the local package id passed in.
133 let package_id = item_id.package.unwrap_or(local_package_id);
134 let package = &self
135 .package_store
136 .get(package_id)
137 .expect("package should exist in store")
138 .package;
139 (
140 package
141 .items
142 .get(item_id.item)
143 .expect("item id should exist"),
144 package,
145 hir::ItemId {
146 package: Some(package_id),
147 item: item_id.item,
148 },
149 )
150 }
151}
152
153/// Generates and returns documentation files for the standard library
154/// and additional sources (if specified.)
155#[must_use]
156pub fn generate_docs(
157 additional_sources: Option<(PackageStore, &Dependencies, SourceMap)>,
158 capabilities: Option<TargetCapabilityFlags>,
159 language_features: Option<LanguageFeatures>,
160) -> Files {
161 let compilation = Compilation::new(additional_sources, capabilities, language_features);
162 let mut files: FilesWithMetadata = vec![];
163
164 let display = &CodeDisplay {
165 compilation: &compilation,
166 };
167
168 let mut toc: FxHashMap<Rc<str>, Vec<String>> = FxHashMap::default();
169
170 for (package_id, unit) in &compilation.package_store {
171 let is_current_package = compilation.current_package_id == Some(package_id);
172 let package_kind;
173 if package_id == PackageId::CORE {
174 // Core package is always included in the compilation.
175 package_kind = PackageKind::Core;
176 } else if package_id == 1.into() {
177 // Standard package is currently always included, but this isn't enforced by the compiler.
178 package_kind = PackageKind::StandardLibrary;
179 } else if is_current_package {
180 // This package could be user code if current package is specified.
181 package_kind = PackageKind::UserCode;
182 } else if let Some(alias) = compilation.dependencies.get(&package_id) {
183 // This is a direct dependency of the user code.
184 package_kind = PackageKind::AliasedPackage(alias.to_string());
185 } else {
186 // This is not a package user can access (an indirect dependency).
187 continue;
188 }
189
190 let package = &unit.package;
191 for (_, item) in &package.items {
192 if let Some((ns, line)) = generate_doc_for_item(
193 package,
194 package_kind.clone(),
195 is_current_package,
196 item,
197 display,
198 &mut files,
199 ) {
200 toc.entry(ns).or_default().push(line);
201 }
202 }
203 }
204
205 // We want to sort documentation files in a meaningful way.
206 // First, we want to put files for the current project, if it exists.
207 // Then we want to put explicit dependencies of the current project, if they exist.
208 // Then we want to add built-in std package. And finally built-in core package.
209 // Namespaces within packages should be sorted alphabetically and
210 // items with a namespace should be also sorted alphabetically.
211 // Also, items without any metadata (table of content) should come last.
212 files.sort_by_key(|file| {
213 (
214 file.1.package.clone(),
215 file.1.namespace.clone(),
216 file.1.name.clone(),
217 )
218 });
219
220 let mut result: Files = files
221 .into_iter()
222 .map(|(name, metadata, content)| (name, Arc::from(metadata.to_string().as_str()), content))
223 .collect();
224
225 generate_toc(&mut toc, &mut result);
226
227 result
228}
229
230fn generate_doc_for_item<'a>(
231 package: &'a Package,
232 package_kind: PackageKind,
233 include_internals: bool,
234 item: &'a Item,
235 display: &'a CodeDisplay,
236 files: &mut FilesWithMetadata,
237) -> Option<(Rc<str>, String)> {
238 // Filter items
239 if !include_internals && (item.visibility == Visibility::Internal) {
240 return None;
241 }
242 if matches!(item.kind, ItemKind::Namespace(_, _)) {
243 return None;
244 }
245
246 // Get namespace for item
247 let ns = get_namespace(package, item)?;
248
249 // Add file
250 let (metadata, content) = generate_file(package_kind, &ns, item, display)?;
251 let file_name: Arc<str> = Arc::from(format!("{ns}/{}.md", metadata.name).as_str());
252 let file_content: Arc<str> = Arc::from(content.as_str());
253
254 // Create toc line
255 let line = format!(" - {{name: {}, uid: {}}}", metadata.name, metadata.uid);
256
257 let met: Arc<Metadata> = Arc::from(metadata);
258 files.push((file_name, met, file_content));
259
260 // Return (ns, line)
261 Some((ns.clone(), line))
262}
263
264fn get_namespace(package: &Package, item: &Item) -> Option<Rc<str>> {
265 match item.parent {
266 Some(local_id) => {
267 let parent = package
268 .items
269 .get(local_id)
270 .expect("Could not resolve parent item id");
271 match &parent.kind {
272 ItemKind::Namespace(name, _) => {
273 if name.starts_with("QIR") {
274 None // We ignore "QIR" namespaces
275 } else {
276 Some(name.name())
277 }
278 }
279 _ => None,
280 }
281 }
282 None => None,
283 }
284}
285
286fn generate_file(
287 package_kind: PackageKind,
288 ns: &Rc<str>,
289 item: &Item,
290 display: &CodeDisplay,
291) -> Option<(Metadata, String)> {
292 let metadata = get_metadata(package_kind, ns.clone(), item, display)?;
293
294 let doc = increase_header_level(&item.doc);
295 let title = &metadata.title;
296 let fqn = &metadata.fully_qualified_name();
297 let sig = &metadata.signature;
298
299 let content = format!(
300 "# {title}
301
302Fully qualified name: {fqn}
303
304```qsharp
305{sig}
306```
307"
308 );
309
310 let content = if doc.is_empty() {
311 content
312 } else {
313 format!("{content}\n{doc}\n")
314 };
315
316 Some((metadata, content))
317}
318
319struct Metadata {
320 uid: String,
321 title: String,
322 topic: String,
323 kind: MetadataKind,
324 package: PackageKind,
325 namespace: Rc<str>,
326 name: Rc<str>,
327 summary: String,
328 signature: String,
329}
330
331impl Metadata {
332 fn fully_qualified_name(&self) -> String {
333 let mut buf = if let PackageKind::AliasedPackage(ref package_alias) = self.package {
334 vec![format!("{package_alias}")]
335 } else {
336 vec![]
337 };
338
339 buf.push(self.namespace.to_string());
340 buf.push(self.name.to_string());
341 buf.join(".")
342 }
343}
344
345#[derive(PartialOrd, Ord, Eq, PartialEq, Clone)]
346enum PackageKind {
347 UserCode,
348 AliasedPackage(String),
349 StandardLibrary,
350 Core,
351}
352
353impl Display for Metadata {
354 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
355 let kind = match &self.kind {
356 MetadataKind::Function => "function",
357 MetadataKind::Operation => "operation",
358 MetadataKind::Udt => "udt",
359 MetadataKind::Export => "export",
360 };
361 write!(
362 f,
363 "---
364uid: {}
365title: {}
366ms.date: {{TIMESTAMP}}
367ms.topic: {}
368qsharp.kind: {}
369qsharp.namespace: {}
370qsharp.name: {}
371qsharp.summary: \"{}\"
372---",
373 self.uid, self.title, self.topic, kind, self.namespace, self.name, self.summary
374 )
375 }
376}
377
378enum MetadataKind {
379 Function,
380 Operation,
381 Udt,
382 Export,
383}
384
385impl Display for MetadataKind {
386 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
387 let s = match &self {
388 MetadataKind::Function => "function",
389 MetadataKind::Operation => "operation",
390 MetadataKind::Udt => "user defined type",
391 MetadataKind::Export => "exported item",
392 };
393 write!(f, "{s}")
394 }
395}
396
397fn get_metadata(
398 package_kind: PackageKind,
399 ns: Rc<str>,
400 item: &Item,
401 display: &CodeDisplay,
402) -> Option<Metadata> {
403 let (name, signature, kind) = match &item.kind {
404 ItemKind::Callable(decl) => Some((
405 decl.name.name.clone(),
406 display.hir_callable_decl(decl).to_string(),
407 match &decl.kind {
408 CallableKind::Function => MetadataKind::Function,
409 CallableKind::Operation => MetadataKind::Operation,
410 },
411 )),
412 ItemKind::Ty(ident, udt) => Some((
413 ident.name.clone(),
414 display.hir_udt(udt).to_string(),
415 MetadataKind::Udt,
416 )),
417 ItemKind::Namespace(_, _) => None,
418 ItemKind::Export(name, _) => Some((
419 name.name.clone(),
420 // If we want to show docs for exports, we could do that here.
421 String::new(),
422 MetadataKind::Export,
423 )),
424 }?;
425
426 let summary = parse_doc_for_summary(&item.doc)
427 .replace("\r\n", " ")
428 .replace('\n', " ");
429
430 Some(Metadata {
431 uid: format!("Qdk.{ns}.{name}"),
432 title: format!("{name} {kind}"),
433 topic: "managed-reference".to_string(),
434 kind,
435 package: package_kind,
436 namespace: ns,
437 name,
438 summary,
439 signature,
440 })
441}
442
443/// Generates the Table of Contents file, toc.yml
444fn generate_toc(map: &mut FxHashMap<Rc<str>, Vec<String>>, files: &mut Files) {
445 let header = "
446# This file is automatically generated.
447# Please do not modify this file manually, or your changes will be lost when
448# documentation is rebuilt.";
449 let mut table = map
450 .iter_mut()
451 .map(|(namespace, lines)| {
452 lines.sort_unstable();
453 let items_str = lines.join("\n");
454 let content =
455 format!("- items:\n{items_str}\n name: {namespace}\n uid: Qdk.{namespace}");
456 (namespace, content)
457 })
458 .collect::<Vec<_>>();
459
460 table.sort_unstable_by_key(|(n, _)| *n);
461 let table = table
462 .into_iter()
463 .map(|(_, c)| c)
464 .collect::<Vec<_>>()
465 .join("\n");
466 let content = format!("{header}\n{table}");
467
468 let file_name: Arc<str> = Arc::from("toc.yml");
469 let file_metadata: Arc<str> = Arc::from("");
470 let file_content: Arc<str> = Arc::from(content.as_str());
471 files.push((file_name, file_metadata, file_content));
472}
473