From 0d9bc1ed99de0487822582e29139d42b025059d5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 01:28:35 +0000 Subject: [PATCH 1/2] chore(deps): update dependency ecmarkup to v20 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bdb44fa..05feb7b 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,6 @@ "repository": "github:DerekNonGeneric/proposal-assertion-error", "license": "MIT", "devDependencies": { - "ecmarkup": "16.0.0" + "ecmarkup": "20.0.0" } } From 8613fbc2f415b214c38c0faa9a42e74b071240e2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" Date: Mon, 23 Sep 2024 01:28:56 +0000 Subject: [PATCH 2/2] fixup: [spec] `npm run build` --- index.html | 1082 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 926 insertions(+), 156 deletions(-) diff --git a/index.html b/index.html index a24f0c3..a593c06 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,13 @@ - + @@ -82,7 +90,7 @@ if (e.code === 'Escape') { sdoBox.deactivate(); } - }) + }), ); }); @@ -99,11 +107,11 @@ this.$searchBox.addEventListener( 'keydown', - debounce(this.searchBoxKeydown.bind(this), { stopPropagation: true }) + debounce(this.searchBoxKeydown.bind(this), { stopPropagation: true }), ); this.$searchBox.addEventListener( 'keyup', - debounce(this.searchBoxKeyup.bind(this), { stopPropagation: true }) + debounce(this.searchBoxKeyup.bind(this), { stopPropagation: true }), ); // Perform an initial search if the box is not empty. @@ -303,7 +311,6 @@ } if (text) { - // prettier-ignore html += ``; } }); @@ -355,6 +362,14 @@ this._pinnedIds = {}; this.loadPinEntries(); + // unpin all button + document + .querySelector('#menu-pins .unpin-all') + .addEventListener('click', this.unpinAll.bind(this)); + + // individual unpinning buttons + this.$pinList.addEventListener('click', this.pinListClick.bind(this)); + // toggle menu this.$toggle.addEventListener('click', this.toggle.bind(this)); @@ -404,8 +419,8 @@ e.stopPropagation(); if (e.keyCode === 80) { this.togglePinEntry(); - } else if (e.keyCode > 48 && e.keyCode < 58) { - this.selectPin(e.keyCode - 49); + } else if (e.keyCode >= 48 && e.keyCode < 58) { + this.selectPin((e.keyCode - 9) % 10); } }; @@ -455,25 +470,68 @@ }; function findActiveClause(root, path) { - let clauses = getChildClauses(root); path = path || []; - for (let $clause of clauses) { - let rect = $clause.getBoundingClientRect(); + let visibleClauses = getVisibleClauses(root, path); + let midpoint = Math.floor(window.innerHeight / 2); + + for (let [$clause, path] of visibleClauses) { + let { top: clauseTop, bottom: clauseBottom } = $clause.getBoundingClientRect(); + let isFullyVisibleAboveTheFold = + clauseTop > 0 && clauseTop < midpoint && clauseBottom < window.innerHeight; + if (isFullyVisibleAboveTheFold) { + return path; + } + } + + visibleClauses.sort(([, pathA], [, pathB]) => pathB.length - pathA.length); + for (let [$clause, path] of visibleClauses) { + let { top: clauseTop, bottom: clauseBottom } = $clause.getBoundingClientRect(); let $header = $clause.querySelector('h1'); + let clauseStyles = getComputedStyle($clause); let marginTop = Math.max( - parseInt(getComputedStyle($clause)['margin-top']), - parseInt(getComputedStyle($header)['margin-top']) + 0, + parseInt(clauseStyles['margin-top']), + parseInt(getComputedStyle($header)['margin-top']), ); - - if (rect.top - marginTop <= 1 && rect.bottom > 0) { - return findActiveClause($clause, path.concat($clause)) || path; + let marginBottom = Math.max(0, parseInt(clauseStyles['margin-bottom'])); + let crossesMidpoint = + clauseTop - marginTop <= midpoint && clauseBottom + marginBottom >= midpoint; + if (crossesMidpoint) { + return path; } } return path; } +function getVisibleClauses(root, path) { + let childClauses = getChildClauses(root); + path = path || []; + + let result = []; + + let seenVisibleClause = false; + for (let $clause of childClauses) { + let { top: clauseTop, bottom: clauseBottom } = $clause.getBoundingClientRect(); + let isPartiallyVisible = + (clauseTop > 0 && clauseTop < window.innerHeight) || + (clauseBottom > 0 && clauseBottom < window.innerHeight) || + (clauseTop < 0 && clauseBottom > window.innerHeight); + + if (isPartiallyVisible) { + seenVisibleClause = true; + let innerPath = path.concat($clause); + result.push([$clause, innerPath]); + result.push(...getVisibleClauses($clause, innerPath)); + } else if (seenVisibleClause) { + break; + } + } + + return result; +} + function* getChildClauses(root) { for (let el of root.children) { switch (el.nodeName) { @@ -524,6 +582,7 @@ return; } + let text; if (entry.type === 'clause') { let prefix; if (entry.number) { @@ -531,12 +590,14 @@ } else { prefix = ''; } - // prettier-ignore - this.$pinList.innerHTML += `
  • ${prefix}${entry.titleHTML}
  • `; + text = `${prefix}${entry.titleHTML}`; } else { - this.$pinList.innerHTML += `
  • ${getKey(entry)}
  • `; + text = getKey(entry); } + let link = `${text}`; + this.$pinList.innerHTML += `
  • ${link}
  • `; + if (Object.keys(this._pinnedIds).length === 0) { this.showPins(); } @@ -545,7 +606,7 @@ }; Menu.prototype.removePinEntry = function (id) { - let item = this.$pinList.querySelector(`a[href="${makeLinkToId(id)}"]`).parentNode; + let item = this.$pinList.querySelector(`li[data-section-id="${id}"]`); this.$pinList.removeChild(item); delete this._pinnedIds[id]; if (Object.keys(this._pinnedIds).length === 0) { @@ -555,6 +616,21 @@ this.persistPinEntries(); }; +Menu.prototype.unpinAll = function () { + for (let id of Object.keys(this._pinnedIds)) { + this.removePinEntry(id); + } +}; + +Menu.prototype.pinListClick = function (event) { + if (event?.target?.classList.contains('unpin')) { + let id = event.target.parentNode.dataset.sectionId; + if (id) { + this.removePinEntry(id); + } + } +}; + Menu.prototype.persistPinEntries = function () { try { if (!window.localStorage) return; @@ -593,6 +669,7 @@ }; Menu.prototype.selectPin = function (num) { + if (num >= this.$pinList.children.length) return; document.location = this.$pinList.children[num].children[0].href; }; @@ -768,6 +845,10 @@ this.$header.appendChild(this.$headerText); this.$headerRefId = document.createElement('a'); this.$header.appendChild(this.$headerRefId); + this.$header.addEventListener('pointerdown', e => { + this.dragStart(e); + }); + this.$closeButton = document.createElement('span'); this.$closeButton.setAttribute('id', 'references-pane-close'); this.$closeButton.addEventListener('click', () => { @@ -776,18 +857,20 @@ this.$header.appendChild(this.$closeButton); this.$pane.appendChild(this.$header); - let tableContainer = document.createElement('div'); - tableContainer.setAttribute('id', 'references-pane-table-container'); + this.$tableContainer = document.createElement('div'); + this.$tableContainer.setAttribute('id', 'references-pane-table-container'); this.$table = document.createElement('table'); this.$table.setAttribute('id', 'references-pane-table'); this.$tableBody = this.$table.createTBody(); - tableContainer.appendChild(this.$table); - this.$pane.appendChild(tableContainer); + this.$tableContainer.appendChild(this.$table); + this.$pane.appendChild(this.$tableContainer); - menu.$specContainer.appendChild(this.$container); + if (menu != null) { + menu.$specContainer.appendChild(this.$container); + } }, activate() { @@ -807,7 +890,7 @@ let previousId; let previousCell; let dupCount = 0; - this.$headerRefId.textContent = '#' + entry.id; + this.$headerRefId.innerHTML = getKey(entry); this.$headerRefId.setAttribute('href', makeLinkToId(entry.id)); this.$headerRefId.style.display = 'inline'; (entry.referencingIds || []) @@ -838,6 +921,7 @@ this.$table.removeChild(this.$tableBody); this.$tableBody = newBody; this.$table.appendChild(this.$tableBody); + this.autoSize(); }, showSDOs(sdos, alternativeId) { @@ -855,7 +939,6 @@ e.parentNode.replaceChild(document.createTextNode(e.textContent), e); }); - // prettier-ignore this.$headerText.innerHTML = `Syntax-Directed Operations for
    ${parentName} ${colons.outerHTML} `; this.$headerText.querySelector('a').append(rhs); this.showSDOsBody(sdos, alternativeId); @@ -884,6 +967,34 @@ this.$table.removeChild(this.$tableBody); this.$tableBody = newBody; this.$table.appendChild(this.$tableBody); + this.autoSize(); + }, + + autoSize() { + this.$tableContainer.style.height = + Math.min(250, this.$table.getBoundingClientRect().height) + 'px'; + }, + + dragStart(pointerDownEvent) { + let startingMousePos = pointerDownEvent.clientY; + let startingHeight = this.$tableContainer.getBoundingClientRect().height; + let moveListener = pointerMoveEvent => { + if (pointerMoveEvent.buttons === 0) { + removeListeners(); + return; + } + let desiredHeight = startingHeight - (pointerMoveEvent.clientY - startingMousePos); + this.$tableContainer.style.height = Math.max(0, desiredHeight) + 'px'; + }; + let listenerOptions = { capture: true, passive: true }; + let removeListeners = () => { + document.removeEventListener('pointermove', moveListener, listenerOptions); + this.$header.removeEventListener('pointerup', removeListeners, listenerOptions); + this.$header.removeEventListener('pointercancel', removeListeners, listenerOptions); + }; + document.addEventListener('pointermove', moveListener, listenerOptions); + this.$header.addEventListener('pointerup', removeListeners, listenerOptions); + this.$header.addEventListener('pointercancel', removeListeners, listenerOptions); }, }; @@ -914,7 +1025,9 @@ referencePane.showReferencesFor(this.entry); }); this.$container.appendChild(this.$permalink); + this.$container.appendChild(document.createTextNode(' ')); this.$container.appendChild(this.$pinLink); + this.$container.appendChild(document.createTextNode(' ')); this.$container.appendChild(this.$refsLink); document.body.appendChild(this.$outer); }, @@ -1092,13 +1205,16 @@ } function init() { + if (document.getElementById('menu') == null) { + return; + } menu = new Menu(); let $container = document.getElementById('spec-container'); $container.addEventListener( 'mouseover', debounce(e => { Toolbox.activateIfMouseOver(e); - }) + }), ); document.addEventListener( 'keydown', @@ -1109,7 +1225,7 @@ } document.getElementById('shortcuts-help').classList.remove('active'); } - }) + }), ); } @@ -1165,12 +1281,40 @@ return [...menu.$menu.querySelectorAll('.active')].map(getTocPath).filter(p => p != null); } -function loadStateFromSessionStorage() { - if (!window.sessionStorage || typeof menu === 'undefined' || window.navigating) { +function initTOCExpansion(visibleItemLimit) { + // Initialize to a reasonable amount of TOC expansion: + // * Expand any full-breadth nesting level up to visibleItemLimit. + // * Expand any *single-item* level while under visibleItemLimit (even if that pushes over it). + + // Limit to initialization by bailing out if any parent item is already expanded. + const tocItems = Array.from(document.querySelectorAll('#menu-toc li')); + if (tocItems.some(li => li.classList.contains('active') && li.querySelector('li'))) { + return; + } + + const selfAndSiblings = maybe => Array.from(maybe?.parentNode.children ?? []); + let currentLevelItems = selfAndSiblings(tocItems[0]); + let availableCount = visibleItemLimit - currentLevelItems.length; + while (availableCount > 0 && currentLevelItems.length) { + const nextLevelItems = currentLevelItems.flatMap(li => selfAndSiblings(li.querySelector('li'))); + availableCount -= nextLevelItems.length; + if (availableCount > 0 || currentLevelItems.length === 1) { + // Expand parent items of the next level down (i.e., current-level items with children). + for (const ol of new Set(nextLevelItems.map(li => li.parentNode))) { + ol.closest('li').classList.add('active'); + } + } + currentLevelItems = nextLevelItems; + } +} + +function initState() { + if (typeof menu === 'undefined' || window.navigating) { return; } - if (sessionStorage.referencePaneState != null) { - let state = JSON.parse(sessionStorage.referencePaneState); + const storage = typeof sessionStorage !== 'undefined' ? sessionStorage : Object.create(null); + if (storage.referencePaneState != null) { + let state = JSON.parse(storage.referencePaneState); if (state != null) { if (state.type === 'ref') { let entry = menu.search.biblio.byId[state.id]; @@ -1184,39 +1328,36 @@ referencePane.showSDOsBody(sdos, state.id); } } - delete sessionStorage.referencePaneState; + delete storage.referencePaneState; } } - if (sessionStorage.activeTocPaths != null) { - document - .getElementById('menu-toc') - .querySelectorAll('.active') - .forEach(e => { - e.classList.remove('active'); - }); - let active = JSON.parse(sessionStorage.activeTocPaths); + if (storage.activeTocPaths != null) { + document.querySelectorAll('#menu-toc li.active').forEach(li => li.classList.remove('active')); + let active = JSON.parse(storage.activeTocPaths); active.forEach(activateTocPath); - delete sessionStorage.activeTocPaths; + delete storage.activeTocPaths; + } else { + initTOCExpansion(20); } - if (sessionStorage.searchValue != null) { - let value = JSON.parse(sessionStorage.searchValue); + if (storage.searchValue != null) { + let value = JSON.parse(storage.searchValue); menu.search.$searchBox.value = value; menu.search.search(value); - delete sessionStorage.searchValue; + delete storage.searchValue; } - if (sessionStorage.tocScroll != null) { - let tocScroll = JSON.parse(sessionStorage.tocScroll); + if (storage.tocScroll != null) { + let tocScroll = JSON.parse(storage.tocScroll); menu.$toc.scrollTop = tocScroll; - delete sessionStorage.tocScroll; + delete storage.tocScroll; } } -document.addEventListener('DOMContentLoaded', loadStateFromSessionStorage); +document.addEventListener('DOMContentLoaded', initState); -window.addEventListener('pageshow', loadStateFromSessionStorage); +window.addEventListener('pageshow', initState); window.addEventListener('beforeunload', () => { if (!window.sessionStorage || typeof menu === 'undefined') { @@ -1229,44 +1370,360 @@ }); 'use strict'; -let decimalBullet = Array.from({ length: 100 }, (a, i) => '' + (i + 1)); -let alphaBullet = Array.from({ length: 26 }, (a, i) => String.fromCharCode('a'.charCodeAt(0) + i)); - -// prettier-ignore -let romanBullet = ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii', 'viii', 'ix', 'x', 'xi', 'xii', 'xiii', 'xiv', 'xv', 'xvi', 'xvii', 'xviii', 'xix', 'xx', 'xxi', 'xxii', 'xxiii', 'xxiv', 'xxv']; -// prettier-ignore -let bullets = [decimalBullet, alphaBullet, romanBullet, decimalBullet, alphaBullet, romanBullet]; - -function addStepNumberText(ol, parentIndex) { - for (let i = 0; i < ol.children.length; ++i) { - let child = ol.children[i]; - let index = parentIndex.concat([i]); - let applicable = bullets[Math.min(index.length - 1, 5)]; - let span = document.createElement('span'); - span.textContent = (applicable[i] || '?') + '. '; - span.style.fontSize = '0'; - span.setAttribute('aria-hidden', 'true'); - child.prepend(span); - let sublist = child.querySelector('ol'); - if (sublist != null) { - addStepNumberText(sublist, index); + +// Manually prefix algorithm step list items with hidden counter representations +// corresponding with their markers so they get selected and copied with content. +// We read list-style-type to avoid divergence with the style sheet, but +// for efficiency assume that all lists at the same nesting depth use the same +// style (except for those associated with replacement steps). +// We also precompute some initial items for each supported style type. +// https://w3c.github.io/csswg-drafts/css-counter-styles/ + +const lowerLetters = Array.from({ length: 26 }, (_, i) => + String.fromCharCode('a'.charCodeAt(0) + i), +); +// Implement the lower-alpha 'alphabetic' algorithm, +// adjusting for indexing from 0 rather than 1. +// https://w3c.github.io/csswg-drafts/css-counter-styles/#simple-alphabetic +// https://w3c.github.io/csswg-drafts/css-counter-styles/#alphabetic-system +const lowerAlphaTextForIndex = i => { + let S = ''; + for (const N = lowerLetters.length; i >= 0; i--) { + S = lowerLetters[i % N] + S; + i = Math.floor(i / N); + } + return S; +}; + +const weightedLowerRomanSymbols = Object.entries({ + m: 1000, + cm: 900, + d: 500, + cd: 400, + c: 100, + xc: 90, + l: 50, + xl: 40, + x: 10, + ix: 9, + v: 5, + iv: 4, + i: 1, +}); +// Implement the lower-roman 'additive' algorithm, +// adjusting for indexing from 0 rather than 1. +// https://w3c.github.io/csswg-drafts/css-counter-styles/#simple-numeric +// https://w3c.github.io/csswg-drafts/css-counter-styles/#additive-system +const lowerRomanTextForIndex = i => { + let value = i + 1; + let S = ''; + for (const [symbol, weight] of weightedLowerRomanSymbols) { + if (!value) break; + if (weight > value) continue; + const reps = Math.floor(value / weight); + S += symbol.repeat(reps); + value -= weight * reps; + } + return S; +}; + +// Memoize pure index-to-text functions with an exposed cache for fast retrieval. +const makeCounter = (pureGetTextForIndex, precomputeCount = 30) => { + const cache = Array.from({ length: precomputeCount }, (_, i) => pureGetTextForIndex(i)); + const getTextForIndex = i => { + if (i >= cache.length) cache[i] = pureGetTextForIndex(i); + return cache[i]; + }; + return { getTextForIndex, cache }; +}; + +const counterByStyle = { + __proto__: null, + decimal: makeCounter(i => String(i + 1)), + 'lower-alpha': makeCounter(lowerAlphaTextForIndex), + 'upper-alpha': makeCounter(i => lowerAlphaTextForIndex(i).toUpperCase()), + 'lower-roman': makeCounter(lowerRomanTextForIndex), + 'upper-roman': makeCounter(i => lowerRomanTextForIndex(i).toUpperCase()), +}; +const fallbackCounter = makeCounter(() => '?'); +const counterByDepth = []; + +function addStepNumberText( + ol, + depth = 0, + indent = '', + special = [...ol.classList].some(c => c.startsWith('nested-')), +) { + let counter = !special && counterByDepth[depth]; + if (!counter) { + const counterStyle = getComputedStyle(ol)['list-style-type']; + counter = counterByStyle[counterStyle]; + if (!counter) { + console.warn('unsupported list-style-type', { + ol, + counterStyle, + id: ol.closest('[id]')?.getAttribute('id'), + }); + counterByStyle[counterStyle] = fallbackCounter; + counter = fallbackCounter; + } + if (!special) { + counterByDepth[depth] = counter; } } + const { cache, getTextForIndex } = counter; + let i = (Number(ol.getAttribute('start')) || 1) - 1; + for (const li of ol.children) { + const marker = document.createElement('span'); + const markerText = i < cache.length ? cache[i] : getTextForIndex(i); + const extraIndent = ' '.repeat(markerText.length + 2); + marker.textContent = `${indent}${markerText}. `; + marker.setAttribute('aria-hidden', 'true'); + marker.setAttribute('class', 'list-marker'); + const attributesContainer = li.querySelector('.attributes-tag'); + if (attributesContainer == null) { + li.prepend(marker); + } else { + attributesContainer.insertAdjacentElement('afterend', marker); + } + for (const sublist of li.querySelectorAll(':scope > ol')) { + addStepNumberText(sublist, depth + 1, indent + extraIndent, special); + } + i++; + } } + document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('emu-alg > ol').forEach(ol => { - addStepNumberText(ol, []); + addStepNumberText(ol); + }); +}); + +// Omit indendation when copying a single algorithm step. +document.addEventListener('copy', evt => { + // Construct a DOM from the selection. + const doc = document.implementation.createHTMLDocument(''); + const domRoot = doc.createElement('div'); + const html = evt.clipboardData.getData('text/html'); + if (html) { + domRoot.innerHTML = html; + } else { + const selection = getSelection(); + const singleRange = selection?.rangeCount === 1 && selection.getRangeAt(0); + const container = singleRange?.commonAncestorContainer; + if (!container?.querySelector?.('.list-marker')) { + return; + } + domRoot.append(singleRange.cloneContents()); + } + + // Preserve the indentation if there is no hidden list marker, or if selection + // of more than one step is indicated by either multiple such markers or by + // visible text before the first one. + const listMarkers = domRoot.querySelectorAll('.list-marker'); + if (listMarkers.length !== 1) { + return; + } + const treeWalker = document.createTreeWalker(domRoot, undefined, { + acceptNode(node) { + return node.nodeType === Node.TEXT_NODE || node === listMarkers[0] + ? NodeFilter.FILTER_ACCEPT + : NodeFilter.FILTER_SKIP; + }, + }); + while (treeWalker.nextNode()) { + const node = treeWalker.currentNode; + if (node.nodeType === Node.ELEMENT_NODE) break; + if (/\S/u.test(node.data)) return; + } + + // Strip leading indentation from the plain text representation. + evt.clipboardData.setData('text/plain', domRoot.textContent.trimStart()); + if (!html) { + evt.clipboardData.setData('text/html', domRoot.innerHTML); + } + evt.preventDefault(); +}); + +'use strict'; + +// Update superscripts to not suffer misinterpretation when copied and pasted as plain text. +// For example, +// * Replace `103` with +// `103` +// so it gets pasted as `10**3` rather than `103`. +// * Replace `10-x` with +// `10-x` +// so it gets pasted as `10**-x` rather than `10-x`. +// * Replace `2a + 1` with +// `2**(a + 1)` +// so it gets pasted as `2**(a + 1)` rather than `2a + 1`. + +function makeExponentPlainTextSafe(sup) { + // Change a only if it appears to be an exponent: + // * text-only and contains only mathematical content (not e.g. `1st`) + // * contains only s and internal links (e.g. + // `2(_y_)`) + const isText = [...sup.childNodes].every(node => node.nodeType === 3); + const text = sup.textContent; + if (isText) { + if (!/^[0-9. 𝔽ℝℤ()=*×/÷±+\u2212-]+$/u.test(text)) { + return; + } + } else { + if (sup.querySelector('*:not(var, emu-xref, :scope emu-xref a)')) { + return; + } + } + + let prefix = '**'; + let suffix = ''; + + // Add wrapping parentheses unless they are already present + // or this is a simple (possibly signed) integer or single-variable exponent. + const skipParens = + /^[±+\u2212-]?(?:[0-9]+|\p{ID_Start}\p{ID_Continue}*)$/u.test(text) || + // Split on parentheses and remember them; the resulting parts must + // start and end empty (i.e., with open/close parentheses) + // and increase depth to 1 only at the first parenthesis + // to e.g. wrap `(a+1)*(b+1)` but not `((a+1)*(b+1))`. + text + .trim() + .split(/([()])/g) + .reduce((depth, s, i, parts) => { + if (s === '(') { + return depth > 0 || i === 1 ? depth + 1 : NaN; + } else if (s === ')') { + return depth > 0 ? depth - 1 : NaN; + } else if (s === '' || (i > 0 && i < parts.length - 1)) { + return depth; + } + return NaN; + }, 0) === 0; + if (!skipParens) { + prefix += '('; + suffix += ')'; + } + + sup.insertAdjacentHTML('beforebegin', ``); + if (suffix) { + sup.insertAdjacentHTML('afterend', ``); + } +} + +document.addEventListener('DOMContentLoaded', () => { + document.querySelectorAll('sup:not(.text)').forEach(sup => { + makeExponentPlainTextSafe(sup); }); }); let sdoMap = JSON.parse(`{}`); let biblio = JSON.parse(`{"refsByClause":{},"entries":[{"type":"clause","id":"sec-demo-clause","titleHTML":"This is an emu-clause","number":"1"},{"type":"clause","id":"sec-copyright-and-software-license","title":"Copyright & Software License","titleHTML":"Copyright & Software License","number":"A"}]}`); -;let usesMultipage = false
    +
    • Toggle shortcuts help?
    • Toggle "can call user code" annotationsu
    • Jump to search box/
    • -

    Stage -1 Draft / January 1, 2024

    AssertionError Proposal

    +

    Stage -1 Draft / September 23, 2024

    AssertionError Proposal

    1 This is an emu-clause

    @@ -2538,7 +3308,7 @@

    Copyright Notice

    © 2024 The TC39 AssertionError Proposal Authors

    Software License

    -

    All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

    +

    All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: