@timiagama/ecr 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +16 -0
- package/README.md +411 -7
- package/dist/bin.d.ts +20 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +26 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli/corpus-loader.d.ts +198 -0
- package/dist/cli/corpus-loader.d.ts.map +1 -0
- package/dist/cli/corpus-loader.js +351 -0
- package/dist/cli/corpus-loader.js.map +1 -0
- package/dist/cli/corpus-statistics.d.ts +113 -0
- package/dist/cli/corpus-statistics.d.ts.map +1 -0
- package/dist/cli/corpus-statistics.js +119 -0
- package/dist/cli/corpus-statistics.js.map +1 -0
- package/dist/cli/diagnostic-reporter.d.ts +103 -0
- package/dist/cli/diagnostic-reporter.d.ts.map +1 -0
- package/dist/cli/diagnostic-reporter.js +254 -0
- package/dist/cli/diagnostic-reporter.js.map +1 -0
- package/dist/cli/project-ignore.d.ts +113 -0
- package/dist/cli/project-ignore.d.ts.map +1 -0
- package/dist/cli/project-ignore.js +199 -0
- package/dist/cli/project-ignore.js.map +1 -0
- package/dist/cli/safe-text.d.ts +25 -0
- package/dist/cli/safe-text.d.ts.map +1 -0
- package/dist/cli/safe-text.js +34 -0
- package/dist/cli/safe-text.js.map +1 -0
- package/dist/cli/supervisor.d.ts +108 -0
- package/dist/cli/supervisor.d.ts.map +1 -0
- package/dist/cli/supervisor.js +139 -0
- package/dist/cli/supervisor.js.map +1 -0
- package/dist/cli.d.ts +247 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +591 -0
- package/dist/cli.js.map +1 -0
- package/dist/corpus-validator.d.ts +183 -0
- package/dist/corpus-validator.d.ts.map +1 -0
- package/dist/corpus-validator.js +479 -0
- package/dist/corpus-validator.js.map +1 -0
- package/dist/document-identity-rule.d.ts +198 -0
- package/dist/document-identity-rule.d.ts.map +1 -0
- package/dist/document-identity-rule.js +244 -0
- package/dist/document-identity-rule.js.map +1 -0
- package/dist/ecr.d.ts +103 -0
- package/dist/ecr.d.ts.map +1 -0
- package/dist/ecr.js +216 -0
- package/dist/ecr.js.map +1 -0
- package/dist/glob-pattern.d.ts +94 -0
- package/dist/glob-pattern.d.ts.map +1 -0
- package/dist/glob-pattern.js +188 -0
- package/dist/glob-pattern.js.map +1 -0
- package/dist/heading-source-form.d.ts +77 -0
- package/dist/heading-source-form.d.ts.map +1 -0
- package/dist/heading-source-form.js +129 -0
- package/dist/heading-source-form.js.map +1 -0
- package/dist/identifier-grammar.d.ts +212 -0
- package/dist/identifier-grammar.d.ts.map +1 -0
- package/dist/identifier-grammar.js +273 -0
- package/dist/identifier-grammar.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/inline-reference-rule.d.ts +557 -0
- package/dist/inline-reference-rule.d.ts.map +1 -0
- package/dist/inline-reference-rule.js +802 -0
- package/dist/inline-reference-rule.js.map +1 -0
- package/dist/meta-documents.d.ts +91 -0
- package/dist/meta-documents.d.ts.map +1 -0
- package/dist/meta-documents.js +133 -0
- package/dist/meta-documents.js.map +1 -0
- package/dist/per-document-visitor.d.ts +510 -0
- package/dist/per-document-visitor.d.ts.map +1 -0
- package/dist/per-document-visitor.js +1032 -0
- package/dist/per-document-visitor.js.map +1 -0
- package/dist/references-section-rule.d.ts +482 -0
- package/dist/references-section-rule.d.ts.map +1 -0
- package/dist/references-section-rule.js +777 -0
- package/dist/references-section-rule.js.map +1 -0
- package/dist/section-hierarchy-rule.d.ts +299 -0
- package/dist/section-hierarchy-rule.d.ts.map +1 -0
- package/dist/section-hierarchy-rule.js +482 -0
- package/dist/section-hierarchy-rule.js.map +1 -0
- package/dist/source-alignment.d.ts +41 -0
- package/dist/source-alignment.d.ts.map +1 -0
- package/dist/source-alignment.js +166 -0
- package/dist/source-alignment.js.map +1 -0
- package/dist/source-lines.d.ts +69 -0
- package/dist/source-lines.d.ts.map +1 -0
- package/dist/source-lines.js +104 -0
- package/dist/source-lines.js.map +1 -0
- package/dist/spec-version.d.ts +19 -0
- package/dist/spec-version.d.ts.map +1 -0
- package/dist/spec-version.js +19 -0
- package/dist/spec-version.js.map +1 -0
- package/dist/types.d.ts +219 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/examples/docs/0. Orientation/0.0 - System Overview.md +34 -0
- package/examples/docs/0. Orientation/0.0.1 - Glossary.md +21 -0
- package/examples/docs/0. Orientation/0.0.2 - Quality Gates.md +22 -0
- package/examples/docs/3. Ingestion/3.1 - Ingestion - Validation Rules.md +33 -0
- package/examples/docs/4. Payments/4.10 - Settlement.md +22 -0
- package/examples/docs/4. Payments/4.2 - Payment Processing Contract.md +33 -0
- package/examples/docs/8. Orchestration/8.1 - Workflow Orchestration Contract.md +38 -0
- package/examples/docs/8. Orchestration/8.1.3 - Retry Policy.md +17 -0
- package/examples/docs/8. Orchestration/8.10 - Dead Letter Queue.md +20 -0
- package/examples/docs/README.md +7 -0
- package/package.json +65 -7
- package/protocol/navigation-protocol.md +223 -0
- package/spec/v2/1 - ECR - Structural Specification.md +1467 -0
- package/spec/v2/2 - ECR - User Guide.md +845 -0
- package/spec/v2/3 - Design Rationale - The Section Separator.md +243 -0
- package/src/bin.ts +30 -0
- package/src/cli/corpus-loader.ts +482 -0
- package/src/cli/corpus-statistics.ts +196 -0
- package/src/cli/diagnostic-reporter.ts +344 -0
- package/src/cli/project-ignore.ts +238 -0
- package/src/cli/safe-text.ts +39 -0
- package/src/cli/supervisor.ts +204 -0
- package/src/cli.ts +836 -0
- package/src/corpus-validator.ts +642 -0
- package/src/document-identity-rule.ts +422 -0
- package/src/ecr.ts +298 -0
- package/src/glob-pattern.ts +242 -0
- package/src/heading-source-form.ts +173 -0
- package/src/identifier-grammar.ts +380 -0
- package/src/index.ts +78 -0
- package/src/inline-reference-rule.ts +1235 -0
- package/src/meta-documents.ts +159 -0
- package/src/per-document-visitor.ts +1528 -0
- package/src/references-section-rule.ts +1182 -0
- package/src/section-hierarchy-rule.ts +705 -0
- package/src/source-alignment.ts +209 -0
- package/src/source-lines.ts +123 -0
- package/src/spec-version.ts +19 -0
- package/src/types.ts +267 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section Hierarchy Rule (ECR102)
|
|
3
|
+
*
|
|
4
|
+
* Validates the section structure constraint defined in the ECR specification:
|
|
5
|
+
* - 1#9.4 -- Section Structure Rules
|
|
6
|
+
* - 1#9.8 -- Per-Document Structural Invariants ("SectionIDs unique within document")
|
|
7
|
+
*
|
|
8
|
+
* This rule consumes heading node data from an AST traversal and validates
|
|
9
|
+
* the section hierarchy for headings with depth \>= 2. It delegates identifier
|
|
10
|
+
* parsing and segment-count logic to {@link IdentifierGrammar} and emits
|
|
11
|
+
* {@link Diagnostic} objects for any violations.
|
|
12
|
+
*
|
|
13
|
+
* Validations performed:
|
|
14
|
+
* 1. A separator (any dash variant) must follow the identifier
|
|
15
|
+
* 2. Heading text prefix must parse as a valid SectionID
|
|
16
|
+
* 3. The SectionID's DocID must equal the document's DocID
|
|
17
|
+
* 4. The section path must have d - 1 segments, where d = heading depth
|
|
18
|
+
* 5. SectionIDs must be unique within the document (no duplicates)
|
|
19
|
+
* 6. Heading levels must not be skipped (e.g., H3 directly after H1 is invalid)
|
|
20
|
+
*
|
|
21
|
+
* Extracted artefacts:
|
|
22
|
+
* - {@link SectionNode} for each valid heading, with parentId derived from the heading stack
|
|
23
|
+
*/
|
|
24
|
+
import { IdentifierGrammar } from './identifier-grammar.js';
|
|
25
|
+
import { HEADING_SOURCE_FORM_CAUSE, HeadingSourceForm } from './heading-source-form.js';
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Rule identifier constant
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* Canonical rule identifier for the Section Hierarchy Rule.
|
|
31
|
+
*
|
|
32
|
+
* Referenced as [ECR102] in the ECR specification (1#9.4).
|
|
33
|
+
*/
|
|
34
|
+
export const SECTION_HIERARCHY_RULE_ID = 'ECR102';
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Diagnostic severity constant
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
/**
|
|
39
|
+
* The severity used for all Section Hierarchy Rule diagnostics.
|
|
40
|
+
*
|
|
41
|
+
* Per 1#9.8, violation of any per-document structural invariant is an ERROR.
|
|
42
|
+
*/
|
|
43
|
+
const SECTION_HIERARCHY_DIAGNOSTIC_SEVERITY = 'error';
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Separator pattern
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/**
|
|
48
|
+
* Matches the separator between a SectionID and its title: a dash with any
|
|
49
|
+
* surrounding whitespace.
|
|
50
|
+
*
|
|
51
|
+
* The dash may be a hyphen-minus (U+002D), an en dash (U+2013) or an em dash
|
|
52
|
+
* (U+2014); the variant carries no structural meaning.
|
|
53
|
+
*/
|
|
54
|
+
const SECTION_SEPARATOR_PATTERN = /\s*[-–—]\s*/;
|
|
55
|
+
/**
|
|
56
|
+
* Matches heading text that begins with a number, i.e. one that at least
|
|
57
|
+
* attempts an identifier.
|
|
58
|
+
*/
|
|
59
|
+
const STARTS_WITH_DIGIT_PATTERN = /^\s*\d/;
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Rule class
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/**
|
|
64
|
+
* Validates the Section Hierarchy Rule as defined in the ECR specification (1#9.4, 1#9.8).
|
|
65
|
+
*
|
|
66
|
+
* The rule enforces that every heading with depth \>= 2 has a valid SectionID
|
|
67
|
+
* whose DocID is the document's own, carries a separator, has a section path of
|
|
68
|
+
* one segment per heading level below the H1, and is unique within the document.
|
|
69
|
+
*
|
|
70
|
+
* For each valid heading, the rule extracts a {@link SectionNode} with its
|
|
71
|
+
* parentId derived from the heading stack (the nearest preceding heading at
|
|
72
|
+
* depth - 1).
|
|
73
|
+
*
|
|
74
|
+
* The root H1 heading is registered via {@link registerRootHeading} to establish
|
|
75
|
+
* the heading stack base and produce the root {@link SectionNode}.
|
|
76
|
+
*
|
|
77
|
+
* Usage:
|
|
78
|
+
* 1. Construct a rule instance with the document URI, established DocID,
|
|
79
|
+
* and an {@link IdentifierGrammar}.
|
|
80
|
+
* 2. Call {@link registerRootHeading} once with the H1 heading data to
|
|
81
|
+
* establish the root of the heading stack.
|
|
82
|
+
* 3. Call {@link evaluateHeading} for every heading node with depth \>= 2
|
|
83
|
+
* encountered during AST traversal.
|
|
84
|
+
* 4. Call {@link finalise} after all headings have been evaluated to obtain
|
|
85
|
+
* the complete result.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const grammar = new IdentifierGrammar();
|
|
90
|
+
* const rule = new SectionHierarchyRule({
|
|
91
|
+
* uri: 'file:///doc.md',
|
|
92
|
+
* docId: '3.1',
|
|
93
|
+
* grammar,
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* rule.registerRootHeading({ depth: 1, text: '3.1 - My Document' });
|
|
97
|
+
* rule.evaluateHeading({ depth: 2, text: '3.1#1 - Section One' });
|
|
98
|
+
* rule.evaluateHeading({ depth: 3, text: '3.1#1.1 - Sub-Section' });
|
|
99
|
+
* rule.evaluateHeading({ depth: 2, text: '3.1#2 - Section Two' });
|
|
100
|
+
*
|
|
101
|
+
* const result: SectionHierarchyRuleResult = rule.finalise();
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export class SectionHierarchyRule {
|
|
105
|
+
/** The opaque, host-provided URI identifying the document being validated. */
|
|
106
|
+
uri;
|
|
107
|
+
/** The established DocID for this document. */
|
|
108
|
+
docId;
|
|
109
|
+
/** The grammar instance used for SectionID parsing and validation. */
|
|
110
|
+
grammar;
|
|
111
|
+
/** Diagnostics accumulated during heading evaluation. */
|
|
112
|
+
collectedDiagnostics;
|
|
113
|
+
/** Section nodes extracted from valid headings, in traversal order. */
|
|
114
|
+
collectedSections;
|
|
115
|
+
/**
|
|
116
|
+
* Depth-aware heading stack for parent-child derivation.
|
|
117
|
+
*
|
|
118
|
+
* Maintained so that each entry's depth is strictly increasing from
|
|
119
|
+
* bottom to top. When a heading at depth d is encountered, all entries
|
|
120
|
+
* with depth \>= d are popped before the new entry is pushed.
|
|
121
|
+
*/
|
|
122
|
+
headingStack;
|
|
123
|
+
/**
|
|
124
|
+
* Set of SectionIDs already encountered, for duplicate detection.
|
|
125
|
+
*/
|
|
126
|
+
encounteredSectionIds;
|
|
127
|
+
/** Checks each heading's source line against the section recipe. */
|
|
128
|
+
headingSourceForm;
|
|
129
|
+
/**
|
|
130
|
+
* Constructs a new Section Hierarchy Rule evaluator.
|
|
131
|
+
*
|
|
132
|
+
* @param options - Configuration including the document URI, established DocID,
|
|
133
|
+
* and grammar instance
|
|
134
|
+
*/
|
|
135
|
+
constructor(options) {
|
|
136
|
+
this.uri = options.uri;
|
|
137
|
+
this.docId = options.docId;
|
|
138
|
+
this.grammar = options.grammar;
|
|
139
|
+
this.headingSourceForm = new HeadingSourceForm(options.sourceText);
|
|
140
|
+
this.collectedDiagnostics = [];
|
|
141
|
+
this.collectedSections = [];
|
|
142
|
+
this.headingStack = [];
|
|
143
|
+
this.encounteredSectionIds = new Set();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Registers the root H1 heading to establish the heading stack base.
|
|
147
|
+
*
|
|
148
|
+
* This produces the root {@link SectionNode} (id = DocID, headingDepth = 1,
|
|
149
|
+
* no parentId) and pushes the H1 onto the heading stack so that subsequent
|
|
150
|
+
* depth-2 headings can derive their parentId.
|
|
151
|
+
*
|
|
152
|
+
* Must be called exactly once, before any calls to {@link evaluateHeading}.
|
|
153
|
+
*
|
|
154
|
+
* @param headingNodeData - Data extracted from the H1 heading AST node.
|
|
155
|
+
* The depth must be 1. The title text is extracted
|
|
156
|
+
* from the heading content after the separator.
|
|
157
|
+
*/
|
|
158
|
+
registerRootHeading(headingNodeData) {
|
|
159
|
+
const title = this.extractHeadingTitle(headingNodeData.text);
|
|
160
|
+
const rootSection = {
|
|
161
|
+
id: this.docId,
|
|
162
|
+
title,
|
|
163
|
+
headingDepth: 1,
|
|
164
|
+
};
|
|
165
|
+
this.collectedSections.push(rootSection);
|
|
166
|
+
this.updateHeadingStack(this.docId, 1);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Evaluates a single heading node with depth \>= 2 against the section
|
|
170
|
+
* hierarchy constraints.
|
|
171
|
+
*
|
|
172
|
+
* For each heading, the method validates:
|
|
173
|
+
* 1. A separator (any dash variant) follows the identifier
|
|
174
|
+
* 2. The textual prefix parses as a valid SectionID
|
|
175
|
+
* 3. The SectionID's DocID equals the document's DocID
|
|
176
|
+
* 4. The section path has d - 1 segments, where d is the heading depth
|
|
177
|
+
* 5. The SectionID has not been seen before in this document
|
|
178
|
+
* 6. No heading levels are skipped (depth must not exceed parent depth + 1)
|
|
179
|
+
*
|
|
180
|
+
* When all validations pass, a {@link SectionNode} is extracted with its
|
|
181
|
+
* parentId derived from the heading stack (the nearest preceding heading
|
|
182
|
+
* at depth - 1), and the heading stack is updated.
|
|
183
|
+
*
|
|
184
|
+
* When any validation fails, an error diagnostic is emitted and the heading
|
|
185
|
+
* stack is still updated to maintain correct parent derivation for
|
|
186
|
+
* subsequent headings.
|
|
187
|
+
*
|
|
188
|
+
* Headings with depth === 1 are ignored (H1 is handled by
|
|
189
|
+
* {@link registerRootHeading}).
|
|
190
|
+
*
|
|
191
|
+
* @param headingNodeData - Data extracted from a heading AST node with depth \>= 2
|
|
192
|
+
*/
|
|
193
|
+
evaluateHeading(headingNodeData) {
|
|
194
|
+
if (headingNodeData.depth === 1) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const headingText = headingNodeData.text;
|
|
198
|
+
const depth = headingNodeData.depth;
|
|
199
|
+
const range = headingNodeData.range;
|
|
200
|
+
// 1. Validate separator
|
|
201
|
+
const separatorDiagnostic = this.validateHeadingSeparator(headingText, range);
|
|
202
|
+
if (separatorDiagnostic !== undefined) {
|
|
203
|
+
this.collectedDiagnostics.push(separatorDiagnostic);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// 2. Extract SectionID candidate from prefix
|
|
207
|
+
const candidate = this.extractSectionIdCandidate(headingText);
|
|
208
|
+
// 3. Parse candidate as SectionID
|
|
209
|
+
const parseResult = this.grammar.parseSectionId(candidate);
|
|
210
|
+
if (!parseResult.valid) {
|
|
211
|
+
const diagnostic = this.createDiagnostic(`Heading prefix "${candidate}" does not parse as a valid SectionID.`, range);
|
|
212
|
+
this.collectedDiagnostics.push(diagnostic);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const sectionId = parseResult.sectionId;
|
|
216
|
+
// 3a. Verify the heading is where the section recipe looks (1#9.11 rule
|
|
217
|
+
// 1). Reported, but the checks below still run and the section is still
|
|
218
|
+
// extracted: withholding it would turn every citation of it into an
|
|
219
|
+
// unresolved target, reporting one mistake many times over.
|
|
220
|
+
const obstruction = this.headingSourceForm.findObstruction(range, depth, sectionId);
|
|
221
|
+
if (obstruction !== undefined) {
|
|
222
|
+
this.collectedDiagnostics.push({
|
|
223
|
+
...this.createDiagnostic(HeadingSourceForm.explain(obstruction, depth, sectionId), range, sectionId),
|
|
224
|
+
data: { cause: HEADING_SOURCE_FORM_CAUSE, obstruction, sectionId },
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
// 4. Verify SectionID extends DocID
|
|
228
|
+
const extendsDocId = this.grammar.tellSectionIdExtendsDocId(sectionId, this.docId);
|
|
229
|
+
if (!extendsDocId) {
|
|
230
|
+
const diagnostic = this.createDiagnostic(`SectionID "${sectionId}" belongs to a different document. ` +
|
|
231
|
+
`Headings in this document must be written as "${this.docId}#<section path>".`, range, sectionId);
|
|
232
|
+
this.collectedDiagnostics.push(diagnostic);
|
|
233
|
+
this.updateHeadingStack(sectionId, depth);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// 5. Verify the section path has one segment per heading level below the H1
|
|
237
|
+
const actualPathLength = this.grammar.showSectionPathLength(sectionId);
|
|
238
|
+
const expectedPathLength = this.grammar.showExpectedSectionPathLength(depth);
|
|
239
|
+
if (actualPathLength !== expectedPathLength) {
|
|
240
|
+
const diagnostic = this.createDiagnostic(`SectionID "${sectionId}" has a section path of ${String(actualPathLength)} ` +
|
|
241
|
+
`segment(s), but heading depth ${String(depth)} requires ` +
|
|
242
|
+
`${String(expectedPathLength)}.`, range, sectionId);
|
|
243
|
+
this.collectedDiagnostics.push(diagnostic);
|
|
244
|
+
this.updateHeadingStack(sectionId, depth);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
// 6. Check for duplicate SectionIDs
|
|
248
|
+
if (this.encounteredSectionIds.has(sectionId)) {
|
|
249
|
+
const diagnostic = this.createDiagnostic(`Duplicate SectionID "${sectionId}". SectionIDs must be unique within a document.`, range, sectionId);
|
|
250
|
+
this.collectedDiagnostics.push(diagnostic);
|
|
251
|
+
this.updateHeadingStack(sectionId, depth);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// 7. Check for skipped heading levels
|
|
255
|
+
const currentTopDepth = this.peekHeadingStackDepth();
|
|
256
|
+
if (depth > currentTopDepth + 1) {
|
|
257
|
+
const skippedFrom = currentTopDepth;
|
|
258
|
+
const diagnostic = this.createDiagnostic(`Heading depth ${String(depth)} skips level(s) after depth ${String(skippedFrom)}. ` +
|
|
259
|
+
`Each heading level must be introduced before its children.`, range, sectionId);
|
|
260
|
+
this.collectedDiagnostics.push(diagnostic);
|
|
261
|
+
this.updateHeadingStack(sectionId, depth);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents -- Semantically distinct: parent may be DocID or SectionID
|
|
265
|
+
const parentId = this.lookUpParentId(depth);
|
|
266
|
+
// 8. Check that the section path continues its parent's path
|
|
267
|
+
const misplacedDiagnostic = this.validatePathAgainstParent(parseResult.sectionPath, parentId, sectionId, range);
|
|
268
|
+
if (misplacedDiagnostic !== undefined) {
|
|
269
|
+
this.collectedDiagnostics.push(misplacedDiagnostic);
|
|
270
|
+
this.updateHeadingStack(sectionId, depth);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// All validations passed — extract SectionNode
|
|
274
|
+
this.encounteredSectionIds.add(sectionId);
|
|
275
|
+
const title = this.extractHeadingTitle(headingText);
|
|
276
|
+
const sectionNode = {
|
|
277
|
+
id: sectionId,
|
|
278
|
+
title,
|
|
279
|
+
headingDepth: depth,
|
|
280
|
+
...(parentId !== undefined ? { parentId } : {}),
|
|
281
|
+
};
|
|
282
|
+
this.collectedSections.push(sectionNode);
|
|
283
|
+
this.updateHeadingStack(sectionId, depth);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Finalises the rule evaluation and produces the complete result.
|
|
287
|
+
*
|
|
288
|
+
* This method must be called after all heading nodes have been supplied
|
|
289
|
+
* via {@link registerRootHeading} and {@link evaluateHeading}. It returns
|
|
290
|
+
* all accumulated diagnostics and extracted section nodes.
|
|
291
|
+
*
|
|
292
|
+
* @returns The complete rule result including all diagnostics and extracted sections
|
|
293
|
+
*/
|
|
294
|
+
finalise() {
|
|
295
|
+
return {
|
|
296
|
+
diagnostics: this.collectedDiagnostics,
|
|
297
|
+
sections: this.collectedSections,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
// -------------------------------------------------------------------------
|
|
301
|
+
// Private helpers
|
|
302
|
+
// -------------------------------------------------------------------------
|
|
303
|
+
/**
|
|
304
|
+
* Validates that a heading string carries a separator, and returns an
|
|
305
|
+
* error diagnostic when none is found.
|
|
306
|
+
*
|
|
307
|
+
* Delegates to {@link IdentifierGrammar.validateSeparator}.
|
|
308
|
+
*
|
|
309
|
+
* @param headingText - The plain text content of the heading node
|
|
310
|
+
* @param range - Optional positional range for the diagnostic
|
|
311
|
+
* @returns A diagnostic if the separator is invalid, or `undefined` if valid
|
|
312
|
+
*/
|
|
313
|
+
validateHeadingSeparator(headingText, range) {
|
|
314
|
+
const separatorResult = this.grammar.validateSeparator(headingText);
|
|
315
|
+
if (separatorResult.valid) {
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
// A heading with no number at all is far more common than a numbered
|
|
319
|
+
// heading missing its dash, and deserves a message that says so.
|
|
320
|
+
if (!STARTS_WITH_DIGIT_PATTERN.test(headingText)) {
|
|
321
|
+
return this.createDiagnostic(`Heading "${headingText.trim()}" is not numbered. Every sub-heading carries a SectionID: ` +
|
|
322
|
+
`the DocID, "#", and a section path, for example "${this.docId}#1 - ${headingText.trim()}".`, range);
|
|
323
|
+
}
|
|
324
|
+
const diagnostic = this.createDiagnostic(`Heading has no recognisable separator. ` +
|
|
325
|
+
`Expected: a number, a dash (-, – or —), then the title.`, range);
|
|
326
|
+
return diagnostic;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Checks that a sub-section's path continues the path of the heading it
|
|
330
|
+
* sits under, so that a SectionID locates itself: `3.1#2.1` must sit under
|
|
331
|
+
* `3.1#2`, not under `3.1#1`.
|
|
332
|
+
*
|
|
333
|
+
* Only a parent that was itself extracted is checked against. A parent
|
|
334
|
+
* that failed validation has already been reported, and measuring its
|
|
335
|
+
* children against it would repeat that error rather than add one.
|
|
336
|
+
*
|
|
337
|
+
* @param sectionPath - The section path of the heading being evaluated
|
|
338
|
+
* @param parentId - The identifier of the heading it sits under, if any
|
|
339
|
+
* @param sectionId - The SectionID of the heading being evaluated
|
|
340
|
+
* @param range - Optional positional range for the diagnostic
|
|
341
|
+
* @returns A diagnostic if the path does not continue its parent's, or `undefined`
|
|
342
|
+
*/
|
|
343
|
+
validatePathAgainstParent(sectionPath,
|
|
344
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents -- Semantically distinct: parent may be DocID or SectionID
|
|
345
|
+
parentId, sectionId, range) {
|
|
346
|
+
if (parentId === undefined || !this.encounteredSectionIds.has(parentId)) {
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
const parentParse = this.grammar.parseSectionId(parentId);
|
|
350
|
+
if (!parentParse.valid) {
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
const segments = sectionPath.split('.');
|
|
354
|
+
const ownPrefix = segments.slice(0, -1).join('.');
|
|
355
|
+
if (ownPrefix === parentParse.sectionPath) {
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
const expectedId = `${this.docId}#${parentParse.sectionPath}.${segments[segments.length - 1] ?? ''}`;
|
|
359
|
+
return this.createDiagnostic(`SectionID "${sectionId}" sits under "${parentId}", so its section path must begin ` +
|
|
360
|
+
`"${parentParse.sectionPath}." (for example "${expectedId}").`, range, sectionId);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Extracts the SectionID candidate string from a heading's text.
|
|
364
|
+
*
|
|
365
|
+
* The candidate is the portion of the heading text before the first
|
|
366
|
+
* occurrence of a separator (any dash variant). If no separator
|
|
367
|
+
* is found, the entire trimmed text is returned as the candidate.
|
|
368
|
+
*
|
|
369
|
+
* @param headingText - The plain text content of the heading node
|
|
370
|
+
* @returns The SectionID candidate string
|
|
371
|
+
*/
|
|
372
|
+
extractSectionIdCandidate(headingText) {
|
|
373
|
+
const match = SECTION_SEPARATOR_PATTERN.exec(headingText);
|
|
374
|
+
if (match === null) {
|
|
375
|
+
return headingText.trim();
|
|
376
|
+
}
|
|
377
|
+
return headingText.substring(0, match.index).trim();
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Extracts the title text from a heading's text.
|
|
381
|
+
*
|
|
382
|
+
* The title is the portion of the heading text after the first
|
|
383
|
+
* occurrence of a separator (any dash variant). If no separator
|
|
384
|
+
* is found, an empty string is returned.
|
|
385
|
+
*
|
|
386
|
+
* @param headingText - The plain text content of the heading node
|
|
387
|
+
* @returns The extracted title string
|
|
388
|
+
*/
|
|
389
|
+
extractHeadingTitle(headingText) {
|
|
390
|
+
const match = SECTION_SEPARATOR_PATTERN.exec(headingText);
|
|
391
|
+
if (match === null) {
|
|
392
|
+
return '';
|
|
393
|
+
}
|
|
394
|
+
return headingText.substring(match.index + match[0].length);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Looks up the parent identifier from the heading stack for a heading
|
|
398
|
+
* at the given depth.
|
|
399
|
+
*
|
|
400
|
+
* The parent is the nearest preceding heading whose depth is exactly
|
|
401
|
+
* `headingDepth - 1`. Returns `undefined` if no such heading exists
|
|
402
|
+
* in the stack (which should not happen for well-formed documents
|
|
403
|
+
* after the root H1 is registered).
|
|
404
|
+
*
|
|
405
|
+
* @param headingDepth - The depth of the heading whose parent is sought
|
|
406
|
+
* @returns The parent's identifier, or `undefined` if not found
|
|
407
|
+
*/
|
|
408
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents -- Semantically distinct: parent may be DocID or SectionID
|
|
409
|
+
lookUpParentId(headingDepth) {
|
|
410
|
+
const targetDepth = headingDepth - 1;
|
|
411
|
+
for (let index = this.headingStack.length - 1; index >= 0; index--) {
|
|
412
|
+
const entry = this.headingStack[index];
|
|
413
|
+
if (entry?.depth === targetDepth) {
|
|
414
|
+
return entry.id;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Returns the depth of the topmost entry on the heading stack.
|
|
421
|
+
*
|
|
422
|
+
* Used to detect skipped heading levels: if the incoming heading depth
|
|
423
|
+
* exceeds the top-of-stack depth by more than 1, an intermediate heading
|
|
424
|
+
* level has been skipped.
|
|
425
|
+
*
|
|
426
|
+
* Returns 0 if the heading stack is empty (should not occur after
|
|
427
|
+
* {@link registerRootHeading} has been called).
|
|
428
|
+
*
|
|
429
|
+
* @returns The depth of the topmost heading stack entry, or 0 if empty
|
|
430
|
+
*/
|
|
431
|
+
peekHeadingStackDepth() {
|
|
432
|
+
if (this.headingStack.length === 0) {
|
|
433
|
+
return 0;
|
|
434
|
+
}
|
|
435
|
+
const topEntry = this.headingStack[this.headingStack.length - 1];
|
|
436
|
+
return topEntry?.depth ?? 0;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Updates the heading stack when a new heading is encountered.
|
|
440
|
+
*
|
|
441
|
+
* All entries with depth \>= the new heading's depth are removed,
|
|
442
|
+
* then the new heading is pushed. This ensures the stack always
|
|
443
|
+
* reflects the current structural nesting.
|
|
444
|
+
*
|
|
445
|
+
* @param id - The DocID or SectionID of the heading
|
|
446
|
+
* @param depth - The Markdown heading depth
|
|
447
|
+
*/
|
|
448
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents -- Semantically distinct: id may be DocID or SectionID
|
|
449
|
+
updateHeadingStack(id, depth) {
|
|
450
|
+
while (this.headingStack.length > 0) {
|
|
451
|
+
const topEntry = this.headingStack[this.headingStack.length - 1];
|
|
452
|
+
if (topEntry === undefined || topEntry.depth < depth) {
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
this.headingStack.pop();
|
|
456
|
+
}
|
|
457
|
+
this.headingStack.push({ id, depth });
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Creates a diagnostic object for the Section Hierarchy Rule.
|
|
461
|
+
*
|
|
462
|
+
* All diagnostics share the same rule ID ({@link SECTION_HIERARCHY_RULE_ID}),
|
|
463
|
+
* severity (error), and document URI.
|
|
464
|
+
*
|
|
465
|
+
* @param message - Human-readable description of the issue
|
|
466
|
+
* @param range - Optional positional range of the heading within the source document
|
|
467
|
+
* @param sectionId - Optional SectionID providing structural context
|
|
468
|
+
* @returns A fully populated diagnostic object
|
|
469
|
+
*/
|
|
470
|
+
createDiagnostic(message, range, sectionId) {
|
|
471
|
+
const diagnostic = {
|
|
472
|
+
ruleId: SECTION_HIERARCHY_RULE_ID,
|
|
473
|
+
severity: SECTION_HIERARCHY_DIAGNOSTIC_SEVERITY,
|
|
474
|
+
message,
|
|
475
|
+
uri: this.uri,
|
|
476
|
+
...(range !== undefined ? { range } : {}),
|
|
477
|
+
...(sectionId !== undefined ? { sectionId } : {}),
|
|
478
|
+
};
|
|
479
|
+
return diagnostic;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
//# sourceMappingURL=section-hierarchy-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"section-hierarchy-rule.js","sourceRoot":"","sources":["../src/section-hierarchy-rule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source Alignment
|
|
3
|
+
*
|
|
4
|
+
* Maps a parsed text node back to the raw Markdown it came from, character by
|
|
5
|
+
* character. The source-form checks of the navigation guarantee (1#9.11) need
|
|
6
|
+
* to know where a parsed character sits in the source, because a search reads
|
|
7
|
+
* the source: an identifier that parses correctly may be escaped, encoded or
|
|
8
|
+
* split across a line there, and no recipe would find it.
|
|
9
|
+
*
|
|
10
|
+
* Used by ECR104 for citations and by ECR103 for the relationship label of a
|
|
11
|
+
* References entry. Moved here unchanged from ECR104, where it was developed.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Maps each offset of a node's parsed text to its offset in the source.
|
|
15
|
+
*
|
|
16
|
+
* A text node's source differs from its parsed value in several ways, and
|
|
17
|
+
* the ones undone here are: backslash escapes, character references, the
|
|
18
|
+
* whitespace Markdown drops before a soft line break, and the continuation
|
|
19
|
+
* prefix a blockquote or list item repeats on every line after the first.
|
|
20
|
+
* Line endings are carried through unchanged by the parser, so both sides
|
|
21
|
+
* consume their own.
|
|
22
|
+
*
|
|
23
|
+
* This list is not claimed to be exhaustive. Each construct that was missed
|
|
24
|
+
* rejected some piece of ordinary prose, so the cases covered here are the
|
|
25
|
+
* ones with fixtures, and a failure to align is reported rather than
|
|
26
|
+
* guessed at.
|
|
27
|
+
*
|
|
28
|
+
* Character references are decoded with the same packages the Markdown
|
|
29
|
+
* parser itself uses, rather than guessed at. Earlier versions inferred an
|
|
30
|
+
* expansion's length from surrounding text and were wrong in every direction:
|
|
31
|
+
* `fj` produces two characters, an unknown name produces none, and
|
|
32
|
+
* ` ` produces a line ending. Guessing is what made each round of this
|
|
33
|
+
* work reject a different piece of ordinary prose.
|
|
34
|
+
*
|
|
35
|
+
* @param source - The node's raw source
|
|
36
|
+
* @param parsed - The node's parsed text
|
|
37
|
+
* @returns Source offset for each parsed offset, or `undefined` if the two
|
|
38
|
+
* could not be aligned
|
|
39
|
+
*/
|
|
40
|
+
export declare function alignParsedToSource(source: string, parsed: string): readonly number[] | undefined;
|
|
41
|
+
//# sourceMappingURL=source-alignment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-alignment.d.ts","sourceRoot":"","sources":["../src/source-alignment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuCH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,SAAS,MAAM,EAAE,GAAG,SAAS,CA8F/B"}
|