diff --git a/spec/index.html b/spec/index.html index 6125735e..e3bf0e95 100644 --- a/spec/index.html +++ b/spec/index.html @@ -55,6 +55,7 @@

Options

`--assets-dir``assetsDir`Directory in which to place assets when using `--assets=external`. Defaults to "assets". `--lint-spec``lintSpec`Enforce some style and correctness checks. `--error-formatter`The eslint formatter 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. + `--max-clause-depth N`Warn when clauses exceed a nesting depth of N. `--strict`Exit with an error if there are warnings. Cannot be used with `--watch`. `--multipage`Emit a distinct page for each top-level clause. diff --git a/src/args.ts b/src/args.ts index f7aa0277..9c4453cc 100644 --- a/src/args.ts +++ b/src/args.ts @@ -67,6 +67,12 @@ export const options = [ description: '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)', }, + { + name: 'max-clause-depth', + type: Number, + description: + 'The maximum nesting depth for clauses; exceeding this will cause a warning. Defaults to no limit.', + }, { name: 'multipage', type: Boolean, diff --git a/src/clauseNums.ts b/src/clauseNums.ts index f77f1631..aec3334d 100644 --- a/src/clauseNums.ts +++ b/src/clauseNums.ts @@ -9,6 +9,7 @@ export default function iterator(spec: Spec): ClauseNumberIterator { const ids: (string | number[])[] = []; let inAnnex = false; let currentLevel = 0; + let hasWarnedForExcessNesting = false; return { next(clauseStack: Clause[], node: HTMLElement) { @@ -26,10 +27,19 @@ export default function iterator(spec: Spec): ClauseNumberIterator { spec.warn({ type: 'node', node, - ruleId: 'skipped-caluse', + ruleId: 'skipped-clause', message: 'clause is being numbered without numbering its parent clause', }); } + if (!hasWarnedForExcessNesting && level + 1 > (spec.opts.maxClauseDepth ?? Infinity)) { + spec.warn({ + type: 'node', + node, + ruleId: 'max-clause-depth', + message: `clause exceeds maximum nesting depth of ${spec.opts.maxClauseDepth}`, + }); + hasWarnedForExcessNesting = true; + } const nextNum = annex ? nextAnnexNum : nextClauseNum; diff --git a/src/cli.ts b/src/cli.ts index cf96ee93..fd038dbc 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -113,6 +113,9 @@ const build = debounce(async function build() { if (args['mark-effects']) { opts.markEffects = true; } + if (args['max-clause-depth']) { + opts.maxClauseDepth = args['max-clause-depth']; + } if (args['no-toc'] != null) { opts.toc = !args['no-toc']; } diff --git a/src/ecmarkup.ts b/src/ecmarkup.ts index 34aae168..815b37e8 100644 --- a/src/ecmarkup.ts +++ b/src/ecmarkup.ts @@ -32,6 +32,7 @@ export interface Options { copyright?: boolean; date?: Date; location?: string; + maxClauseDepth?: number; multipage?: boolean; extraBiblios?: ExportedBiblio[]; contributors?: string; diff --git a/test/errors.js b/test/errors.js index a99bfb5c..a4fc5275 100644 --- a/test/errors.js +++ b/test/errors.js @@ -1237,4 +1237,49 @@ ${M} `); }); }); + + describe('max clause depth', () => { + it('max depth', async () => { + await assertError( + positioned` + +

One

+ ${M} +

Two

+
+ +

Not warned

+
+
+ `, + { + ruleId: 'max-clause-depth', + nodeType: 'emu-clause', + message: 'clause exceeds maximum nesting depth of 1', + }, + { + maxClauseDepth: 1, + }, + ); + }); + + it('negative', async () => { + await assertErrorFree( + ` + +

One

+ +

Two

+
+ +

Not warned

+
+
+ `, + { + maxClauseDepth: 2, + }, + ); + }); + }); });