Skip to content

Commit

Permalink
support marking individual steps as legacy/optional/deprecated
Browse files Browse the repository at this point in the history
  • Loading branch information
bakkot committed Apr 7, 2023
1 parent 34b0797 commit 2054ff8
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 19 deletions.
8 changes: 4 additions & 4 deletions css/elements.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand Down
7 changes: 6 additions & 1 deletion js/listNumbers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
5 changes: 5 additions & 0 deletions spec/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,11 @@ <h3>Result</h3>
<td>Originate, suppress, or constrain the scope of effects.</td>
<td>`&lt;emu-meta>`, `[fence-effects="user-code"]`</td>
</tr>
<tr>
<td>Step optionality</td>
<td>Mark a step as deprecated, normative-optional, or legacy.</td>
<td>`[normative-optional]`</td>
</tr>
</table>
</emu-table>

Expand Down
29 changes: 29 additions & 0 deletions src/Algorithm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
20 changes: 12 additions & 8 deletions src/Clause.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')) {
Expand Down Expand Up @@ -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);
Expand Down
13 changes: 11 additions & 2 deletions src/lint/rules/step-attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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, // ="
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<head><meta charset="utf-8">
<link rel="icon" href="../img/favicon.ico">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=6NkS69kx" defer=""></script><link rel="canonical" href="../#sec-intro"></head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=GmpNb4X1" defer=""></script><link rel="canonical" href="../#sec-intro"></head>
<body><div id="menu"><div id="menu-search"><input type="text" id="menu-search-box" placeholder="Search..."><div id="menu-search-results" class="inactive"></div></div><div id="menu-pins"><div class="menu-pane-header">Pins</div><ul id="menu-pins-list"></ul></div><div class="menu-pane-header">Table of Contents</div><div id="menu-toc"><ol class="toc"><li><span class="item-toggle-none"></span><a href="./#sec-intro" title="Intro">Intro</a></li><li><span class="item-toggle-none"></span><a href="second.html#sec-second" title="Second Clause"><span class="secnum">1</span> Second Clause</a></li><li><span class="item-toggle">◢</span><a href="third.html#sec-third" title="Third Clause"><span class="secnum">2</span> Third Clause</a><ol class="toc"><li><span class="item-toggle-none"></span><a href="third.html#sec-alg" title="Algorithm"><span class="secnum">2.1</span> Algorithm</a></li></ol></li></ol></div></div>
<div id="menu-spacer" class="menu-spacer"></div>
<div id="menu-toggle"><svg xmlns="http://www.w3.org/2000/svg" style="width:100%; height:100%; stroke:currentColor" viewBox="0 0 120 120">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<head><meta charset="utf-8">
<link rel="icon" href="../img/favicon.ico">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=6NkS69kx" defer=""></script><link rel="canonical" href="../#sec-second"></head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=GmpNb4X1" defer=""></script><link rel="canonical" href="../#sec-second"></head>
<body><div id="menu"><div id="menu-search"><input type="text" id="menu-search-box" placeholder="Search..."><div id="menu-search-results" class="inactive"></div></div><div id="menu-pins"><div class="menu-pane-header">Pins</div><ul id="menu-pins-list"></ul></div><div class="menu-pane-header">Table of Contents</div><div id="menu-toc"><ol class="toc"><li><span class="item-toggle-none"></span><a href="./#sec-intro" title="Intro">Intro</a></li><li><span class="item-toggle-none"></span><a href="second.html#sec-second" title="Second Clause"><span class="secnum">1</span> Second Clause</a></li><li><span class="item-toggle">◢</span><a href="third.html#sec-third" title="Third Clause"><span class="secnum">2</span> Third Clause</a><ol class="toc"><li><span class="item-toggle-none"></span><a href="third.html#sec-alg" title="Algorithm"><span class="secnum">2.1</span> Algorithm</a></li></ol></li></ol></div></div>
<div id="menu-spacer" class="menu-spacer"></div>
<div id="menu-toggle"><svg xmlns="http://www.w3.org/2000/svg" style="width:100%; height:100%; stroke:currentColor" viewBox="0 0 120 120">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!doctype html>
<head><meta charset="utf-8">
<link rel="icon" href="../img/favicon.ico">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=6NkS69kx" defer=""></script><link rel="canonical" href="../#sec-third"></head>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.0.1/styles/base16/solarized-light.min.css"><link rel="stylesheet" href="../ecmarkup.css"><script src="multipage.js?cache=9g6w4CbH" defer=""></script><script src="../ecmarkup.js?cache=GmpNb4X1" defer=""></script><link rel="canonical" href="../#sec-third"></head>
<body><div id="menu"><div id="menu-search"><input type="text" id="menu-search-box" placeholder="Search..."><div id="menu-search-results" class="inactive"></div></div><div id="menu-pins"><div class="menu-pane-header">Pins</div><ul id="menu-pins-list"></ul></div><div class="menu-pane-header">Table of Contents</div><div id="menu-toc"><ol class="toc"><li><span class="item-toggle-none"></span><a href="./#sec-intro" title="Intro">Intro</a></li><li><span class="item-toggle-none"></span><a href="second.html#sec-second" title="Second Clause"><span class="secnum">1</span> Second Clause</a></li><li><span class="item-toggle">◢</span><a href="third.html#sec-third" title="Third Clause"><span class="secnum">2</span> Third Clause</a><ol class="toc"><li><span class="item-toggle-none"></span><a href="third.html#sec-alg" title="Algorithm"><span class="secnum">2.1</span> Algorithm</a></li></ol></li></ol></div></div>
<div id="menu-spacer" class="menu-spacer"></div>
<div id="menu-toggle"><svg xmlns="http://www.w3.org/2000/svg" style="width:100%; height:100%; stroke:currentColor" viewBox="0 0 120 120">
Expand Down
29 changes: 29 additions & 0 deletions test/baselines/generated-reference/optional-parts.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!doctype html>
<head><meta charset="utf-8"></head><body><div id="shortcuts-help">
<ul>
<li><span>Toggle shortcuts help</span><code>?</code></li>
<li><span>Toggle "can call user code" annotations</span><code>u</code></li>

