From 66e852c17881bc00401d9b1fc193f964b57cea32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Sun, 24 Jul 2022 09:38:57 +0200 Subject: [PATCH 1/3] Allow specifying custom clause numbers --- src/clauseNums.ts | 16 +++++- .../generated-reference/clauses.html | 18 ++++++- test/baselines/sources/clauses.html | 16 ++++++ test/clauseIds.js | 4 +- test/errors.js | 54 +++++++++++++++++++ 5 files changed, 103 insertions(+), 5 deletions(-) diff --git a/src/clauseNums.ts b/src/clauseNums.ts index dcec5c41..24631960 100644 --- a/src/clauseNums.ts +++ b/src/clauseNums.ts @@ -70,10 +70,22 @@ export default function iterator(spec: Spec): ClauseNumberIterator { return String.fromCharCode((ids[0]).charCodeAt(0) + 1); } - return nextClauseNum(clauseStack); + return nextClauseNum(clauseStack, node); } - function nextClauseNum({ length: level }: { length: number }) { + function nextClauseNum({ length: level }: { length: number }, node: HTMLElement) { + if (node.hasAttribute('number')) { + const num = Number(node.getAttribute('number')); + if (Number.isSafeInteger(num) && num > 0) return num; + + spec.warn({ + type: 'node', + node, + ruleId: 'invalid-clause-number', + message: 'clause numbers must be positive integers', + }); + } + if (ids[level] === undefined) return 1; return ids[level] + 1; } diff --git a/test/baselines/generated-reference/clauses.html b/test/baselines/generated-reference/clauses.html index 546bd263..c06e2f20 100644 --- a/test/baselines/generated-reference/clauses.html +++ b/test/baselines/generated-reference/clauses.html @@ -6,7 +6,7 @@
  • Jump to search box/
  • -

    Table of Contents

    1. Intro
      1. Sub Intro
    2. 1 Clause Foo(a, b)
      1. 1.1 Sub Clause
    3. A Annex
      1. A.1 Sub-annex
    +

    Intro

    Sub Intro

    @@ -23,6 +23,22 @@

    1.1 Sub Clause

    + +

    7 Explicit number

    + + +

    7.1 Automatic number inside explicit number

    +
    + + +

    7.4 Nested explicit number

    +
    + + +

    7.5 Automatic number after explicit number

    +
    +
    +

    A Annex

    diff --git a/test/baselines/sources/clauses.html b/test/baselines/sources/clauses.html index baf4fc00..31df1095 100644 --- a/test/baselines/sources/clauses.html +++ b/test/baselines/sources/clauses.html @@ -20,6 +20,22 @@

    Sub Clause

    + +

    Explicit number

    + + +

    Automatic number inside explicit number

    +
    + + +

    Nested explicit number

    +
    + + +

    Automatic number after explicit number

    +
    +
    +

    Annex

    diff --git a/test/clauseIds.js b/test/clauseIds.js index 2240667a..a2f495af 100644 --- a/test/clauseIds.js +++ b/test/clauseIds.js @@ -11,8 +11,8 @@ describe('clause id generation', () => { }); specify('generating clause ids', () => { - const CLAUSE = { nodeName: 'EMU-CLAUSE' }; - const ANNEX = { nodeName: 'EMU-ANNEX' }; + const CLAUSE = { nodeName: 'EMU-CLAUSE', hasAttribute: () => false }; + const ANNEX = { nodeName: 'EMU-ANNEX', hasAttribute: () => false }; assert.strictEqual(iter.next([], CLAUSE), '1'); assert.strictEqual(iter.next([{}], CLAUSE), '1.1'); assert.strictEqual(iter.next([{}], CLAUSE), '1.2'); diff --git a/test/errors.js b/test/errors.js index f6eb4ca3..f358490e 100644 --- a/test/errors.js +++ b/test/errors.js @@ -895,6 +895,60 @@ ${M} ); }); + it('invalid clause number', async () => { + await assertError( + positioned` + ${M} +

    Clause

    +
    + `, + { + ruleId: 'invalid-clause-number', + nodeType: 'emu-clause', + message: 'clause numbers must be positive integers', + } + ); + + await assertError( + positioned` + ${M} +

    Clause

    +
    + `, + { + ruleId: 'invalid-clause-number', + nodeType: 'emu-clause', + message: 'clause numbers must be positive integers', + } + ); + + await assertError( + positioned` + ${M} +

    Clause

    +
    + `, + { + ruleId: 'invalid-clause-number', + nodeType: 'emu-clause', + message: 'clause numbers must be positive integers', + } + ); + + await assertError( + positioned` + ${M} +

    Clause

    +
    + `, + { + ruleId: 'invalid-clause-number', + nodeType: 'emu-clause', + message: 'clause numbers must be positive integers', + } + ); + }); + it('biblio missing href', async () => { await assertError( positioned` From 14068c618500c555e2f7bb80ff9dafc7f52c7f0a Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Fri, 29 Jul 2022 12:53:33 -0700 Subject: [PATCH 2/3] add support for multi-step explicit numbers and more error handling --- src/clauseNums.ts | 70 ++++++++++++++---- .../generated-reference/clauses.html | 20 +++++- test/baselines/sources/clauses.html | 18 +++++ test/errors.js | 71 ++++++++++++++++--- 4 files changed, 154 insertions(+), 25 deletions(-) diff --git a/src/clauseNums.ts b/src/clauseNums.ts index 24631960..ce74228a 100644 --- a/src/clauseNums.ts +++ b/src/clauseNums.ts @@ -8,7 +8,7 @@ export interface ClauseNumberIterator { /*@internal*/ export default function iterator(spec: Spec): ClauseNumberIterator { - const ids: (string | number)[] = []; + const ids: (string | number[])[] = []; let inAnnex = false; let currentLevel = 0; @@ -46,12 +46,22 @@ export default function iterator(spec: Spec): ClauseNumberIterator { currentLevel = level; - return ids.join('.'); + return ids.flat().join('.'); }, }; - function nextAnnexNum(clauseStack: Clause[], node: HTMLElement): string | number { + function nextAnnexNum(clauseStack: Clause[], node: HTMLElement): string | number[] { const level = clauseStack.length; + if (level === 0 && node.hasAttribute('number')) { + spec.warn({ + type: 'attr', + node, + attr: 'number', + ruleId: 'annex-clause-number', + message: + 'top-level annexes do not support explicit numbers; if you need this, open a bug on ecmarkup', + }); + } if (!inAnnex) { if (level > 0) { spec.warn({ @@ -67,7 +77,7 @@ export default function iterator(spec: Spec): ClauseNumberIterator { } if (level === 0) { - return String.fromCharCode((ids[0]).charCodeAt(0) + 1); + return String.fromCharCode((ids[0] as string).charCodeAt(0) + 1); } return nextClauseNum(clauseStack, node); @@ -75,18 +85,48 @@ export default function iterator(spec: Spec): ClauseNumberIterator { function nextClauseNum({ length: level }: { length: number }, node: HTMLElement) { if (node.hasAttribute('number')) { - const num = Number(node.getAttribute('number')); - if (Number.isSafeInteger(num) && num > 0) return num; - - spec.warn({ - type: 'node', - node, - ruleId: 'invalid-clause-number', - message: 'clause numbers must be positive integers', - }); + const nums = node + .getAttribute('number')! + .split('.') + .map(n => Number(n)); + if (nums.length === 0 || nums.some(num => !Number.isSafeInteger(num) || num <= 0)) { + spec.warn({ + type: 'attr-value', + node, + attr: 'number', + ruleId: 'invalid-clause-number', + message: 'clause numbers must be positive integers or dotted lists of positive integers', + }); + } + if (ids[level] !== undefined) { + if (nums.length !== ids[level].length) { + spec.warn({ + type: 'attr-value', + node, + attr: 'number', + ruleId: 'invalid-clause-number', + message: + 'multi-step explicit clause numbers should not be mixed with single-step clause numbers in the same parent clause', + }); + } else if ( + nums.some((n, i) => n < ids[level][i]) || + nums.every((n, i) => n === ids[level][i]) + ) { + spec.warn({ + type: 'attr-value', + node, + attr: 'number', + ruleId: 'invalid-clause-number', + message: 'clause numbers should be strictly increasing', + }); + } + } + return nums; } - if (ids[level] === undefined) return 1; - return ids[level] + 1; + if (ids[level] === undefined) return [1]; + const head = (ids[level] as number[]).slice(0, -1); + const tail = (ids[level] as number[])[ids[level].length - 1]; + return [...head, tail + 1]; } } diff --git a/test/baselines/generated-reference/clauses.html b/test/baselines/generated-reference/clauses.html index c06e2f20..3b804a5a 100644 --- a/test/baselines/generated-reference/clauses.html +++ b/test/baselines/generated-reference/clauses.html @@ -6,7 +6,7 @@
  • Jump to search box/
  • -

    Table of Contents

    1. Intro
      1. Sub Intro
    2. 1 Clause Foo(a, b)
      1. 1.1 Sub Clause
    3. 7 Explicit number
      1. 7.1 Automatic number inside explicit number
      2. 7.4 Nested explicit number
      3. 7.5 Automatic number after explicit number
    4. A Annex
      1. A.1 Sub-annex
    +

    Intro

    Sub Intro

    @@ -37,6 +37,24 @@

    7.4 Nested explicit number

    7.5 Automatic number after explicit number

    + + +

    7.6 Multi-step explicit numbers

    + +

    7.6.1.1 Multi-step explicit numbers

    +
    + + +

    7.6.1.2 Automatic number after explicit number

    + +

    7.6.1.2.1 Nested clause after explicit multi-step number

    +
    +
    + + +

    7.6.2.1 Increasing multi-step explicit numbers

    +
    +
    diff --git a/test/baselines/sources/clauses.html b/test/baselines/sources/clauses.html index 31df1095..b13d4753 100644 --- a/test/baselines/sources/clauses.html +++ b/test/baselines/sources/clauses.html @@ -34,6 +34,24 @@

    Nested explicit number

    Automatic number after explicit number

    + + +

    Multi-step explicit numbers

    + +

    Multi-step explicit numbers

    +
    + + +

    Automatic number after explicit number

    + +

    Nested clause after explicit multi-step number

    +
    +
    + + +

    Increasing multi-step explicit numbers

    +
    +
    diff --git a/test/errors.js b/test/errors.js index f358490e..71338b90 100644 --- a/test/errors.js +++ b/test/errors.js @@ -898,53 +898,106 @@ ${M} it('invalid clause number', async () => { await assertError( positioned` - ${M} +

    Clause

    `, { ruleId: 'invalid-clause-number', nodeType: 'emu-clause', - message: 'clause numbers must be positive integers', + message: 'clause numbers must be positive integers or dotted lists of positive integers', } ); await assertError( positioned` - ${M} +

    Clause

    `, { ruleId: 'invalid-clause-number', nodeType: 'emu-clause', - message: 'clause numbers must be positive integers', + message: 'clause numbers must be positive integers or dotted lists of positive integers', } ); await assertError( positioned` - ${M} +

    Clause

    `, { ruleId: 'invalid-clause-number', nodeType: 'emu-clause', - message: 'clause numbers must be positive integers', + message: 'clause numbers must be positive integers or dotted lists of positive integers', } ); await assertError( positioned` - ${M} +

    Clause

    - `, + + +

    Clause

    +
    + `, { ruleId: 'invalid-clause-number', nodeType: 'emu-clause', - message: 'clause numbers must be positive integers', + message: 'clause numbers should be strictly increasing', + } + ); + + await assertError( + positioned` + +

    Clause

    +
    + + +

    Clause

    +
    + `, + { + ruleId: 'invalid-clause-number', + nodeType: 'emu-clause', + message: 'clause numbers should be strictly increasing', + } + ); + + await assertError( + positioned` + +

    Clause

    +
    + + +

    Clause

    +
    + `, + { + ruleId: 'invalid-clause-number', + nodeType: 'emu-clause', + message: + 'multi-step explicit clause numbers should not be mixed with single-step clause numbers in the same parent clause', + } + ); + + await assertError( + positioned` + +

    Clause

    +
    + `, + { + ruleId: 'annex-clause-number', + nodeType: 'emu-annex', + message: + 'top-level annexes do not support explicit numbers; if you need this, open a bug on ecmarkup', } ); }); From d53082f2b964fde8bc84ddc149a25bbc364cbc84 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Fri, 29 Jul 2022 12:56:57 -0700 Subject: [PATCH 3/3] document clause numbers --- spec/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/index.html b/spec/index.html index 9b6de7c0..7172d0ed 100644 --- a/spec/index.html +++ b/spec/index.html @@ -171,6 +171,7 @@

    Attributes

    example: If present, the clause is an example.

    legacy: If present, the clause is Legacy.

    normative-optional: If present, the clause is Normative Optional.

    +

    number: Optional: An explicit clause number, overriding the default auto-incrementing number. Can be a nested number, as in `number="2.1"`.

    type: Optional: Type of feature described by the clause.

    • "abstract operation"