Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VSCode shows documentation by package #1740

Merged
merged 15 commits into from
Aug 20, 2024
164 changes: 140 additions & 24 deletions compiler/qsc_doc_gen/src/generate_docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ use std::rc::Rc;
use std::sync::Arc;

type Files = Vec<(Arc<str>, Arc<str>, Arc<str>)>;
type FilesWithMetadata = Vec<(Arc<Metadata>, Arc<str>, Arc<str>)>;

/// Represents an immutable compilation state.
#[derive(Debug)]
struct Compilation {
/// Package store, containing the current package and all its dependencies.
package_store: PackageStore,
/// Current package id when provided.
current_package_id: Option<PackageId>,
/// Aliases for packages.
dependencies: FxHashMap<PackageId, Arc<str>>,
}

impl Compilation {
Expand All @@ -38,6 +43,9 @@ impl Compilation {
let actual_capabilities = capabilities.unwrap_or_default();
let actual_language_features = language_features.unwrap_or_default();

let mut current_package_id: Option<PackageId> = None;
let mut package_aliases: FxHashMap<PackageId, Arc<str>> = FxHashMap::default();

let package_store =
if let Some((mut package_store, dependencies, sources)) = additional_program {
let unit = compile(
Expand All @@ -51,7 +59,13 @@ impl Compilation {
// documentation we can produce. In future we may consider
// displaying the fact of error presence on documentation page.

package_store.insert(unit);
for (package_id, package_alias) in dependencies {
if let Some(package_alias) = package_alias {
package_aliases.insert(*package_id, package_alias.clone());
}
}

current_package_id = Some(package_store.insert(unit));
package_store
} else {
let mut package_store = PackageStore::new(compile::core());
Expand All @@ -60,7 +74,11 @@ impl Compilation {
package_store
};

Self { package_store }
Self {
package_store,
current_package_id,
dependencies: package_aliases,
}
}
}

Expand Down Expand Up @@ -140,51 +158,110 @@ pub fn generate_docs(
language_features: Option<LanguageFeatures>,
) -> Files {
let compilation = Compilation::new(additional_sources, capabilities, language_features);
let mut files: Files = vec![];
let mut files: FilesWithMetadata = vec![];

let display = &CodeDisplay {
compilation: &compilation,
};

let mut toc: FxHashMap<Rc<str>, Vec<String>> = FxHashMap::default();
for (_, unit) in &compilation.package_store {

for (package_id, unit) in &compilation.package_store {
let is_current_package = compilation.current_package_id == Some(package_id);
let package_kind;
match package_id {
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
// Core package is always included in the compilation.
PackageId::CORE => package_kind = PackageKind::Core,

// Standard package is currently always included, but this isn't enforced by the compiler.
_ if package_id == 1.into() => package_kind = PackageKind::StandardLibrary,

// This package could be user code if current package is specified.
_ if is_current_package => {
package_kind = PackageKind::UserCode;
}

_ => {
if let Some(alias) = compilation.dependencies.get(&package_id) {
// This is a direct dependency of the user code.
package_kind = PackageKind::AliasedPackage(alias.to_string());
} else {
// This is not a package user can access (an indirect dependency).
continue;
}
}
}

let package = &unit.package;
for (_, item) in &package.items {
if let Some((ns, line)) = generate_doc_for_item(package, item, display, &mut files) {
if let Some((ns, line)) = generate_doc_for_item(
package,
package_kind.clone(),
is_current_package,
item,
display,
&mut files,
) {
toc.entry(ns).or_default().push(line);
}
}
}

generate_toc(&mut toc, &mut files);
// We want to sort documentation files in a meaningful way.
// First, we want to put files for the current project, if it exists
// Then we want to put explicit dependencies of the current project, if they exist
// Then we want to add built-in std package. And finally built-in core package.
// Namespaces within packages should be sorted alphabetically and
// items with a namespace should be also sorted alphabetically.
// Also, items without any metadata (table of content) should come last
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
files.sort_by_key(|file| {
(
file.0.package.clone(),
file.0.namespace.clone(),
file.0.name.clone(),
)
});

let mut result: Files = files
.into_iter()
.map(|(metadata, name, content)| (name, Arc::from(metadata.to_string().as_str()), content))
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved
.collect();

generate_toc(&mut toc, &mut result);

files
result
}

fn generate_doc_for_item<'a>(
package: &'a Package,
package_kind: PackageKind,
include_internals: bool,
item: &'a Item,
display: &'a CodeDisplay,
files: &mut Files,
files: &mut FilesWithMetadata,
) -> Option<(Rc<str>, String)> {
// Filter items
if item.visibility == Visibility::Internal || matches!(item.kind, ItemKind::Namespace(_, _)) {
if !include_internals && (item.visibility == Visibility::Internal) {
return None;
}
if matches!(item.kind, ItemKind::Namespace(_, _)) {
return None;
}

// Get namespace for item
let ns = get_namespace(package, item)?;

// Add file
let (metadata, content) = generate_file(&ns, item, display)?;
let (metadata, content) = generate_file(package_kind, &ns, item, display)?;
let file_name: Arc<str> = Arc::from(format!("{ns}/{}.md", metadata.name).as_str());
let file_metadata: Arc<str> = Arc::from(metadata.to_string().as_str());
let file_content: Arc<str> = Arc::from(content.as_str());
files.push((file_name, file_metadata, file_content));

// Create toc line
let line = format!(" - {{name: {}, uid: {}}}", metadata.name, metadata.uid);

let met: Arc<Metadata> = Arc::from(metadata);
files.push((met, file_name, file_content));

// Return (ns, line)
Some((ns.clone(), line))
}
Expand All @@ -211,18 +288,21 @@ fn get_namespace(package: &Package, item: &Item) -> Option<Rc<str>> {
}
}

fn generate_file(ns: &Rc<str>, item: &Item, display: &CodeDisplay) -> Option<(Metadata, String)> {
let metadata = get_metadata(ns.clone(), item, display)?;
fn generate_file(
package_kind: PackageKind,
ns: &Rc<str>,
item: &Item,
display: &CodeDisplay,
) -> Option<(Metadata, String)> {
let metadata = get_metadata(package_kind, ns.clone(), item, display)?;

let doc = increase_header_level(&item.doc);
let title = &metadata.title;
let title = format!("{} {}", metadata.fully_qualified_name(), metadata.kind);
let sig = &metadata.signature;

let content = format!(
"# {title}

Namespace: {ns}

```qsharp
{sig}
```
Expand All @@ -243,12 +323,35 @@ struct Metadata {
title: String,
topic: String,
kind: MetadataKind,
package: PackageKind,
namespace: Rc<str>,
name: Rc<str>,
summary: String,
signature: String,
}

impl Metadata {
fn fully_qualified_name(&self) -> String {
let mut buf = if let PackageKind::AliasedPackage(ref package_alias) = self.package {
vec![format!("{package_alias}")]
} else {
vec![]
};

buf.push(self.namespace.to_string());
buf.push(self.name.to_string());
buf.join(".")
}
}

#[derive(PartialOrd, Ord, Eq, PartialEq, Clone)]
enum PackageKind {
UserCode,
AliasedPackage(String),
StandardLibrary,
Core,
}

impl Display for Metadata {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let kind = match &self.kind {
Expand Down Expand Up @@ -281,7 +384,24 @@ enum MetadataKind {
Export,
}

fn get_metadata(ns: Rc<str>, item: &Item, display: &CodeDisplay) -> Option<Metadata> {
impl Display for MetadataKind {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let s = match &self {
MetadataKind::Function => "function",
MetadataKind::Operation => "operation",
MetadataKind::Udt => "user defined type",
MetadataKind::Export => "exported item",
};
write!(f, "{s}")
}
}

fn get_metadata(
package_kind: PackageKind,
ns: Rc<str>,
item: &Item,
display: &CodeDisplay,
) -> Option<Metadata> {
let (name, signature, kind) = match &item.kind {
ItemKind::Callable(decl) => Some((
decl.name.name.clone(),
Expand Down Expand Up @@ -311,14 +431,10 @@ fn get_metadata(ns: Rc<str>, item: &Item, display: &CodeDisplay) -> Option<Metad

Some(Metadata {
uid: format!("Qdk.{ns}.{name}"),
title: match &kind {
MetadataKind::Function => format!("{name} function"),
MetadataKind::Operation => format!("{name} operation"),
MetadataKind::Udt => format!("{name} user defined type"),
MetadataKind::Export => format!("{name} exported item"),
},
title: format!("{name} {kind}"),
topic: "managed-reference".to_string(),
kind,
package: package_kind,
namespace: ns,
name,
summary,
Expand Down
4 changes: 1 addition & 3 deletions compiler/qsc_doc_gen/src/generate_docs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ fn docs_generation() {
qsharp.summary: "Returns the number of elements in the input array `a`."
---

# Length function

Namespace: Microsoft.Quantum.Core
# Microsoft.Quantum.Core.Length function
DmitryVasilevsky marked this conversation as resolved.
Show resolved Hide resolved

```qsharp
function Length<'T>(a : 'T[]) : Int
Expand Down
Loading