diff --git a/css/elements.css b/css/elements.css index 2fcc67f4..41156ffe 100644 --- a/css/elements.css +++ b/css/elements.css @@ -191,7 +191,7 @@ emu-alg ol.nested-lots ol { list-style-type: lower-roman; } -emu-alg [aria-hidden=true] { +emu-alg [aria-hidden='true'] { font-size: 0; } @@ -1205,19 +1205,19 @@ li.menu-search-result-term:before { } [normative-optional], +[deprecated], [legacy] { border-left: 5px solid #ff6600; padding: 0.5em; - display: block; background: #ffeedd; } -.clause-attributes-tag { +.attributes-tag { text-transform: uppercase; color: #884400; } -.clause-attributes-tag a { +.attributes-tag a { color: #884400; } diff --git a/js/listNumbers.js b/js/listNumbers.js index dfba4807..7200e6ca 100644 --- a/js/listNumbers.js +++ b/js/listNumbers.js @@ -105,7 +105,12 @@ function addStepNumberText( const marker = document.createElement('span'); marker.textContent = `${i < cache.length ? cache[i] : getTextForIndex(i)}. `; marker.setAttribute('aria-hidden', 'true'); - li.prepend(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, special); } diff --git a/spec/index.html b/spec/index.html index e4e00e83..094c9aaa 100644 --- a/spec/index.html +++ b/spec/index.html @@ -400,6 +400,11 @@

Result

Originate, suppress, or constrain the scope of effects. `<emu-meta>`, `[fence-effects="user-code"]` + + Step optionality + Mark a step as deprecated, normative-optional, or legacy. + `[normative-optional]` + diff --git a/src/Algorithm.ts b/src/Algorithm.ts index f12fdd7c..8cc0b572 100644 --- a/src/Algorithm.ts +++ b/src/Algorithm.ts @@ -3,6 +3,7 @@ import type { Node as EcmarkdownNode, OrderedListItemNode, AlgorithmNode } from import type { PartialBiblioEntry, StepBiblioEntry } from './Biblio'; import Builder from './Builder'; +import { SPECIAL_KINDS_MAP, SPECIAL_KINDS } from './Clause'; import { warnEmdFailure, wrapEmdFailure } from './utils'; import { collectNonterminalsFromEmd } from './lint/utils'; import * as emd from 'ecmarkdown'; @@ -19,6 +20,8 @@ function findLabeledSteps(root: EcmarkdownNode) { return steps; } +const kindSelector = SPECIAL_KINDS.map(kind => `li[${kind}]`).join(','); + export type AlgorithmElementWithTree = HTMLElement & { // null means a failed parse ecmarkdownTree: AlgorithmNode | null; @@ -175,6 +178,32 @@ export default class Algorithm extends Builder { context.spec.labeledStepsToBeRectified.add(step.id); } } + + for (const step of node.querySelectorAll(kindSelector)) { + // prettier-ignore + const attributes = SPECIAL_KINDS + .filter(kind => step.hasAttribute(kind)) + .map(kind => SPECIAL_KINDS_MAP.get(kind)); + const tag = spec.doc.createElement('div'); + tag.className = 'attributes-tag'; + const text = attributes.join(', '); + const contents = spec.doc.createTextNode(text); + tag.append(contents); + step.prepend(tag); + + // we've already walked past the text node, so it won't get picked up by the usual process for autolinking + const clause = clauseStack[clauseStack.length - 1]; + if (clause != null) { + // the `== null` case only happens if you put an algorithm at the top level of your document + spec._textNodes[clause.namespace] = spec._textNodes[clause.namespace] || []; + spec._textNodes[clause.namespace].push({ + node: contents, + clause, + inAlg: true, + currentId: context.currentId, + }); + } + } } static exit(context: Context) { diff --git a/src/Clause.ts b/src/Clause.ts index 2d8d0596..4c01c218 100644 --- a/src/Clause.ts +++ b/src/Clause.ts @@ -24,6 +24,13 @@ const aoidTypes = [ 'numeric method', ]; +export const SPECIAL_KINDS_MAP = new Map([ + ['normative-optional', 'Normative Optional'], + ['legacy', 'Legacy'], + ['deprecated', 'Deprecated'], +]); +export const SPECIAL_KINDS = [...SPECIAL_KINDS_MAP.keys()]; + export function extractStructuredHeader(header: Element): Element | null { const dl = header.nextElementSibling; if (dl == null || dl.tagName !== 'DL' || !dl.classList.contains('header')) { @@ -302,16 +309,13 @@ export default class Clause extends Builder { clause.buildExamples(); clause.buildNotes(); - const attributes = []; - if (node.hasAttribute('normative-optional')) { - attributes.push('Normative Optional'); - } - if (node.hasAttribute('legacy')) { - attributes.push('Legacy'); - } + // prettier-ignore + const attributes = SPECIAL_KINDS + .filter(kind => node.hasAttribute(kind)) + .map(kind => SPECIAL_KINDS_MAP.get(kind)); if (attributes.length > 0) { const tag = spec.doc.createElement('div'); - tag.className = 'clause-attributes-tag'; + tag.className = 'attributes-tag'; const text = attributes.join(', '); const contents = spec.doc.createTextNode(text); tag.append(contents); diff --git a/src/lint/rules/step-attributes.ts b/src/lint/rules/step-attributes.ts index ba67c6e3..99d36a18 100644 --- a/src/lint/rules/step-attributes.ts +++ b/src/lint/rules/step-attributes.ts @@ -2,9 +2,11 @@ import type { OrderedListItemNode } from 'ecmarkdown'; import type { Reporter } from '../algorithm-error-reporter-type'; import type { Seq } from '../../expr-parser'; -const ruleId = 'unknown-step-attribute'; +import { SPECIAL_KINDS } from '../../Clause'; -const KNOWN_ATTRIBUTES = ['id', 'fence-effects', 'declared']; +const ruleId = 'step-attribute'; + +const KNOWN_ATTRIBUTES = ['id', 'fence-effects', 'declared', ...SPECIAL_KINDS]; /* Checks for unknown attributes on steps. @@ -18,6 +20,13 @@ export default function (report: Reporter, stepSeq: Seq | null, node: OrderedLis line: attr.location.start.line, column: attr.location.start.column, }); + } else if (attr.value !== '' && SPECIAL_KINDS.includes(attr.key)) { + report({ + ruleId, + message: `step attribute ${JSON.stringify(attr.key)} should not have a value`, + line: attr.location.start.line, + column: attr.location.start.column + attr.key.length + 2, // =" + }); } } } diff --git a/test/baselines/generated-reference/multipage.html/multipage/index.html b/test/baselines/generated-reference/multipage.html/multipage/index.html index 6563d2a4..7343f286 100644 --- a/test/baselines/generated-reference/multipage.html/multipage/index.html +++ b/test/baselines/generated-reference/multipage.html/multipage/index.html @@ -1,7 +1,7 @@ - +