diff --git a/public/api/qiskit-ibm-provider/objects.inv b/public/api/qiskit-ibm-provider/objects.inv new file mode 100644 index 00000000000..9617b8a7740 Binary files /dev/null and b/public/api/qiskit-ibm-provider/objects.inv differ diff --git a/public/api/qiskit-ibm-runtime/objects.inv b/public/api/qiskit-ibm-runtime/objects.inv new file mode 100644 index 00000000000..a0a05740cf7 Binary files /dev/null and b/public/api/qiskit-ibm-runtime/objects.inv differ diff --git a/public/api/qiskit/objects.inv b/public/api/qiskit/objects.inv new file mode 100644 index 00000000000..7acf17226a6 Binary files /dev/null and b/public/api/qiskit/objects.inv differ diff --git a/scripts/commands/checkLinks.ts b/scripts/commands/checkLinks.ts index 3ae6dd495b8..6011b62a66a 100644 --- a/scripts/commands/checkLinks.ts +++ b/scripts/commands/checkLinks.ts @@ -117,8 +117,10 @@ async function determineCurrentDocsFileBatch( ): Promise { const toCheck = [ "docs/**/*.{ipynb,md,mdx}", + "public/api/*/objects.inv", // Ignore historical versions "!docs/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/[0-9]*/*", + "!public/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/[0-9]*/*", ]; const toLoad = [ "docs/api/qiskit/0.44/{algorithms,opflow}.md", @@ -127,7 +129,9 @@ async function determineCurrentDocsFileBatch( ]; if (!args.currentApis) { - toCheck.push("!docs/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/*"); + toCheck.push( + "!{public,docs}/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/*", + ); toLoad.push("docs/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/*"); } @@ -161,7 +165,10 @@ async function determineHistoricalFileBatches( const result = []; for (const folder of historicalFolders) { const fileBatch = await FileBatch.fromGlobs( - [`docs/api/${projectName}/${folder.name}/*`], + [ + `docs/api/${projectName}/${folder.name}/*`, + `public/api/${projectName}/${folder.name}/objects.inv`, + ], toLoad, `${projectName} v${folder.name}`, ); diff --git a/scripts/commands/updateApiDocs.ts b/scripts/commands/updateApiDocs.ts index 02e56c575cd..ee4fd0705c4 100644 --- a/scripts/commands/updateApiDocs.ts +++ b/scripts/commands/updateApiDocs.ts @@ -20,6 +20,7 @@ import yargs from "yargs/yargs"; import { hideBin } from "yargs/helpers"; import transformLinks from "transform-markdown-links"; +import { ObjectsInv } from "../lib/api/objectsInv"; import { sphinxHtmlToMarkdown } from "../lib/api/htmlToMd"; import { saveImages } from "../lib/api/saveImages"; import { generateToc } from "../lib/api/generateToc"; @@ -188,6 +189,7 @@ async function convertHtmlToMarkdown( baseGitHubUrl: string, pkg: Pkg, ) { + const objectsInv = await ObjectsInv.fromFile(htmlPath); const files = await globby( [ "apidocs/**.html", @@ -245,10 +247,11 @@ async function convertHtmlToMarkdown( results = await mergeClassMembers(results); flattenFolders(results); specialCaseResults(results); - await updateLinks(results, pkg.transformLink); + await updateLinks(results, objectsInv, pkg.transformLink); await dedupeHtmlIdsFromResults(results); addFrontMatter(results, pkg); + await objectsInv.write(getPkgRoot(pkg, "public")); for (const result of results) { let path = urlToPath(result.url); diff --git a/scripts/lib/api/objectsInv.test.ts b/scripts/lib/api/objectsInv.test.ts new file mode 100644 index 00000000000..73005372b7c --- /dev/null +++ b/scripts/lib/api/objectsInv.test.ts @@ -0,0 +1,127 @@ +// This code is a Qiskit project. +// +// (C) Copyright IBM 2023. +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +import { describe, expect, test } from "@jest/globals"; +import { ObjectsInv, ObjectsInvEntry } from "./objectsInv"; +import { unlink, stat } from "fs/promises"; + +const TEST_FOLDER = "scripts/lib/api/testdata/"; +const TEMP_FOLDER = "scripts/lib/api/testdata/temp/"; + +describe("objects.inv", () => { + test("read file and decompress", async () => { + const objectsInv = await ObjectsInv.fromFile(TEST_FOLDER); + + expect(objectsInv.preamble).toMatch( + "# Sphinx inventory version 2\n" + + "# Project: Qiskit\n" + + "# Version: 0.45\n" + + "# The remainder of this file is compressed using zlib.\n", + ); + + const uriIndices = [10, 88, 107, 1419, 23575]; + // This test fails when you include / exclude entries, which shifts some array indices. + // Use the following code to find the new indices. + // console.log(objectsInv.entries.findLastIndex( e => { return e.uri.includes("index") })) + expect(uriIndices.map((i) => objectsInv.entries[i].uri)) + .toMatchInlineSnapshot(` + [ + "stubs/qiskit.algorithms.AlgorithmJob.html#qiskit.algorithms.AlgorithmJob.job_id", + "stubs/qiskit.algorithms.FasterAmplitudeEstimation.html#qiskit.algorithms.FasterAmplitudeEstimation.sampler", + "stubs/qiskit.algorithms.Grover.html#qiskit.algorithms.Grover.quantum_instance", + "apidoc/assembler.html#qiskit.assembler.disassemble", + "index.html", + ] + `); + const nameIndices = [23575, 24146]; + expect(nameIndices.map((i) => objectsInv.entries[i].dispname)) + .toMatchInlineSnapshot(` + [ + "Qiskit 0.45 documentation", + "FakeOslo", + ] + `); + }); + + test("write file and re-read matches original", async () => { + const originalObjectsInv = await ObjectsInv.fromFile(TEST_FOLDER); + await originalObjectsInv.write(TEMP_FOLDER); + + const newObjectsInv = await ObjectsInv.fromFile(TEMP_FOLDER); + expect(originalObjectsInv.entries.length).toEqual( + newObjectsInv.entries.length, + ); + expect(originalObjectsInv.preamble).toMatch(newObjectsInv.preamble); + expect(originalObjectsInv.entriesString()).toMatch( + newObjectsInv.entriesString(), + ); + }); + + test("URI transform works correctly", () => { + const preamble = `# Simple preamble\n`; + // Use nonsense transform function to check things are actually changing + const transformFunction = (x: string) => x.replaceAll("i", "a"); + const entries: ObjectsInvEntry[] = [ + { + name: "qiskit_ibm_runtime.RuntimeJob.job_id", + domainAndRole: "py:method", + priority: "1", + uri: "qiskit_ibm_runtime.RuntimeJob#qiskit_ibm_runtime.RuntimeJob.job_id", + dispname: "-", + }, + { + name: "stubs/qiskit_ibm_provider.transpiler.passes.scheduling.ASAPScheduleAnalysis.__call__", + domainAndRole: "std:doc", + priority: "-1", + uri: "stubs/qiskit_ibm_provider.transpiler.passes.scheduling.ASAPScheduleAnalysis.__call__.html", + dispname: "ASAPScheduleAnalysis.__call__", + }, + { + name: "search", + domainAndRole: "std:label", + priority: "-1", + uri: "search.html", + dispname: "Search Page", + }, + { + name: "release notes_ignis_0.5.0", + domainAndRole: "std:label", + priority: "-1", + uri: "legacy_release_notes.html#release-notes-ignis-0-5-0", + dispname: "Ignis 0.5.0", + }, + { + name: "index", + domainAndRole: "std:doc", + priority: "-1", + uri: "index.html", + dispname: "Qiskit IBM Quantum Provider API docs preview", + }, + ]; + + const objectsInv = new ObjectsInv(preamble, entries); + objectsInv.updateUris(transformFunction); + expect(objectsInv.entries.map((i) => i.uri)).toEqual([ + "qaskat_abm_runtame.RuntameJob#qaskat_abm_runtame.RuntameJob.job_ad", + "stubs/qaskat_abm_provader.transpaler.passes.schedulang.ASAPScheduleAnalysas.__call__", + "search", + "legacy_release_notes#release-notes-agnas-0-5-0", + "andex", + ]); + }); + + afterAll(async () => { + if (await stat(TEMP_FOLDER + "objects.inv")) { + await unlink(TEMP_FOLDER + "objects.inv"); + } + }); +}); diff --git a/scripts/lib/api/objectsInv.ts b/scripts/lib/api/objectsInv.ts new file mode 100644 index 00000000000..d2d4685f2ff --- /dev/null +++ b/scripts/lib/api/objectsInv.ts @@ -0,0 +1,171 @@ +// This code is a Qiskit project. +// +// (C) Copyright IBM 2024. +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +import { readFile, writeFile } from "fs/promises"; +import { unzipSync, deflateSync } from "zlib"; +import { removeSuffix } from "../stringUtils"; +import { join, dirname } from "path"; +import { mkdirp } from "mkdirp"; + +/** + * Some pages exist in the sphinx docs but not in our docs + * If any URIs match these cases, we remove their entries. + **/ +const ENTRIES_TO_EXCLUDE = [ + /^genindex(\.html)?$/, + /^py-modindex(\.html)?$/, + /^search(\.html)?$/, + /^explanation(\.html)?(?=\/|#|$)/, + /^how_to(\.html)?(?=\/|#|$)/, + /^tutorials(\.html)?(?=\/|#|$)/, + /^migration_guides(\.html)?(?=\/|#|$)/, + /^configuration(\.html)?(?=#|$)/, + /^contributing_to_qiskit(\.html)?(?=#|$)/, + /^deprecation_policy(\.html)?(?=#|$)/, + /^faq(\.html)?(?=#|$)/, + /^getting_started(\.html)?(?=#|$)/, + /^intro_tutorial1(\.html)?(?=#|$)/, + /^maintainers_guide(\.html)?(?=#|$)/, + /^qc_intro(\.html)?(?=#|$)/, + /^release[-_]notes(\.html)?(?=#|$)/, + /^legacy_release_notes(\.html)?(?=#|$)/, +]; + +function shouldExcludePage(uri: string): boolean { + return ENTRIES_TO_EXCLUDE.some((condition) => uri.match(condition)); +} + +export type ObjectsInvEntry = { + name: string; + domainAndRole: string; + priority: string; + uri: string; + dispname: string; +}; + +/** + * Class to hold and operate on data from Sphinx's objects.inv file. + * For information on the syntax, see: + * https://sphobjinv.readthedocs.io/en/stable/syntax.html + */ +export class ObjectsInv { + readonly preamble: string; + entries: ObjectsInvEntry[]; + + constructor(preamble: string, entries: ObjectsInvEntry[]) { + this.preamble = preamble; + this.entries = entries; + } + + /** + * Decompress Sphinx's objects.inv. + * This function follows the process from: + * https://github.com/bskinn/sphobjinv/blob/stable/src/sphobjinv/zlib.py + */ + static async fromFile(directoryPath: string): Promise { + const path = join(directoryPath, "objects.inv"); + let buffer = await readFile(path); + // Extract preamble (first 4 lines of file) + let preamble = ""; + for (let line = 0; line < 4; line++) { + let nextNewline = buffer.indexOf(10) + 1; + preamble += buffer.toString("utf8", 0, nextNewline); + buffer = buffer.subarray(nextNewline); + } + + // Decompress the rest + const lines = unzipSync(buffer).toString("utf8").trim().split("\n"); + + // Sort the strings into their parts + const entries: ObjectsInvEntry[] = []; + for (const line of lines) { + // Regex from sphinx source + // https://github.com/sphinx-doc/sphinx/blob/2f60b44999d7e610d932529784f082fc1c6af989/sphinx/util/inventory.py#L115-L116 + const parts = line.match(/(.+?)\s+(\S+)\s+(-?\d+)\s+?(\S*)\s+(.*)/); + if (parts == null || parts.length != 6) { + console.warn(`Error parsing line of objects.inv: ${line}`); + continue; + } + const entry = { + name: parts[1], + domainAndRole: parts[2], + priority: parts[3], + uri: parts[4], + dispname: parts[5], + }; + entry.uri = ObjectsInv.#expandUri(entry.uri, entry.name); + if (shouldExcludePage(entry.uri)) { + continue; + } + + entries.push(entry); + } + + return new ObjectsInv(preamble, entries); + } + + static #expandUri(uri: string, name: string): string { + if (uri.includes("#") && uri.endsWith("$")) { + // #$ is a shorthand for "anchor==name"; see "For illustration" in + // https://sphobjinv.readthedocs.io/en/stable/syntax.html + uri = removeSuffix(uri, "$") + name; + } + return uri; + } + + static #compressUri(uri: string, name: string): string { + if (uri.includes("#") && uri.endsWith(name)) { + uri = removeSuffix(uri, name) + "$"; + } + return uri; + } + + updateUris(transformLink: Function): void { + for (const entry of this.entries) { + entry.uri = entry.uri.replace(/\.html/, ""); + entry.uri = transformLink(entry.uri); + } + } + + /** + * Return all entries joined together as a single string + * to be compressed before writing + */ + entriesString(): string { + const lines: string[] = []; + for (const e of this.entries) { + lines.push( + [ + e.name, + e.domainAndRole, + e.priority, + ObjectsInv.#compressUri(e.uri, e.name), + e.dispname, + ].join(" "), + ); + } + return lines.join("\n"); + } + + /** + * Compress and write to file + */ + async write(directoryPath: string): Promise { + const path = join(directoryPath, "objects.inv"); + const preamble = Buffer.from(this.preamble); + const compressed = deflateSync(Buffer.from(this.entriesString(), "utf8"), { + level: 9, + }); + await mkdirp(dirname(path)); + await writeFile(path, Buffer.concat([preamble, compressed])); + } +} diff --git a/scripts/lib/api/specialCaseResults.ts b/scripts/lib/api/specialCaseResults.ts index f626b45b146..ca4edb614e3 100644 --- a/scripts/lib/api/specialCaseResults.ts +++ b/scripts/lib/api/specialCaseResults.ts @@ -42,7 +42,6 @@ export function specialCaseResults(results: HtmlToMdResultWithUrl[]): void { hardcodedFrontmatter: RUNTIME_INDEX_META, }; } - result.url = transformSpecialCaseUrl(result.url); } } diff --git a/scripts/lib/api/testdata/objects.inv b/scripts/lib/api/testdata/objects.inv new file mode 100644 index 00000000000..370793aa789 Binary files /dev/null and b/scripts/lib/api/testdata/objects.inv differ diff --git a/scripts/lib/api/updateLinks.test.ts b/scripts/lib/api/updateLinks.test.ts index 1a14d878919..d7274f662f2 100644 --- a/scripts/lib/api/updateLinks.test.ts +++ b/scripts/lib/api/updateLinks.test.ts @@ -11,6 +11,7 @@ // that they have been altered from the originals. import { describe, expect, test } from "@jest/globals"; +import { ObjectsInv } from "./objectsInv"; import { last } from "lodash"; import { updateLinks, updateUrl } from "./updateLinks"; @@ -51,7 +52,33 @@ describe("updateLinks", () => { }, ]; - await updateLinks(data); + const objectsInvEntries = [ + "stubs/qiskit.algorithms.Eigensolver#qiskit.algorithms.Eigensolver", + "stubs/qiskit.algorithms.EstimationProblem.html#qiskit.algorithms.EstimationProblem.state_preparation", + "stubs/qiskit.algorithms.FasterAmplitudeEstimationResult.html#qiskit.algorithms.FasterAmplitudeEstimationResult.success_probability", + "stubs/qiskit_ibm_runtime.QiskitRuntimeService", + "stubs/qiskit_ibm_runtime.RuntimeJob#qiskit_ibm_runtime.RuntimeJob.submit", + "stubs/qiskit_ibm_runtime.RuntimeEncoder#qiskit_ibm_runtime.RuntimeEncoder", + "stubs/qiskit_ibm_runtime.options.Options#options", + "tutorials/qaoa_with_primitives", + "tutorials/vqe_with_estimator#Step-1:-Map-classical-inputs-to-a-quantum-problem", + "qiskit.algorithms.gradients.LinCombEstimatorGradient#SUPPORTED_GATES", + "stubs/qiskit_ibm_provider.transpiler.passes.scheduling.DynamicCircuitInstructionDurations#MEASURE_PATCH_CYCLES", + ].map((uri) => { + return { + name: "-", + domainAndRole: "-", + priority: "-", + uri, + dispname: "-", + }; + }); + const objectsInv = new ObjectsInv( + "# Here's a simple preamble", + objectsInvEntries, + ); + + await updateLinks(data, objectsInv); expect(data).toMatchInlineSnapshot(` [ { @@ -84,6 +111,21 @@ describe("updateLinks", () => { }, ] `); + expect(objectsInv.entries.map((e) => e.uri)).toMatchInlineSnapshot(` + [ + "qiskit.algorithms.Eigensolver#qiskit.algorithms.Eigensolver", + "qiskit.algorithms.EstimationProblem#qiskit.algorithms.EstimationProblem.state_preparation", + "qiskit.algorithms.FasterAmplitudeEstimationResult#qiskit.algorithms.FasterAmplitudeEstimationResult.success_probability", + "qiskit_ibm_runtime.QiskitRuntimeService", + "qiskit_ibm_runtime.RuntimeJob#submit", + "qiskit_ibm_runtime.RuntimeEncoder#qiskit_ibm_runtime.RuntimeEncoder", + "qiskit_ibm_runtime.options.Options#options", + "tutorials/qaoa_with_primitives", + "tutorials/vqe_with_estimator#step-1:-map-classical-inputs-to-a-quantum-problem", + "qiskit.algorithms.gradients.LinCombEstimatorGradient#supported_gates", + "qiskit_ibm_provider.transpiler.passes.scheduling.DynamicCircuitInstructionDurations#measure_patch_cycles", + ] + `); }); test("update links using a transform function", async () => { @@ -106,7 +148,30 @@ describe("updateLinks", () => { }, ]; - await updateLinks(data, (link) => { + const objectsInvEntries = [ + "stubs/qiskit.algorithms.Eigensolver.html#$", + "stubs/qiskit.algorithms.Eigensolver.html#$", + "stubs/qiskit.algorithms.EigensolverResult.html#$", + "stubs/qiskit.algorithms.EstimationProblem.html#$", + "stubs/qiskit.algorithms.EvolutionProblem.html#$", + "stubs/qiskit.algorithms.EvolutionProblem.html#$", + "stubs/qiskit.algorithms.FasterAmplitudeEstimationResult.html#$", + "stubs/qiskit.algorithms.FasterAmplitudeEstimationResult.html#$", + ].map((uri) => { + return { + name: "-", + domainAndRole: "-", + priority: "-", + uri, + dispname: "-", + }; + }); + const objectsInv = new ObjectsInv( + "# Here's a simple preamble", + objectsInvEntries, + ); + + await updateLinks(data, objectsInv, (link) => { let path = last(link.url.split("/"))!; if (path.includes("#")) { path = path.split("#").join(".html#"); @@ -138,6 +203,18 @@ describe("updateLinks", () => { }, ] `); + expect(objectsInv.entries.map((e) => e.uri)).toMatchInlineSnapshot(` + [ + "qiskit.algorithms.Eigensolver#$", + "qiskit.algorithms.Eigensolver#$", + "qiskit.algorithms.EigensolverResult#$", + "qiskit.algorithms.EstimationProblem#$", + "qiskit.algorithms.EvolutionProblem#$", + "qiskit.algorithms.EvolutionProblem#$", + "qiskit.algorithms.FasterAmplitudeEstimationResult#$", + "qiskit.algorithms.FasterAmplitudeEstimationResult#$", + ] + `); }); }); diff --git a/scripts/lib/api/updateLinks.ts b/scripts/lib/api/updateLinks.ts index 238448236dd..75312a6abfe 100644 --- a/scripts/lib/api/updateLinks.ts +++ b/scripts/lib/api/updateLinks.ts @@ -25,6 +25,30 @@ import { removePart, removePrefix } from "../stringUtils"; import { HtmlToMdResultWithUrl } from "./HtmlToMdResult"; import { remarkStringifyOptions } from "./commonParserConfig"; import { Link } from "./Pkg"; +import { ObjectsInv } from "./objectsInv"; +import { transformSpecialCaseUrl } from "./specialCaseResults"; + +/** + * Anchors generated from markdown headings are always lower case but, if these + * headings are API references, Sphinx sometimes expects them to include + * uppercase characters. + * + * As a heuristic, we assume urls containing periods are anchors to HTML id + * tags (which preserve Sphinx's original casing), and anchors with no periods + * are from markdown headings (which must be lower-cased). This seems to work + * ok. + */ +function lowerCaseIfMarkdownAnchor(url: string): string { + if (!url.includes("#")) { + return url; + } + const [base, anchor] = url.split("#"); + if (anchor.includes(".")) { + return url; + } + const newAnchor = anchor.toLowerCase(); + return `${base}#${newAnchor}`; +} export function updateUrl( url: string, @@ -33,6 +57,7 @@ export function updateUrl( ): string { if (isAbsoluteUrl(url)) return url; if (url.startsWith("/")) return url; + url = transformSpecialCaseUrl(url); url = removePart(url, "/", ["stubs", "apidocs", "apidoc", ".."]); @@ -47,13 +72,13 @@ export function updateUrl( // qiskit_ibm_runtime.RuntimeJob#qiskit_ibm_runtime.RuntimeJob if (itemNames.has(path)) { if (hash === path) { - return [...initialUrlParts, path].join("/"); + url = [...initialUrlParts, path].join("/"); } // qiskit_ibm_runtime.RuntimeJob#qiskit_ibm_runtime.RuntimeJob.job -> qiskit_ibm_runtime.RuntimeJob#job if (hash?.startsWith(`${path}.`)) { const member = removePrefix(hash, `${path}.`); - return [...initialUrlParts, path].join("/") + `#${member}`; + url = [...initialUrlParts, path].join("/") + `#${member}`; } } @@ -63,13 +88,16 @@ export function updateUrl( const initialPathParts = initial(pathParts); const parentName = initialPathParts.join("."); if ("class" === resultsByName[parentName]?.meta.apiType) { - return [...initialUrlParts, parentName].join("/") + "#" + member; + url = [...initialUrlParts, parentName].join("/") + "#" + member; } + + url = lowerCaseIfMarkdownAnchor(url); return url; } export async function updateLinks( results: HtmlToMdResultWithUrl[], + objectsInv: ObjectsInv, transformLink?: (link: Link) => Link | undefined, ): Promise { const resultsByName = keyBy(results, (result) => result.meta.apiName!); @@ -110,4 +138,8 @@ export async function updateLinks( result.markdown = output?.toString(); } + + objectsInv.updateUris((uri: string) => + updateUrl(uri, resultsByName, itemNames), + ); } diff --git a/scripts/lib/links/extractLinks.ts b/scripts/lib/links/extractLinks.ts index aace610be1c..e8bcf3e7847 100644 --- a/scripts/lib/links/extractLinks.ts +++ b/scripts/lib/links/extractLinks.ts @@ -21,6 +21,9 @@ import { Root } from "remark-mdx"; import rehypeRemark from "rehype-remark"; import rehypeParse from "rehype-parse"; import remarkGfm from "remark-gfm"; +import { ObjectsInv } from "../api/objectsInv"; +import { removePrefix, removeSuffix } from "../stringUtils"; +import { getRoot } from "../fs"; export type ParsedFile = { /** Anchors that the file defines. These can be linked to from other files. */ @@ -71,7 +74,22 @@ export async function parseLinks(markdown: string): Promise { return result; } +async function parseObjectsInv(filePath: string): Promise { + const absoluteFilePath = path.join( + getRoot(), + removeSuffix(filePath, "objects.inv"), + ); + const objinv = await ObjectsInv.fromFile(absoluteFilePath); + // All URIs are relative to the objects.inv file + const dirname = removePrefix(path.dirname(filePath), "public"); + const links = objinv.entries.map((entry) => path.join(dirname, entry.uri)); + return { links, anchors: [] }; +} + export async function parseFile(filePath: string): Promise { + if (filePath.endsWith(".inv")) { + return await parseObjectsInv(filePath); + } const source = await readFile(filePath, { encoding: "utf8" }); const markdown = path.extname(filePath) === ".ipynb" ? markdownFromNotebook(source) : source; diff --git a/scripts/lib/links/ignores.ts b/scripts/lib/links/ignores.ts index 23ae5bb109a..ad4eb57594e 100644 --- a/scripts/lib/links/ignores.ts +++ b/scripts/lib/links/ignores.ts @@ -74,6 +74,69 @@ const SHOULD_BE_FIXED: FilesToIgnores = { "docs/api/qiskit-ibm-provider/release-notes.md": [ "https://github.com/Qiskit/qiskit-ibm-provider/blob/main/docs/tutorials/Migration_Guide_from_qiskit-ibmq-provider.ipynb", ], + "public/api/qiskit/objects.inv": [ + "/api/qiskit/circuit#qiskit.circuit.CASE_DEFAULT", + "/api/qiskit/qiskit.passmanager.FlowController#registered_controllers", + "/api/qiskit/qiskit.providers.basicaer.QasmSimulatorPy#default_configuration", + "/api/qiskit/qiskit.providers.basicaer.QasmSimulatorPy#default_options", + "/api/qiskit/qiskit.providers.basicaer.StatevectorSimulatorPy#default_configuration", + "/api/qiskit/qiskit.providers.basicaer.StatevectorSimulatorPy#default_options", + "/api/qiskit/qiskit.providers.basicaer.UnitarySimulatorPy#default_configuration", + "/api/qiskit/qiskit.providers.basicaer.UnitarySimulatorPy#default_options", + "/api/qiskit/qiskit.pulse.instructions.Reference#scope_delimiter", + "/api/qiskit/qasm2#qiskit.qasm2.LEGACY_CUSTOM_CLASSICAL", + "/api/qiskit/qasm2#qiskit.qasm2.LEGACY_CUSTOM_INSTRUCTIONS", + "/api/qiskit/qasm2#qiskit.qasm2.LEGACY_INCLUDE_PATH", + "/api/qiskit/qiskit.transpiler.StagedPassManager#invalid_stage_regex", + "/api/qiskit/utils#qiskit.utils.algorithm_globals", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_AER", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_CONSTRAINT", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_CPLEX", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_CVXPY", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_DOCPLEX", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_FIXTURES", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_GRAPHVIZ", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_IBMQ", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_IGNIS", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_IPYTHON", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_IPYWIDGETS", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_JAX", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_JUPYTER", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_MATPLOTLIB", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_NETWORKX", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_NLOPT", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_PDFLATEX", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_PDFTOCAIRO", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_PIL", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_PYDOT", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_PYGMENTS", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_PYLATEX", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_QASM3_IMPORT", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_SEABORN", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_SKLEARN", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_SKQUANT", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_SQSNOBFIT", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_SYMENGINE", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_TESTTOOLS", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_TOQM", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_TWEEDLEDUM", + "/api/qiskit/utils#qiskit.utils.optionals.HAS_Z3", + "/api/qiskit/index#main-qiskit-related-projects", + "/api/qiskit/index#qiskit-version-documentation", + "/api/qiskit/qiskit.visualization.timeline_drawer#style-dict-doc", + "/api/qiskit/qiskit.pulse.library.SymbolicPulse#symbolic-pulse-constraints", + "/api/qiskit/qiskit.pulse.library.SymbolicPulse#symbolic-pulse-envelope", + "/api/qiskit/qiskit.pulse.library.SymbolicPulse#symbolic-pulse-eval-condition", + "/api/qiskit/qiskit.pulse.library.SymbolicPulse#symbolic-pulse-serialize", + "/api/qiskit-ibm-runtime/qiskit_ibm_runtime.RuntimeEncoder#key_separator", + "/api/qiskit-ibm-runtime/index#next-steps", + "/api/qiskit-ibm-runtime/index#qiskit-runtime-version-api-docs-preview", + ], + "public/api/qiskit-ibm-runtime/objects.inv": [ + "/api/qiskit-ibm-runtime/qiskit_ibm_runtime.RuntimeEncoder#key_separator", + "/api/qiskit-ibm-runtime/index#next-steps", + "/api/qiskit-ibm-runtime/index#qiskit-runtime-version-api-docs-preview", + ], }; // Issues that are okay, such as because the link checker times out