<li><span>Jump to search box</span><code>/</code></li>
</ul></div><div id="spec-container">
<emu-note>This is a note. Since it is outside a clause it is unstyled.</emu-note>

<emu-clause id="conformance">
<h1><span class="secnum">1</span> Definitions of Normative Optional etc</h1>
<p><dfn id="normative-optional">Normative Optional</dfn> is a defined term, as is <dfn id="legacy">Legacy</dfn>. For comparison, "Deprecated" is not.</p>
</emu-clause>

<emu-clause id="example-normative-optional" normative-optional=""><div class="attributes-tag"><emu-xref href="#normative-optional" id="_ref_0"><a href="#normative-optional">Normative Optional</a></emu-xref></div>
<h1><span class="secnum">2</span> Example Normative Optional Clause</h1>
<p>This clause is normative optional.</p>
</emu-clause>

<emu-clause id="example-normative-optional-legacy" normative-optional="" legacy=""><div class="attributes-tag"><emu-xref href="#normative-optional" id="_ref_1"><a href="#normative-optional">Normative Optional</a></emu-xref>, <emu-xref href="#legacy" id="_ref_2"><a href="#legacy">Legacy</a></emu-xref></div>
<h1><span class="secnum">3</span> Example Legacy Clause</h1>
<p>This clause is legacy.</p>
</emu-clause>

<emu-clause id="example-alg">
<h1><span class="secnum">4</span> Example algorithm with marked steps.</h1>
<emu-alg><ol><li>This step is required.</li><li normative-optional=""><div class="attributes-tag"><emu-xref href="#normative-optional" id="_ref_3"><a href="#normative-optional">Normative Optional</a></emu-xref></div>This step is optional.</li><li normative-optional="" legacy=""><div class="attributes-tag"><emu-xref href="#normative-optional" id="_ref_4"><a href="#normative-optional">Normative Optional</a></emu-xref>, <emu-xref href="#legacy" id="_ref_5"><a href="#legacy">Legacy</a></emu-xref></div>This step is legacy.</li><li legacy=""><div class="attributes-tag"><emu-xref href="#legacy" id="_ref_6"><a href="#legacy">Legacy</a></emu-xref></div>This step is only legacy, but has substeps.<ol><li>This is a substep.</li></ol></li><li deprecated=""><div class="attributes-tag">Deprecated</div>This step is optional.</li><li>This step is required.</li></ol></emu-alg></emu-clause>
</div></body>
33 changes: 33 additions & 0 deletions test/baselines/sources/optional-parts.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<pre class=metadata>
toc: false
copyright: false
assets: none
</pre>
<emu-note>This is a note. Since it is outside a clause it is unstyled.</emu-note>

<emu-clause id="conformance">
<h1>Definitions of Normative Optional etc</h1>
<p><dfn id="normative-optional">Normative Optional</dfn> is a defined term, as is <dfn id="legacy">Legacy</dfn>. For comparison, "Deprecated" is not.</p>
</emu-clause>

<emu-clause id="example-normative-optional" normative-optional>
<h1>Example Normative Optional Clause</h1>
<p>This clause is normative optional.</p>
</emu-clause>

<emu-clause id="example-normative-optional-legacy" normative-optional legacy>
<h1>Example Legacy Clause</h1>
<p>This clause is legacy.</p>
</emu-clause>

<emu-clause id="example-alg">
<h1>Example algorithm with marked steps.</h1>
<emu-alg>
1. This step is required.
1. [normative-optional=""] This step is optional.
1. [normative-optional="", legacy=""] This step is legacy.
1. [legacy=""] This step is only legacy, but has substeps.
1. This is a substep.
1. [deprecated=""] This step is optional.
1. This step is required.
</emu-clause>
15 changes: 14 additions & 1 deletion test/lint-algorithms.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ describe('linting algorithms', () => {
});

describe('step attributes', () => {
const ruleId = 'unknown-step-attribute';
const ruleId = 'step-attribute';
it('rejects unknown attributes', async () => {
await assertLint(
positioned`<emu-alg>
Expand All @@ -423,6 +423,19 @@ describe('linting algorithms', () => {
}
);
});

it('rejects values on attributes which do not make use of them', async () => {
await assertLint(
positioned`<emu-alg>
1. [legacy="${M}foo"] Step.
</emu-alg>`,
{
ruleId,
nodeType,
message: 'step attribute "legacy" should not have a value',
}
);
});
});

describe('for each element', () => {
Expand Down

0 comments on commit 2054ff8

Please sign in to comment.