Skip to content

Commit

Permalink
Docs Gen Tool Updates (#2012)
Browse files Browse the repository at this point in the history
Many updates to the docs gen tool:
- Generates docs for `@Config(Unrestricted)` items.
- Moves Unstable namespaces to the bottom of the Table of Contents.
- Generates docs for reexported Core items.
- Generates index.md files for namespaces.
- Generates a top-level index.md file.
- Trims datetime to just be the date.
- Adds description metadata field.

fixes #1998
fixes #1866
  • Loading branch information
ScottCarda-MS authored Nov 13, 2024
1 parent 093ccfe commit 61a4720
Show file tree
Hide file tree
Showing 9 changed files with 618 additions and 183 deletions.
562 changes: 393 additions & 169 deletions compiler/qsc_doc_gen/src/generate_docs.rs

Large diffs are not rendered by default.

167 changes: 165 additions & 2 deletions compiler/qsc_doc_gen/src/generate_docs/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ use super::generate_docs;
use expect_test::expect;

#[test]
fn docs_generation() {
fn generates_standard_item() {
let files = generate_docs(None, None, None);
let (_, metadata, contents) = files
.iter()
.find(|(file_name, _, _)| &**file_name == "Std.Core/Length.md")
.expect("Could not file doc file for Length");
.expect("Could not find doc file for Length");
let full_contents = format!("{metadata}\n\n{contents}");

expect![[r#"
---
uid: Qdk.Std.Core.Length
title: Length function
description: "Q# Length function: Returns the number of elements in the input array `a`."
ms.date: {TIMESTAMP}
ms.topic: managed-reference
qsharp.kind: function
Expand Down Expand Up @@ -51,3 +52,165 @@ fn docs_generation() {
"#]]
.assert_eq(full_contents.as_str());
}

#[test]
fn generates_unrestricted_item() {
let files = generate_docs(None, None, None);
let (_, metadata, contents) = files
.iter()
.find(|(file_name, _, _)| &**file_name == "Std.Diagnostics/CheckZero.md")
.expect("Could not file doc file for CheckZero");
let full_contents = format!("{metadata}\n\n{contents}");

expect![[r#"
---
uid: Qdk.Std.Diagnostics.CheckZero
title: CheckZero operation
description: "Q# CheckZero operation: Checks whether a qubit is in the \|0⟩ state, returning true if it is."
ms.date: {TIMESTAMP}
ms.topic: managed-reference
qsharp.kind: operation
qsharp.package: __Std__
qsharp.namespace: Std.Diagnostics
qsharp.name: CheckZero
qsharp.summary: "Checks whether a qubit is in the \|0⟩ state, returning true if it is."
---
# CheckZero operation
Fully qualified name: Std.Diagnostics.CheckZero
```qsharp
operation CheckZero(qubit : Qubit) : Bool
```
## Summary
Checks whether a qubit is in the |0⟩ state, returning true if it is.
## Description
This operation checks whether a qubit is in the |0⟩ state. It will return true only
if the qubit is deterministically in the |0⟩ state, and will return false otherwise. This operation
does not change the state of the qubit.
## Input
### qubit
The qubit to check.
## Output
True if the qubit is in the |0⟩ state, false otherwise.
## Remarks
This operation is useful for checking whether a qubit is in the |0⟩ state during simulation. It is not possible to check
this on hardware without measuring the qubit, which could change the state.
"#]]
.assert_eq(full_contents.as_str());
}

#[test]
fn redirect_generation() {
let files = generate_docs(None, None, None);
let (_, metadata, contents) = files
.iter()
.find(|(file_name, _, _)| &**file_name == "Microsoft.Quantum.Core/Length.md")
.expect("Could not find doc file for Length");
let full_contents = format!("{metadata}\n\n{contents}");

expect![[r#"
---
uid: Qdk.Microsoft.Quantum.Core.Length
title: Length exported item
description: "Q# Length exported item: This is an exported item. The actual definition is found here: [Length](xref:Qdk.Std.Core.Length)"
ms.date: {TIMESTAMP}
ms.topic: managed-reference
qsharp.kind: export
qsharp.package: __Std__
qsharp.namespace: Microsoft.Quantum.Core
qsharp.name: Length
qsharp.summary: "This is an exported item. The actual definition is found here: [Length](xref:Qdk.Std.Core.Length)"
---
# Length exported item
Fully qualified name: Microsoft.Quantum.Core.Length
This is an exported item. The actual definition is found here: [Length](xref:Qdk.Std.Core.Length)
"#]]
.assert_eq(full_contents.as_str());
}

#[test]
fn index_file_generation() {
let files = generate_docs(None, None, None);
let (_, metadata, contents) = files
.iter()
.find(|(file_name, _, _)| &**file_name == "Std.Core/index.md")
.expect("Could not find Std.Core Table of Contents file");
let full_contents = format!("{metadata}\n\n{contents}");

expect![[r#"
---
uid: Qdk.Std.Core-toc
title: Std.Core namespace
description: Table of contents for the Q# Core namespace
author: {AUTHOR}
ms.author: {MS_AUTHOR}
ms.date: {TIMESTAMP}
ms.topic: landing-page
---
# Std.Core
The Std.Core namespace contains the following items:
| Name | Description |
|------|-------------|
| [Length](xref:Qdk.Std.Core.Length) | Returns the number of elements in the input array `a`. |
| [Repeated](xref:Qdk.Std.Core.Repeated) | Creates an array of given `length` with all elements equal to given `value`. `length` must be a non-negative integer. |
"#]]
.assert_eq(full_contents.as_str());
}

#[test]
fn top_index_file_generation() {
let files = generate_docs(None, None, None);
let (_, metadata, contents) = files
.iter()
.find(|(file_name, _, _)| &**file_name == "index.md")
.expect("Could not find top-level Table of Contents file");
let full_contents = format!("{metadata}\n\n{contents}");

expect![[r#"
---
uid: Microsoft.Quantum.apiref-toc
title: Q# standard libraries for the Azure Quantum Development Kit
description: Table of contents for the Q# standard libraries for Azure Quantum Development Kit
author: {AUTHOR}
ms.author: {MS_AUTHOR}
ms.date: {TIMESTAMP}
ms.topic: landing-page
---
# Q# standard library
The Q# standard library contains the following namespaces:
| Namespace | Description |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| [`Microsoft.Quantum.Core`](xref:Qdk.Microsoft.Quantum.Core-toc) | Re-exported functions. |
| [`Std.Arrays`](xref:Qdk.Std.Arrays-toc) | Items for working with arrays. |
| [`Std.Canon`](xref:Qdk.Std.Canon-toc) | Canonical implementations of common classical and quantum utilities.|
| [`Std.Convert`](xref:Qdk.Std.Convert-toc) | Items for converting between different types. |
| [`Std.Core`](xref:Qdk.Std.Core-toc) | Items for language built-in operations. |
| [`Std.Diagnostics`](xref:Qdk.Std.Diagnostics-toc) | Items for debugging and testing quantum programs. |
| [`Std.Intrinsic`](xref:Qdk.Std.Intrinsic-toc) | Items that provide core quantum operations. |
| [`Std.Logical`](xref:Qdk.Std.Logical-toc) | Boolean Logic functions. |
| [`Std.Math`](xref:Qdk.Std.Math-toc) | Items for classical math operations. |
| [`Std.Measurement`](xref:Qdk.Std.Measurement-toc) | Items for measuring quantum results. |
| [`Std.Random`](xref:Qdk.Std.Random-toc) | Items for creating random values. |
| [`Std.Range`](xref:Qdk.Std.Range-toc) | Items for working with ranges. |
| [`Std.ResourceEstimation`](xref:Qdk.Std.ResourceEstimation-toc) | Items for working with the Azure Quantum Resource Estimator. |
| [`Microsoft.Quantum.Unstable.Arithmetic`](xref:Qdk.Microsoft.Quantum.Unstable.Arithmetic-toc) | Items for working with quantum arithmetic operations. |
| [`Microsoft.Quantum.Unstable.StatePreparation`](xref:Qdk.Microsoft.Quantum.Unstable.StatePreparation-toc) | Items for preparing a quantum state. |
| [`Microsoft.Quantum.Unstable.TableLookup`](xref:Qdk.Microsoft.Quantum.Unstable.TableLookup-toc) | Items for performing quantum table lookups. |
"#]]
.assert_eq(full_contents.as_str());
}
1 change: 1 addition & 0 deletions compiler/qsc_doc_gen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@

pub mod display;
pub mod generate_docs;
mod table_of_contents;
31 changes: 31 additions & 0 deletions compiler/qsc_doc_gen/src/table_of_contents.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/// Returns the string of the table of contents for the standard library.
/// This table of contents will be the content of the top-level index.md
/// created during documentation generation.
pub(super) fn table_of_contents() -> String {
"# Q# standard library
The Q# standard library contains the following namespaces:
| Namespace | Description |
| --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| [`Microsoft.Quantum.Core`](xref:Qdk.Microsoft.Quantum.Core-toc) | Re-exported functions. |
| [`Std.Arrays`](xref:Qdk.Std.Arrays-toc) | Items for working with arrays. |
| [`Std.Canon`](xref:Qdk.Std.Canon-toc) | Canonical implementations of common classical and quantum utilities.|
| [`Std.Convert`](xref:Qdk.Std.Convert-toc) | Items for converting between different types. |
| [`Std.Core`](xref:Qdk.Std.Core-toc) | Items for language built-in operations. |
| [`Std.Diagnostics`](xref:Qdk.Std.Diagnostics-toc) | Items for debugging and testing quantum programs. |
| [`Std.Intrinsic`](xref:Qdk.Std.Intrinsic-toc) | Items that provide core quantum operations. |
| [`Std.Logical`](xref:Qdk.Std.Logical-toc) | Boolean Logic functions. |
| [`Std.Math`](xref:Qdk.Std.Math-toc) | Items for classical math operations. |
| [`Std.Measurement`](xref:Qdk.Std.Measurement-toc) | Items for measuring quantum results. |
| [`Std.Random`](xref:Qdk.Std.Random-toc) | Items for creating random values. |
| [`Std.Range`](xref:Qdk.Std.Range-toc) | Items for working with ranges. |
| [`Std.ResourceEstimation`](xref:Qdk.Std.ResourceEstimation-toc) | Items for working with the Azure Quantum Resource Estimator. |
| [`Microsoft.Quantum.Unstable.Arithmetic`](xref:Qdk.Microsoft.Quantum.Unstable.Arithmetic-toc) | Items for working with quantum arithmetic operations. |
| [`Microsoft.Quantum.Unstable.StatePreparation`](xref:Qdk.Microsoft.Quantum.Unstable.StatePreparation-toc) | Items for preparing a quantum state. |
| [`Microsoft.Quantum.Unstable.TableLookup`](xref:Qdk.Microsoft.Quantum.Unstable.TableLookup-toc) | Items for performing quantum table lookups. |
".to_string()
}
2 changes: 2 additions & 0 deletions library/std/src/Std/Math.qs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ function AbsD(a : Double) : Double {
a < 0.0 ? -a | a
}

/// # Summary
/// Returns the absolute value of a big integer.
function AbsL(a : BigInt) : BigInt {
a < 0L ? -a | a
}
Expand Down
17 changes: 11 additions & 6 deletions npm/qsharp/generate_docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ var today = new Date();
var dd = String(today.getDate()).padStart(2, "0");
var mm = String(today.getMonth() + 1).padStart(2, "0"); //January is 0!
var yyyy = today.getFullYear();
var today_str = mm + "/" + dd + "/" + yyyy + " 12:00:00 AM";
var today_str = mm + "/" + dd + "/" + yyyy;

docs.forEach((doc) => {
// If the filename contains a /, then we need to create the directory
const parts = doc.filename.split("/");
let fullPath = "";
switch (parts.length) {
case 1:
if (doc.filename !== "toc.yml") {
if (doc.filename !== "toc.yml" && doc.filename !== "index.md") {
throw new Error(`Invalid filename: ${doc.filename}`);
} else {
fullPath = join(docsDirPath, doc.filename);
Expand All @@ -57,10 +57,15 @@ docs.forEach((doc) => {
default:
throw new Error(`Invalid file path: ${doc.filename}`);
}
var contents =
doc.metadata.replace("ms.date: {TIMESTAMP}", `ms.date: ${today_str}`) +
"\n\n" +
doc.contents;
var contents = "";
if (doc.filename === "toc.yml") {
contents = doc.contents;
} else {
contents =
doc.metadata.replace("ms.date: {TIMESTAMP}", `ms.date: ${today_str}`) +
"\n\n" +
doc.contents;
}
writeFileSync(fullPath, contents);
});

Expand Down
3 changes: 3 additions & 0 deletions npm/qsharp/test/basics.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ test("autogenerated documentation", async () => {
var numberOfGoodFiles = 0;
for (const doc of docFiles) {
assert(doc, "Each documentation file should be present.");
if (doc.filename === "index.md") {
continue; // Skip index.md - its contents are added later
}
assert(
doc.contents && doc.contents.length > 10,
"Content for each documentation file should be present.",
Expand Down
7 changes: 5 additions & 2 deletions playground/src/docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ export function processDocumentFiles(
docFiles: IDocFile[],
): Map<string, string> {
const contentByNamespace = new Map<string, string>();
const regex = new RegExp("^qsharp.namespace: Microsoft.Quantum.(.+)$", "m");
const regex = new RegExp(
"^qsharp\\.namespace: (Microsoft\\.Quantum|Std)\\.(.+)$",
"m",
);

for (const doc of docFiles) {
const match = regex.exec(doc.metadata); // Parse namespace out of metadata
Expand All @@ -29,7 +32,7 @@ export function processDocumentFiles(
}
// The next line contains "Zero-width space" unicode character
// to allow line breaks before the period.
const newNamespace = "… " + match[1].replace(".", "​.");
const newNamespace = "… " + match[2].replace(".", "​.");

if (contentByNamespace.has(newNamespace)) {
const existingContent = contentByNamespace.get(newNamespace)!;
Expand Down
11 changes: 7 additions & 4 deletions wasm/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,17 +504,20 @@ fn test_doc_gen() {
let docs = qsc_doc_gen::generate_docs::generate_docs(None, None, None);
assert!(docs.len() > 100);
for (name, metadata, contents) in docs {
// filename will be something like "Microsoft.Quantum.Canon/ApplyToEachC.md"
// filename will be something like "Std.Canon/ApplyToEachC.md"
let filename = name.to_string();
// Text is the full markdown including initial metadata inside '---' blocks
let text = format!("{metadata}\n\n{contents}");
if filename.eq("toc.yml") {
assert!(text.contains("uid: Qdk.Microsoft.Quantum.Core"));
} else {
assert!(text.contains("uid: Qdk.Std.Core"));
} else if !filename.eq("index.md") {
assert!(std::path::Path::new(&filename)
.extension()
.map_or(false, |ext| ext.eq_ignore_ascii_case("md")));
assert!(text.starts_with("---\n"));
assert!(
text.starts_with("---\n"),
"file {name} does not start with metadata\ncontents: {text}"
);
}
}
}
Expand Down

0 comments on commit 61a4720

Please sign in to comment.