diff --git a/src/core/inline-idl-parser.js b/src/core/inline-idl-parser.js index 4eb862c130..0dce75e683 100644 --- a/src/core/inline-idl-parser.js +++ b/src/core/inline-idl-parser.js @@ -4,13 +4,14 @@ import { htmlJoinComma, showError } from "./utils.js"; import { html } from "./import-maps.js"; -const idlPrimitiveRegex = /^[a-z]+(\s+[a-z]+)+$/; // {{unrestricted double}} {{ double }} +const idlPrimitiveRegex = /^[a-z]+(\s+[a-z]+)+\??$/; // {{unrestricted double?}} {{ double }} const exceptionRegex = /\B"([^"]*)"\B/; // {{ "SomeException" }} const methodRegex = /(\w+)\((.*)\)$/; const slotRegex = /^\[\[(\w+)\]\]$/; // matches: `value` or `[[value]]` // NOTE: [[value]] is actually a slot, but database has this as type="attribute" const attributeRegex = /^((?:\[\[)?(?:\w+)(?:\]\])?)$/; +const baseRegex = /^(?:\w+)\??$/; const enumRegex = /^(\w+)\["([\w- ]*)"\]$/; // TODO: const splitRegex = /(?<=\]\]|\b)\./ // https://github.com/w3c/respec/pull/1848/files#r225087385 @@ -21,6 +22,7 @@ const methodSplitRegex = /\.?(\w+\(.*\)$)/; * @property {"base"} type * @property {string} identifier * @property {boolean} renderParent + * @property {boolean} nullable * @property {InlineIdl | null} [parent] * * @typedef {object} IdlAttribute @@ -56,6 +58,7 @@ const methodSplitRegex = /\.?(\w+\(.*\)$)/; * * @typedef {object} IdlPrimitive * @property {"idl-primitive"} type + * @property {boolean} nullable * @property {string} identifier * @property {boolean} renderParent * @property {InlineIdl | null} [parent] @@ -116,12 +119,21 @@ function parseInlineIDL(str) { continue; } if (idlPrimitiveRegex.test(value)) { - results.push({ type: "idl-primitive", identifier: value, renderParent }); + const nullable = value.endsWith("?"); + const identifier = nullable ? value.slice(0, -1) : value; + results.push({ + type: "idl-primitive", + identifier, + renderParent, + nullable, + }); continue; } // base, always final token - if (attributeRegex.test(value) && tokens.length === 0) { - results.push({ type: "base", identifier: value, renderParent }); + if (baseRegex.test(value) && tokens.length === 0) { + const nullable = value.endsWith("?"); + const identifier = nullable ? value.slice(0, -1) : value; + results.push({ type: "base", identifier, renderParent, nullable }); continue; } throw new SyntaxError(`IDL micro-syntax parsing error in \`{{ ${str} }}\``); @@ -139,10 +151,13 @@ function parseInlineIDL(str) { */ function renderBase(details) { // Check if base is a local variable in a section - const { identifier, renderParent } = details; + const { identifier, renderParent, nullable } = details; if (renderParent) { - return html`${identifier}${identifier + (nullable ? "?" : "")}`; } } @@ -244,12 +259,13 @@ function renderException(details) { * @param {IdlPrimitive} details */ function renderIdlPrimitiveType(details) { - const { identifier } = details; + const { identifier, nullable } = details; const element = html`${identifier}${identifier + (nullable ? "?" : "")}`; return element; } diff --git a/src/core/inlines.js b/src/core/inlines.js index 72fd63d45b..544ca15817 100644 --- a/src/core/inlines.js +++ b/src/core/inlines.js @@ -61,7 +61,7 @@ const l10n = getIntlData(localizationStrings); // TODO: Replace (?!`) at the end with (?:]+\??)?\|\B/; // |var : Type?| const inlineCitation = /(?:\[\[(?:!|\\|\?)?[\w.-]+(?:|[^\]]+)?\]\])/; // [[citation]] const inlineExpansion = /(?:\[\[\[(?:!|\\|\?)?#?[\w-.]+\]\]\])/; // [[[expand]]] diff --git a/tests/spec/core/inlines-spec.js b/tests/spec/core/inlines-spec.js index 1680405959..65a6394ee3 100644 --- a/tests/spec/core/inlines-spec.js +++ b/tests/spec/core/inlines-spec.js @@ -530,6 +530,56 @@ describe("Core - Inlines", () => { ); }); + it("supports {{ Nullable? }} types and primitives", async () => { + const body = ` +
+
+        [Exposed=Window]
+        interface InterFace{};
+        dictionary Dict{};
+        
+

{{ InterFace? }}

+

{{ Dict? }}

+

{{ unsigned short? }}

+
+ `; + const config = { xref: ["WebIDL"] }; + const doc = await makeRSDoc(makeStandardOps(config, body)); + + const [interfaceDfn, dictDfn] = doc.querySelectorAll(".idl dfn"); + + // {{ Interface? }} + const interfaceAnchor = doc.querySelector("#interface a"); + expect(interfaceAnchor.textContent).toBe("InterFace?"); + expect(interfaceAnchor.hash.endsWith(interfaceDfn.id)).toBeTrue(); + + const interfaceData = interfaceAnchor.dataset; + expect(interfaceData.xrefType).toBe("_IDL_"); + expect(interfaceData.linkType).toBe("idl"); + expect(interfaceData.lt).toBe("InterFace"); + + // {{ Dict? }} + const dictAnchor = doc.querySelector("#dictionary a"); + expect(dictAnchor.textContent).toBe("Dict?"); + expect(dictAnchor.hash.endsWith(dictDfn.id)).toBeTrue(); + + const dictData = dictAnchor.dataset; + expect(dictData.xrefType).toBe("_IDL_"); + expect(dictData.linkType).toBe("idl"); + expect(dictData.lt).toBe("Dict"); + + // {{ unsigned short? }} + const primitiveAnchor = doc.querySelector("#primitive a"); + expect(primitiveAnchor.textContent).toBe("unsigned short?"); + expect(primitiveAnchor.hash).toBe("#idl-unsigned-short"); + + const primitiveData = primitiveAnchor.dataset; + expect(primitiveData.linkType).toBe("idl"); + expect(primitiveData.cite).toBe("webidl"); + expect(primitiveData.xrefType).toBe("interface"); + expect(primitiveData.lt).toBe("unsigned short"); + }); + it("doesn't link processed inline WebIDL if inside a definition", async () => { const body = `