Skip to content

Commit f7d2f4c

Browse files
Add --max-clause-depth option (#656)
1 parent 717ed45 commit f7d2f4c

File tree

9 files changed

+170
-12
lines changed

9 files changed

+170
-12
lines changed

spec/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ <h1>Options</h1>
5555
<tr><td>`--assets-dir`</td><td>`assetsDir`</td><td>Directory in which to place assets when using `--assets=external`. Defaults to "assets".</td></tr>
5656
<tr><td>`--lint-spec`</td><td>`lintSpec`</td><td>Enforce some style and correctness checks.</td></tr>
5757
<tr><td>`--error-formatter`</td><td></td><td>The <a href="https://eslint.org/docs/user-guide/formatters/">eslint formatter</a> to be used for printing warnings and errors when using `--verbose`. Either the name of a built-in eslint formatter or the package name of an installed eslint compatible formatter.</td></tr>
58+
<tr><td>`--max-clause-depth N`</td><td></td><td>Warn when clauses exceed a nesting depth of N, and cause those clauses to be numbered by incrementing their parent clause's number rather than by nesting a new number within their parent clause.</td></tr>
5859
<tr><td>`--strict`</td><td></td><td>Exit with an error if there are warnings. Cannot be used with `--watch`.</td></tr>
5960
<tr><td>`--multipage`</td><td></td><td>Emit a distinct page for each top-level clause.</td></tr>
6061
</table>

src/args.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ export const options = [
6767
description:
6868
'The formatter for warnings and errors; either a path prefixed with "." or "./", or package name, of an installed eslint compatible formatter (default: eslint-formatter-codeframe)',
6969
},
70+
{
71+
name: 'max-clause-depth',
72+
type: Number,
73+
description:
74+
'The maximum nesting depth for clauses; exceeding this will cause a warning. Defaults to no limit.',
75+
},
7076
{
7177
name: 'multipage',
7278
type: Boolean,

src/clauseNums.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export default function iterator(spec: Spec): ClauseNumberIterator {
99
const ids: (string | number[])[] = [];
1010
let inAnnex = false;
1111
let currentLevel = 0;
12+
let hasWarnedForExcessNesting = false;
13+
const MAX_LEVELS = spec.opts.maxClauseDepth ?? Infinity;
1214

1315
return {
1416
next(clauseStack: Clause[], node: HTMLElement) {
@@ -22,28 +24,47 @@ export default function iterator(spec: Spec): ClauseNumberIterator {
2224
message: 'clauses cannot follow annexes',
2325
});
2426
}
25-
if (level - currentLevel > 1) {
27+
if (level - currentLevel > 1 && (level < MAX_LEVELS || currentLevel < MAX_LEVELS - 1)) {
2628
spec.warn({
2729
type: 'node',
2830
node,
29-
ruleId: 'skipped-caluse',
31+
ruleId: 'skipped-clause',
3032
message: 'clause is being numbered without numbering its parent clause',
3133
});
3234
}
35+
if (!hasWarnedForExcessNesting && level + 1 > (spec.opts.maxClauseDepth ?? Infinity)) {
36+
spec.warn({
37+
type: 'node',
38+
node,
39+
ruleId: 'max-clause-depth',
40+
message: `clause exceeds maximum nesting depth of ${spec.opts.maxClauseDepth}`,
41+
});
42+
hasWarnedForExcessNesting = true;
43+
}
3344

3445
const nextNum = annex ? nextAnnexNum : nextClauseNum;
3546

36-
if (level === currentLevel) {
37-
ids[currentLevel] = nextNum(clauseStack, node);
38-
} else if (level > currentLevel) {
39-
ids.push(nextNum(clauseStack, node));
47+
if (level >= MAX_LEVELS) {
48+
if (ids.length === MAX_LEVELS) {
49+
const lastLevelIndex = MAX_LEVELS - 1;
50+
const lastLevel = ids[lastLevelIndex] as number[];
51+
lastLevel[lastLevel.length - 1]++;
52+
} else {
53+
while (ids.length < MAX_LEVELS) {
54+
ids.push([1]);
55+
}
56+
}
4057
} else {
41-
ids.length = level + 1;
42-
ids[level] = nextNum(clauseStack, node);
58+
if (level === currentLevel) {
59+
ids[currentLevel] = nextNum(clauseStack, node);
60+
} else if (level > currentLevel) {
61+
ids.push(nextNum(clauseStack, node));
62+
} else {
63+
ids.length = level + 1;
64+
ids[level] = nextNum(clauseStack, node);
65+
}
4366
}
44-
45-
currentLevel = level;
46-
67+
currentLevel = Math.min(level, MAX_LEVELS - 1);
4768
return ids.flat().join('.');
4869
},
4970
};

src/cli.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ const build = debounce(async function build() {
113113
if (args['mark-effects']) {
114114
opts.markEffects = true;
115115
}
116+
if (args['max-clause-depth']) {
117+
opts.maxClauseDepth = args['max-clause-depth'];
118+
}
116119
if (args['no-toc'] != null) {
117120
opts.toc = !args['no-toc'];
118121
}

src/ecmarkup.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface Options {
3232
copyright?: boolean;
3333
date?: Date;
3434
location?: string;
35+
maxClauseDepth?: number;
3536
multipage?: boolean;
3637
extraBiblios?: ExportedBiblio[];
3738
contributors?: string;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!doctype html>
2+
<head><meta name="viewport" content="width=device-width, initial-scale=1"><meta charset="utf-8">
3+
<link rel="icon" href="img/favicon.ico">
4+
</head>
5+
6+
<body><div id="shortcuts-help">
7+
<ul>
8+
<li><span>Toggle shortcuts help</span><code>?</code></li>
9+
<li><span>Toggle "can call user code" annotations</span><code>u</code></li>
10+
11+
<li><span>Jump to search box</span><code>/</code></li>
12+
<li><span>Toggle pinning of the current clause</span><code>p</code></li>
13+
<li><span>Jump to the <i>n</i><sup>th</sup> pin</span><code>1-9</code></li>
14+
<li><span>Jump to the 10<sup>th</sup> pin</span><code>0</code></li>
15+
<li><span>Jump to the most recent link target</span><code>`</code></li>
16+
</ul></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" width="54" height="54">
17+
<title>Menu</title>
18+
<path stroke-width="10" stroke-linecap="round" d="M30,60 h60 M30,30 m0,5 h60 M30,90 m0,-5 h60"></path>
19+
</svg></div><div id="menu-spacer" class="menu-spacer"></div><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<button class="unpin-all">clear</button></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">+</span><a href="#one" title="One"><span class="secnum">1</span> One</a><ol class="toc"><li><span class="item-toggle">+</span><a href="#two" title="Two"><span class="secnum">1.1</span> Two</a><ol class="toc"><li><span class="item-toggle">+</span><a href="#three" title="Three"><span class="secnum">1.2</span> Three</a><ol class="toc"><li><span class="item-toggle-none"></span><a href="#four" title="four"><span class="secnum">1.3</span> four</a></li></ol></li><li><span class="item-toggle">+</span><a href="#three-again" title="Three Again"><span class="secnum">1.4</span> Three Again</a><ol class="toc"><li><span class="item-toggle-none"></span><a href="#four-again" title="Four Again"><span class="secnum">1.5</span> Four Again</a></li></ol></li></ol></li><li><span class="item-toggle-none"></span><a href="#two-again" title="Two Again"><span class="secnum">1.6</span> Two Again</a></li></ol></li><li><span class="item-toggle-none"></span><a href="#one-again" title="One Again"><span class="secnum">2</span> One Again</a></li></ol></div></div><div id="spec-container">
20+
21+
<emu-clause id="one">
22+
<h1><span class="secnum">1</span> One</h1>
23+
<emu-clause id="two">
24+
<h1><span class="secnum">1.1</span> Two</h1>
25+
<!-- EXPECT_WARNING { "ruleId": "max-clause-depth", "message": "clause exceeds maximum nesting depth of 2" } -->
26+
<emu-clause id="three">
27+
<h1><span class="secnum">1.2</span> Three</h1>
28+
<emu-clause id="four">
29+
<h1><span class="secnum">1.3</span> four</h1>
30+
</emu-clause>
31+
</emu-clause>
32+
<emu-clause id="three-again">
33+
<h1><span class="secnum">1.4</span> Three Again</h1>
34+
<emu-clause id="four-again">
35+
<h1><span class="secnum">1.5</span> Four Again</h1>
36+
</emu-clause>
37+
</emu-clause>
38+
</emu-clause>
39+
<emu-clause id="two-again">
40+
<h1><span class="secnum">1.6</span> Two Again</h1>
41+
</emu-clause>
42+
</emu-clause>
43+
<emu-clause id="one-again">
44+
<h1><span class="secnum">2</span> One Again</h1>
45+
</emu-clause>
46+
</div></body>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<head>
2+
<link rel="icon" href="img/favicon.ico">
3+
</head>
4+
5+
<pre class=metadata>
6+
copyright: false
7+
assets: none
8+
maxClauseDepth: 2
9+
</pre>
10+
11+
<emu-clause id="one">
12+
<h1>One</h1>
13+
<emu-clause id="two">
14+
<h1>Two</h1>
15+
<!-- EXPECT_WARNING { "ruleId": "max-clause-depth", "message": "clause exceeds maximum nesting depth of 2" } -->
16+
<emu-clause id="three">
17+
<h1>Three</h1>
18+
<emu-clause id="four">
19+
<h1>four</h1>
20+
</emu-clause>
21+
</emu-clause>
22+
<emu-clause id="three-again">
23+
<h1>Three Again</h1>
24+
<emu-clause id="four-again">
25+
<h1>Four Again</h1>
26+
</emu-clause>
27+
</emu-clause>
28+
</emu-clause>
29+
<emu-clause id="two-again">
30+
<h1>Two Again</h1>
31+
</emu-clause>
32+
</emu-clause>
33+
<emu-clause id="one-again">
34+
<h1>One Again</h1>
35+
</emu-clause>

test/clauseIds.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ describe('clause id generation', () => {
77
let iter;
88

99
beforeEach(() => {
10-
iter = sectionNums();
10+
iter = sectionNums({ opts: {} });
1111
});
1212

1313
specify('generating clause ids', () => {

test/errors.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,4 +1237,49 @@ ${M} </pre>
12371237
`);
12381238
});
12391239
});
1240+
1241+
describe('max clause depth', () => {
1242+
it('max depth', async () => {
1243+
await assertError(
1244+
positioned`
1245+
<emu-clause id="one">
1246+
<h1>One</h1>
1247+
${M}<emu-clause id="two">
1248+
<h1>Two</h1>
1249+
</emu-clause>
1250+
<emu-clause id="two-again">
1251+
<h1>Not warned</h1>
1252+
</emu-clause>
1253+
</emu-clause>
1254+
`,
1255+
{
1256+
ruleId: 'max-clause-depth',
1257+
nodeType: 'emu-clause',
1258+
message: 'clause exceeds maximum nesting depth of 1',
1259+
},
1260+
{
1261+
maxClauseDepth: 1,
1262+
},
1263+
);
1264+
});
1265+
1266+
it('negative', async () => {
1267+
await assertErrorFree(
1268+
`
1269+
<emu-clause id="one">
1270+
<h1>One</h1>
1271+
<emu-clause id="two">
1272+
<h1>Two</h1>
1273+
</emu-clause>
1274+
<emu-clause id="two-again">
1275+
<h1>Not warned</h1>
1276+
</emu-clause>
1277+
</emu-clause>
1278+
`,
1279+
{
1280+
maxClauseDepth: 2,
1281+
},
1282+
);
1283+
});
1284+
});
12401285
});

0 commit comments

Comments
 (0)