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 @@
-
+