@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.
Files changed (138) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/README.md +411 -7
  4. package/dist/bin.d.ts +20 -0
  5. package/dist/bin.d.ts.map +1 -0
  6. package/dist/bin.js +26 -0
  7. package/dist/bin.js.map +1 -0
  8. package/dist/cli/corpus-loader.d.ts +198 -0
  9. package/dist/cli/corpus-loader.d.ts.map +1 -0
  10. package/dist/cli/corpus-loader.js +351 -0
  11. package/dist/cli/corpus-loader.js.map +1 -0
  12. package/dist/cli/corpus-statistics.d.ts +113 -0
  13. package/dist/cli/corpus-statistics.d.ts.map +1 -0
  14. package/dist/cli/corpus-statistics.js +119 -0
  15. package/dist/cli/corpus-statistics.js.map +1 -0
  16. package/dist/cli/diagnostic-reporter.d.ts +103 -0
  17. package/dist/cli/diagnostic-reporter.d.ts.map +1 -0
  18. package/dist/cli/diagnostic-reporter.js +254 -0
  19. package/dist/cli/diagnostic-reporter.js.map +1 -0
  20. package/dist/cli/project-ignore.d.ts +113 -0
  21. package/dist/cli/project-ignore.d.ts.map +1 -0
  22. package/dist/cli/project-ignore.js +199 -0
  23. package/dist/cli/project-ignore.js.map +1 -0
  24. package/dist/cli/safe-text.d.ts +25 -0
  25. package/dist/cli/safe-text.d.ts.map +1 -0
  26. package/dist/cli/safe-text.js +34 -0
  27. package/dist/cli/safe-text.js.map +1 -0
  28. package/dist/cli/supervisor.d.ts +108 -0
  29. package/dist/cli/supervisor.d.ts.map +1 -0
  30. package/dist/cli/supervisor.js +139 -0
  31. package/dist/cli/supervisor.js.map +1 -0
  32. package/dist/cli.d.ts +247 -0
  33. package/dist/cli.d.ts.map +1 -0
  34. package/dist/cli.js +591 -0
  35. package/dist/cli.js.map +1 -0
  36. package/dist/corpus-validator.d.ts +183 -0
  37. package/dist/corpus-validator.d.ts.map +1 -0
  38. package/dist/corpus-validator.js +479 -0
  39. package/dist/corpus-validator.js.map +1 -0
  40. package/dist/document-identity-rule.d.ts +198 -0
  41. package/dist/document-identity-rule.d.ts.map +1 -0
  42. package/dist/document-identity-rule.js +244 -0
  43. package/dist/document-identity-rule.js.map +1 -0
  44. package/dist/ecr.d.ts +103 -0
  45. package/dist/ecr.d.ts.map +1 -0
  46. package/dist/ecr.js +216 -0
  47. package/dist/ecr.js.map +1 -0
  48. package/dist/glob-pattern.d.ts +94 -0
  49. package/dist/glob-pattern.d.ts.map +1 -0
  50. package/dist/glob-pattern.js +188 -0
  51. package/dist/glob-pattern.js.map +1 -0
  52. package/dist/heading-source-form.d.ts +77 -0
  53. package/dist/heading-source-form.d.ts.map +1 -0
  54. package/dist/heading-source-form.js +129 -0
  55. package/dist/heading-source-form.js.map +1 -0
  56. package/dist/identifier-grammar.d.ts +212 -0
  57. package/dist/identifier-grammar.d.ts.map +1 -0
  58. package/dist/identifier-grammar.js +273 -0
  59. package/dist/identifier-grammar.js.map +1 -0
  60. package/dist/index.d.ts +19 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +11 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/inline-reference-rule.d.ts +557 -0
  65. package/dist/inline-reference-rule.d.ts.map +1 -0
  66. package/dist/inline-reference-rule.js +802 -0
  67. package/dist/inline-reference-rule.js.map +1 -0
  68. package/dist/meta-documents.d.ts +91 -0
  69. package/dist/meta-documents.d.ts.map +1 -0
  70. package/dist/meta-documents.js +133 -0
  71. package/dist/meta-documents.js.map +1 -0
  72. package/dist/per-document-visitor.d.ts +510 -0
  73. package/dist/per-document-visitor.d.ts.map +1 -0
  74. package/dist/per-document-visitor.js +1032 -0
  75. package/dist/per-document-visitor.js.map +1 -0
  76. package/dist/references-section-rule.d.ts +482 -0
  77. package/dist/references-section-rule.d.ts.map +1 -0
  78. package/dist/references-section-rule.js +777 -0
  79. package/dist/references-section-rule.js.map +1 -0
  80. package/dist/section-hierarchy-rule.d.ts +299 -0
  81. package/dist/section-hierarchy-rule.d.ts.map +1 -0
  82. package/dist/section-hierarchy-rule.js +482 -0
  83. package/dist/section-hierarchy-rule.js.map +1 -0
  84. package/dist/source-alignment.d.ts +41 -0
  85. package/dist/source-alignment.d.ts.map +1 -0
  86. package/dist/source-alignment.js +166 -0
  87. package/dist/source-alignment.js.map +1 -0
  88. package/dist/source-lines.d.ts +69 -0
  89. package/dist/source-lines.d.ts.map +1 -0
  90. package/dist/source-lines.js +104 -0
  91. package/dist/source-lines.js.map +1 -0
  92. package/dist/spec-version.d.ts +19 -0
  93. package/dist/spec-version.d.ts.map +1 -0
  94. package/dist/spec-version.js +19 -0
  95. package/dist/spec-version.js.map +1 -0
  96. package/dist/types.d.ts +219 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +11 -0
  99. package/dist/types.js.map +1 -0
  100. package/examples/docs/0. Orientation/0.0 - System Overview.md +34 -0
  101. package/examples/docs/0. Orientation/0.0.1 - Glossary.md +21 -0
  102. package/examples/docs/0. Orientation/0.0.2 - Quality Gates.md +22 -0
  103. package/examples/docs/3. Ingestion/3.1 - Ingestion - Validation Rules.md +33 -0
  104. package/examples/docs/4. Payments/4.10 - Settlement.md +22 -0
  105. package/examples/docs/4. Payments/4.2 - Payment Processing Contract.md +33 -0
  106. package/examples/docs/8. Orchestration/8.1 - Workflow Orchestration Contract.md +38 -0
  107. package/examples/docs/8. Orchestration/8.1.3 - Retry Policy.md +17 -0
  108. package/examples/docs/8. Orchestration/8.10 - Dead Letter Queue.md +20 -0
  109. package/examples/docs/README.md +7 -0
  110. package/package.json +65 -7
  111. package/protocol/navigation-protocol.md +223 -0
  112. package/spec/v2/1 - ECR - Structural Specification.md +1467 -0
  113. package/spec/v2/2 - ECR - User Guide.md +845 -0
  114. package/spec/v2/3 - Design Rationale - The Section Separator.md +243 -0
  115. package/src/bin.ts +30 -0
  116. package/src/cli/corpus-loader.ts +482 -0
  117. package/src/cli/corpus-statistics.ts +196 -0
  118. package/src/cli/diagnostic-reporter.ts +344 -0
  119. package/src/cli/project-ignore.ts +238 -0
  120. package/src/cli/safe-text.ts +39 -0
  121. package/src/cli/supervisor.ts +204 -0
  122. package/src/cli.ts +836 -0
  123. package/src/corpus-validator.ts +642 -0
  124. package/src/document-identity-rule.ts +422 -0
  125. package/src/ecr.ts +298 -0
  126. package/src/glob-pattern.ts +242 -0
  127. package/src/heading-source-form.ts +173 -0
  128. package/src/identifier-grammar.ts +380 -0
  129. package/src/index.ts +78 -0
  130. package/src/inline-reference-rule.ts +1235 -0
  131. package/src/meta-documents.ts +159 -0
  132. package/src/per-document-visitor.ts +1528 -0
  133. package/src/references-section-rule.ts +1182 -0
  134. package/src/section-hierarchy-rule.ts +705 -0
  135. package/src/source-alignment.ts +209 -0
  136. package/src/source-lines.ts +123 -0
  137. package/src/spec-version.ts +19 -0
  138. 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
+ * `&fjlig;` produces two characters, an unknown name produces none, and
32
+ * `&#10;` 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"}