@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,183 @@
|
|
|
1
|
+
import type { CorpusDocumentEntry, CorpusResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validates a corpus of ECR documents for corpus-wide structural integrity.
|
|
4
|
+
*
|
|
5
|
+
* Performs Pass 2 of the ECR validation algorithm (per 1#11.2):
|
|
6
|
+
* global uniqueness of DocIDs and SectionIDs, resolution of reference
|
|
7
|
+
* targets, and resolution of inline reference targets.
|
|
8
|
+
*
|
|
9
|
+
* This class is stateless. Each invocation of {@link validateCorpus}
|
|
10
|
+
* operates purely over the provided document entries and produces a
|
|
11
|
+
* self-contained {@link CorpusResult}.
|
|
12
|
+
*/
|
|
13
|
+
declare class CorpusValidator {
|
|
14
|
+
/**
|
|
15
|
+
* Validates corpus-wide structural invariants across a set of
|
|
16
|
+
* per-document extraction results.
|
|
17
|
+
*
|
|
18
|
+
* Documents whose per-document lint result does not include a
|
|
19
|
+
* successfully extracted artefact are excluded from corpus-wide
|
|
20
|
+
* indexing and cross-reference resolution.
|
|
21
|
+
*
|
|
22
|
+
* Corpus-wide violations are reported as diagnostics with severity
|
|
23
|
+
* `'error'` (per 1#9.9, 1#12.2), except a stale References title, which
|
|
24
|
+
* is a `'warning'` (1#9.9 rule 5).
|
|
25
|
+
*
|
|
26
|
+
* @param documents - The per-document entries to validate as a corpus.
|
|
27
|
+
* @returns A {@link CorpusResult} containing per-document results,
|
|
28
|
+
* corpus-wide diagnostics, and an optional global identifier index.
|
|
29
|
+
*/
|
|
30
|
+
validateCorpus(documents: readonly CorpusDocumentEntry[]): CorpusResult;
|
|
31
|
+
/**
|
|
32
|
+
* Filters the input document entries to only those with successfully
|
|
33
|
+
* extracted structural artefacts.
|
|
34
|
+
*
|
|
35
|
+
* Documents without an `extracted` field on their lint result are excluded
|
|
36
|
+
* from corpus-wide indexing and cross-reference resolution.
|
|
37
|
+
*
|
|
38
|
+
* @param documents - The full set of corpus document entries.
|
|
39
|
+
* @returns An array of indexable documents with guaranteed `extracted` fields.
|
|
40
|
+
*/
|
|
41
|
+
private collectIndexableDocuments;
|
|
42
|
+
/**
|
|
43
|
+
* Builds a global DocID-to-URI index and emits error diagnostics for
|
|
44
|
+
* any duplicate DocIDs found across the corpus.
|
|
45
|
+
*
|
|
46
|
+
* Per 1#9.9: DocID MUST be globally unique. Violations are errors
|
|
47
|
+
* (per 1#12.2).
|
|
48
|
+
*
|
|
49
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
50
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
51
|
+
* @returns A map from each DocID to the URI of its first-encountered defining document.
|
|
52
|
+
*/
|
|
53
|
+
private buildDocIdIndex;
|
|
54
|
+
/**
|
|
55
|
+
* Builds a global SectionID-to-URI index and emits error diagnostics for
|
|
56
|
+
* any duplicate SectionIDs found across the corpus.
|
|
57
|
+
*
|
|
58
|
+
* Per 1#9.9: SectionID MUST be globally unique across corpus.
|
|
59
|
+
* Violations are errors (per 1#12.2).
|
|
60
|
+
*
|
|
61
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
62
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
63
|
+
* @returns A map from each SectionID to the URI of its first-encountered defining document.
|
|
64
|
+
*/
|
|
65
|
+
private buildSectionIdIndex;
|
|
66
|
+
/**
|
|
67
|
+
* Maps each DocID to the title in its document's H1.
|
|
68
|
+
*
|
|
69
|
+
* Where a DocID is duplicated, the first-encountered document wins, matching
|
|
70
|
+
* {@link CorpusValidator.buildDocIdIndex}. The duplicate is reported there.
|
|
71
|
+
*
|
|
72
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
73
|
+
* @returns A map from each DocID to its canonical title.
|
|
74
|
+
*/
|
|
75
|
+
private buildCanonicalTitleIndex;
|
|
76
|
+
/**
|
|
77
|
+
* Resolves all `ReferenceEdge.toDocId` targets against the global DocID
|
|
78
|
+
* index and emits error diagnostics for unresolved targets.
|
|
79
|
+
*
|
|
80
|
+
* Per 1#9.9 rule 3: Every TargetDocID in References MUST resolve to an
|
|
81
|
+
* existing DocID. Violations are errors (per 1#12.2).
|
|
82
|
+
*
|
|
83
|
+
* Per 1#9.9 rule 5: where the target resolves but the entry's title differs
|
|
84
|
+
* from the target's H1 title, a warning is emitted carrying the canonical
|
|
85
|
+
* title. Titles are not part of graph identity, so a stale title never
|
|
86
|
+
* fails validation.
|
|
87
|
+
*
|
|
88
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
89
|
+
* @param docIdIndex - The global DocID-to-URI index.
|
|
90
|
+
* @param canonicalTitles - Each DocID's title as written in its own H1.
|
|
91
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
92
|
+
*/
|
|
93
|
+
private resolveReferenceTargets;
|
|
94
|
+
/**
|
|
95
|
+
* Resolves all `InlineReferenceEdge.toId` targets against the union of
|
|
96
|
+
* DocID and SectionID indexes and emits error diagnostics for unresolved
|
|
97
|
+
* targets.
|
|
98
|
+
*
|
|
99
|
+
* Per 1#9.9 rule 4: Every inline TargetID MUST resolve to an existing
|
|
100
|
+
* SectionID. Violations are errors (per 1#12.2).
|
|
101
|
+
*
|
|
102
|
+
* Note: the spec says "existing SectionID" but the algorithm (1#11.2
|
|
103
|
+
* step 5) resolves against "the union of DocID and SectionID indexes",
|
|
104
|
+
* meaning a DocID is also a valid resolution target for inline references.
|
|
105
|
+
*
|
|
106
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
107
|
+
* @param docIdIndex - The global DocID-to-URI index.
|
|
108
|
+
* @param sectionIdIndex - The global SectionID-to-URI index.
|
|
109
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
110
|
+
*/
|
|
111
|
+
private resolveInlineReferenceTargets;
|
|
112
|
+
/**
|
|
113
|
+
* Validates that for each extracted inline reference whose target is a
|
|
114
|
+
* SectionID, the DocID of that SectionID is declared in the referring
|
|
115
|
+
* document's References section.
|
|
116
|
+
*
|
|
117
|
+
* Per 1#9.5 rule 2 and 1#11.2 step 6: if an inline reference targets
|
|
118
|
+
* `X#Y`, the DocID `X` MUST appear in the References section of the
|
|
119
|
+
* referring document.
|
|
120
|
+
*
|
|
121
|
+
* The linter itself never extracts an undeclared edge, so through the
|
|
122
|
+
* {@link Ecr} facade this cannot fire; undeclared targets are handled by
|
|
123
|
+
* {@link CorpusValidator.escalateUndeclaredInlineTargets}. It guards hosts
|
|
124
|
+
* that assemble extracted documents themselves.
|
|
125
|
+
*
|
|
126
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
127
|
+
* @param docIdIndex - The global DocID-to-URI index.
|
|
128
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
129
|
+
*/
|
|
130
|
+
private validateInlineReferenceParentDeclarations;
|
|
131
|
+
/**
|
|
132
|
+
* Raises a document-level undeclared-target warning to an error when the
|
|
133
|
+
* target turns out to be a document in the corpus.
|
|
134
|
+
*
|
|
135
|
+
* Per 1#9.5 rule 3: a single document cannot tell `per 3.1` (a reference
|
|
136
|
+
* whose declaration is missing) from `per 60 seconds` (prose), so ECR104
|
|
137
|
+
* only warns. Here the corpus is known. If DocID `3.1` exists, the author
|
|
138
|
+
* has referenced a real document without declaring it, which is an error.
|
|
139
|
+
* If no document `60` exists, the warning stands on its own.
|
|
140
|
+
*
|
|
141
|
+
* An undeclared SectionID target (`per 3.1#2`) is already an ECR104 error,
|
|
142
|
+
* so only warnings are considered here; raising it again would report the
|
|
143
|
+
* same mistake twice.
|
|
144
|
+
*
|
|
145
|
+
* @param documents - The per-document entries, including their diagnostics.
|
|
146
|
+
* @param docIdIndex - The global DocID-to-URI index.
|
|
147
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
148
|
+
*/
|
|
149
|
+
private escalateUndeclaredInlineTargets;
|
|
150
|
+
/**
|
|
151
|
+
* Converts the mutable maps into a frozen {@link CorpusIndex}.
|
|
152
|
+
*
|
|
153
|
+
* @param docIdIndex - The global DocID-to-URI map.
|
|
154
|
+
* @param sectionIdIndex - The global SectionID-to-URI map.
|
|
155
|
+
* @returns A frozen corpus index suitable for graph construction.
|
|
156
|
+
*/
|
|
157
|
+
private buildCorpusIndex;
|
|
158
|
+
/**
|
|
159
|
+
* Collects the set of target DocIDs declared in a document's References
|
|
160
|
+
* section.
|
|
161
|
+
*
|
|
162
|
+
* @param extracted - The extracted document whose references are inspected.
|
|
163
|
+
* @returns A set of DocIDs declared as reference targets.
|
|
164
|
+
*/
|
|
165
|
+
private collectDeclaredReferenceDocIds;
|
|
166
|
+
/**
|
|
167
|
+
* Finds the DocID a SectionID belongs to.
|
|
168
|
+
*
|
|
169
|
+
* This is a direct read rather than a search: everything before the `#`
|
|
170
|
+
* separator is the DocID. A dotted-only grammar would have to try
|
|
171
|
+
* progressively longer dotted prefixes against the index, which cannot
|
|
172
|
+
* distinguish section `1` of document `0.0.1` from section `1.3` of
|
|
173
|
+
* document `0.0`, and silently prefers whichever the corpus happens to
|
|
174
|
+
* contain.
|
|
175
|
+
*
|
|
176
|
+
* @param sectionId - The SectionID whose DocID is sought.
|
|
177
|
+
* @param docIdIndex - The global DocID-to-URI index, used to confirm the document exists.
|
|
178
|
+
* @returns The DocID the section belongs to, or `undefined` if it is not in the corpus.
|
|
179
|
+
*/
|
|
180
|
+
private findParentDocId;
|
|
181
|
+
}
|
|
182
|
+
export { CorpusValidator };
|
|
183
|
+
//# sourceMappingURL=corpus-validator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corpus-validator.d.ts","sourceRoot":"","sources":["../src/corpus-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,YAAY,EAKb,MAAM,YAAY,CAAC;AAoCpB;;;;;;;;;;GAUG;AACH,cAAM,eAAe;IACnB;;;;;;;;;;;;;;;OAeG;IACI,cAAc,CACnB,SAAS,EAAE,SAAS,mBAAmB,EAAE,GACxC,YAAY;IA2Df;;;;;;;;;OASG;IACH,OAAO,CAAC,yBAAyB;IAqBjC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,eAAe;IAyCvB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,mBAAmB;IAuD3B;;;;;;;;OAQG;IACH,OAAO,CAAC,wBAAwB;IAchC;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,uBAAuB;IA2C/B;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,6BAA6B;IA8BrC;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,yCAAyC;IAuDjD;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,+BAA+B;IA2DvC;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IAsBxB;;;;;;OAMG;IACH,OAAO,CAAC,8BAA8B;IAYtC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,eAAe;CAexB;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { CITATION_SOURCE_FORM_CAUSE, INLINE_REFERENCE_RULE_ID, UNDECLARED_TARGET_REASON, } from './inline-reference-rule.js';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// CorpusValidator
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
/**
|
|
6
|
+
* Validates a corpus of ECR documents for corpus-wide structural integrity.
|
|
7
|
+
*
|
|
8
|
+
* Performs Pass 2 of the ECR validation algorithm (per 1#11.2):
|
|
9
|
+
* global uniqueness of DocIDs and SectionIDs, resolution of reference
|
|
10
|
+
* targets, and resolution of inline reference targets.
|
|
11
|
+
*
|
|
12
|
+
* This class is stateless. Each invocation of {@link validateCorpus}
|
|
13
|
+
* operates purely over the provided document entries and produces a
|
|
14
|
+
* self-contained {@link CorpusResult}.
|
|
15
|
+
*/
|
|
16
|
+
class CorpusValidator {
|
|
17
|
+
/**
|
|
18
|
+
* Validates corpus-wide structural invariants across a set of
|
|
19
|
+
* per-document extraction results.
|
|
20
|
+
*
|
|
21
|
+
* Documents whose per-document lint result does not include a
|
|
22
|
+
* successfully extracted artefact are excluded from corpus-wide
|
|
23
|
+
* indexing and cross-reference resolution.
|
|
24
|
+
*
|
|
25
|
+
* Corpus-wide violations are reported as diagnostics with severity
|
|
26
|
+
* `'error'` (per 1#9.9, 1#12.2), except a stale References title, which
|
|
27
|
+
* is a `'warning'` (1#9.9 rule 5).
|
|
28
|
+
*
|
|
29
|
+
* @param documents - The per-document entries to validate as a corpus.
|
|
30
|
+
* @returns A {@link CorpusResult} containing per-document results,
|
|
31
|
+
* corpus-wide diagnostics, and an optional global identifier index.
|
|
32
|
+
*/
|
|
33
|
+
validateCorpus(documents) {
|
|
34
|
+
const indexableDocuments = this.collectIndexableDocuments(documents);
|
|
35
|
+
const accumulator = { items: [] };
|
|
36
|
+
// Step 1–3: Build indexes and validate global uniqueness
|
|
37
|
+
const docIdIndex = this.buildDocIdIndex(indexableDocuments, accumulator);
|
|
38
|
+
const sectionIdIndex = this.buildSectionIdIndex(indexableDocuments, accumulator);
|
|
39
|
+
const hasDuplicateIdentifiers = accumulator.items.length > 0;
|
|
40
|
+
// Step 4: Resolve ReferenceEdge.toDocId targets, and warn where an
|
|
41
|
+
// entry's title has drifted from the target document's own title
|
|
42
|
+
this.resolveReferenceTargets(indexableDocuments, docIdIndex, this.buildCanonicalTitleIndex(indexableDocuments), accumulator);
|
|
43
|
+
// Step 5: Resolve InlineReferenceEdge.toId targets
|
|
44
|
+
this.resolveInlineReferenceTargets(indexableDocuments, docIdIndex, sectionIdIndex, accumulator);
|
|
45
|
+
// Step 6: Validate inline reference parent DocID declared in References
|
|
46
|
+
this.validateInlineReferenceParentDeclarations(indexableDocuments, docIdIndex, accumulator);
|
|
47
|
+
// Step 6b: An undeclared inline target that names a real document is an error
|
|
48
|
+
this.escalateUndeclaredInlineTargets(documents, docIdIndex, accumulator);
|
|
49
|
+
if (hasDuplicateIdentifiers) {
|
|
50
|
+
return {
|
|
51
|
+
documents,
|
|
52
|
+
diagnostics: accumulator.items,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
documents,
|
|
57
|
+
index: this.buildCorpusIndex(docIdIndex, sectionIdIndex),
|
|
58
|
+
diagnostics: accumulator.items,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// -------------------------------------------------------------------------
|
|
62
|
+
// Private helpers — filtering
|
|
63
|
+
// -------------------------------------------------------------------------
|
|
64
|
+
/**
|
|
65
|
+
* Filters the input document entries to only those with successfully
|
|
66
|
+
* extracted structural artefacts.
|
|
67
|
+
*
|
|
68
|
+
* Documents without an `extracted` field on their lint result are excluded
|
|
69
|
+
* from corpus-wide indexing and cross-reference resolution.
|
|
70
|
+
*
|
|
71
|
+
* @param documents - The full set of corpus document entries.
|
|
72
|
+
* @returns An array of indexable documents with guaranteed `extracted` fields.
|
|
73
|
+
*/
|
|
74
|
+
collectIndexableDocuments(documents) {
|
|
75
|
+
const indexable = [];
|
|
76
|
+
for (const entry of documents) {
|
|
77
|
+
if (entry.result.extracted !== undefined) {
|
|
78
|
+
indexable.push({
|
|
79
|
+
uri: entry.uri,
|
|
80
|
+
extracted: entry.result.extracted,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return indexable;
|
|
85
|
+
}
|
|
86
|
+
// -------------------------------------------------------------------------
|
|
87
|
+
// Private helpers — index building (Steps 1–3)
|
|
88
|
+
// -------------------------------------------------------------------------
|
|
89
|
+
/**
|
|
90
|
+
* Builds a global DocID-to-URI index and emits error diagnostics for
|
|
91
|
+
* any duplicate DocIDs found across the corpus.
|
|
92
|
+
*
|
|
93
|
+
* Per 1#9.9: DocID MUST be globally unique. Violations are errors
|
|
94
|
+
* (per 1#12.2).
|
|
95
|
+
*
|
|
96
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
97
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
98
|
+
* @returns A map from each DocID to the URI of its first-encountered defining document.
|
|
99
|
+
*/
|
|
100
|
+
buildDocIdIndex(indexableDocuments, accumulator) {
|
|
101
|
+
const docIdIndex = new Map();
|
|
102
|
+
const duplicateDocIds = new Map();
|
|
103
|
+
for (const document of indexableDocuments) {
|
|
104
|
+
const docId = document.extracted.docId;
|
|
105
|
+
const existingUri = docIdIndex.get(docId);
|
|
106
|
+
if (existingUri !== undefined) {
|
|
107
|
+
const existing = duplicateDocIds.get(docId);
|
|
108
|
+
if (existing !== undefined) {
|
|
109
|
+
duplicateDocIds.set(docId, [...existing, document.uri]);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
duplicateDocIds.set(docId, [existingUri, document.uri]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
docIdIndex.set(docId, document.uri);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const [docId, uris] of duplicateDocIds) {
|
|
120
|
+
for (const uri of uris) {
|
|
121
|
+
accumulator.items.push({
|
|
122
|
+
severity: 'error',
|
|
123
|
+
ruleId: 'corpus/duplicate-doc-id',
|
|
124
|
+
message: `Duplicate DocID '${docId}' found in multiple documents`,
|
|
125
|
+
uri,
|
|
126
|
+
docId,
|
|
127
|
+
data: { duplicateUris: uris },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return docIdIndex;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Builds a global SectionID-to-URI index and emits error diagnostics for
|
|
135
|
+
* any duplicate SectionIDs found across the corpus.
|
|
136
|
+
*
|
|
137
|
+
* Per 1#9.9: SectionID MUST be globally unique across corpus.
|
|
138
|
+
* Violations are errors (per 1#12.2).
|
|
139
|
+
*
|
|
140
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
141
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
142
|
+
* @returns A map from each SectionID to the URI of its first-encountered defining document.
|
|
143
|
+
*/
|
|
144
|
+
buildSectionIdIndex(indexableDocuments, accumulator) {
|
|
145
|
+
const sectionIdIndex = new Map();
|
|
146
|
+
const duplicateSectionIds = new Map();
|
|
147
|
+
for (const document of indexableDocuments) {
|
|
148
|
+
for (const section of document.extracted.sections) {
|
|
149
|
+
// Skip the root H1 node — its id is the DocID, handled by DocID index
|
|
150
|
+
if (section.headingDepth === 1) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const sectionId = section.id;
|
|
154
|
+
const existingUri = sectionIdIndex.get(sectionId);
|
|
155
|
+
if (existingUri !== undefined) {
|
|
156
|
+
const existing = duplicateSectionIds.get(sectionId);
|
|
157
|
+
if (existing !== undefined) {
|
|
158
|
+
duplicateSectionIds.set(sectionId, [...existing, document.uri]);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
duplicateSectionIds.set(sectionId, [existingUri, document.uri]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
sectionIdIndex.set(sectionId, document.uri);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const [sectionId, uris] of duplicateSectionIds) {
|
|
170
|
+
for (const uri of uris) {
|
|
171
|
+
const baseDiagnostic = {
|
|
172
|
+
severity: 'error',
|
|
173
|
+
ruleId: 'corpus/duplicate-section-id',
|
|
174
|
+
message: `Duplicate SectionID '${sectionId}' found in multiple documents`,
|
|
175
|
+
uri,
|
|
176
|
+
sectionId,
|
|
177
|
+
data: { duplicateUris: uris },
|
|
178
|
+
};
|
|
179
|
+
accumulator.items.push(baseDiagnostic);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return sectionIdIndex;
|
|
183
|
+
}
|
|
184
|
+
// -------------------------------------------------------------------------
|
|
185
|
+
// Private helpers — reference resolution (Steps 4–6)
|
|
186
|
+
// -------------------------------------------------------------------------
|
|
187
|
+
/**
|
|
188
|
+
* Maps each DocID to the title in its document's H1.
|
|
189
|
+
*
|
|
190
|
+
* Where a DocID is duplicated, the first-encountered document wins, matching
|
|
191
|
+
* {@link CorpusValidator.buildDocIdIndex}. The duplicate is reported there.
|
|
192
|
+
*
|
|
193
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
194
|
+
* @returns A map from each DocID to its canonical title.
|
|
195
|
+
*/
|
|
196
|
+
buildCanonicalTitleIndex(indexableDocuments) {
|
|
197
|
+
const canonicalTitles = new Map();
|
|
198
|
+
for (const document of indexableDocuments) {
|
|
199
|
+
if (!canonicalTitles.has(document.extracted.docId)) {
|
|
200
|
+
canonicalTitles.set(document.extracted.docId, document.extracted.title.trim());
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return canonicalTitles;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Resolves all `ReferenceEdge.toDocId` targets against the global DocID
|
|
207
|
+
* index and emits error diagnostics for unresolved targets.
|
|
208
|
+
*
|
|
209
|
+
* Per 1#9.9 rule 3: Every TargetDocID in References MUST resolve to an
|
|
210
|
+
* existing DocID. Violations are errors (per 1#12.2).
|
|
211
|
+
*
|
|
212
|
+
* Per 1#9.9 rule 5: where the target resolves but the entry's title differs
|
|
213
|
+
* from the target's H1 title, a warning is emitted carrying the canonical
|
|
214
|
+
* title. Titles are not part of graph identity, so a stale title never
|
|
215
|
+
* fails validation.
|
|
216
|
+
*
|
|
217
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
218
|
+
* @param docIdIndex - The global DocID-to-URI index.
|
|
219
|
+
* @param canonicalTitles - Each DocID's title as written in its own H1.
|
|
220
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
221
|
+
*/
|
|
222
|
+
resolveReferenceTargets(indexableDocuments, docIdIndex, canonicalTitles, accumulator) {
|
|
223
|
+
for (const document of indexableDocuments) {
|
|
224
|
+
for (const reference of document.extracted.references) {
|
|
225
|
+
const targetExists = docIdIndex.has(reference.toDocId);
|
|
226
|
+
if (!targetExists) {
|
|
227
|
+
accumulator.items.push({
|
|
228
|
+
severity: 'error',
|
|
229
|
+
ruleId: 'corpus/unresolved-reference-target',
|
|
230
|
+
message: `References entry target DocID '${reference.toDocId}' does not resolve to any document in the corpus`,
|
|
231
|
+
uri: document.uri,
|
|
232
|
+
docId: document.extracted.docId,
|
|
233
|
+
data: { unresolvedDocId: reference.toDocId },
|
|
234
|
+
});
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const canonicalTitle = canonicalTitles.get(reference.toDocId);
|
|
238
|
+
const referencedTitle = reference.title.trim();
|
|
239
|
+
if (canonicalTitle !== undefined && referencedTitle !== canonicalTitle) {
|
|
240
|
+
accumulator.items.push({
|
|
241
|
+
severity: 'warning',
|
|
242
|
+
ruleId: 'corpus/reference-title-mismatch',
|
|
243
|
+
message: `References entry for '${reference.toDocId}' is titled '${referencedTitle}', but that document's title is '${canonicalTitle}'`,
|
|
244
|
+
uri: document.uri,
|
|
245
|
+
docId: document.extracted.docId,
|
|
246
|
+
data: {
|
|
247
|
+
targetDocId: reference.toDocId,
|
|
248
|
+
referencedTitle,
|
|
249
|
+
canonicalTitle,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Resolves all `InlineReferenceEdge.toId` targets against the union of
|
|
258
|
+
* DocID and SectionID indexes and emits error diagnostics for unresolved
|
|
259
|
+
* targets.
|
|
260
|
+
*
|
|
261
|
+
* Per 1#9.9 rule 4: Every inline TargetID MUST resolve to an existing
|
|
262
|
+
* SectionID. Violations are errors (per 1#12.2).
|
|
263
|
+
*
|
|
264
|
+
* Note: the spec says "existing SectionID" but the algorithm (1#11.2
|
|
265
|
+
* step 5) resolves against "the union of DocID and SectionID indexes",
|
|
266
|
+
* meaning a DocID is also a valid resolution target for inline references.
|
|
267
|
+
*
|
|
268
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
269
|
+
* @param docIdIndex - The global DocID-to-URI index.
|
|
270
|
+
* @param sectionIdIndex - The global SectionID-to-URI index.
|
|
271
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
272
|
+
*/
|
|
273
|
+
resolveInlineReferenceTargets(indexableDocuments, docIdIndex, sectionIdIndex, accumulator) {
|
|
274
|
+
for (const document of indexableDocuments) {
|
|
275
|
+
for (const inlineReference of document.extracted.inlineReferences) {
|
|
276
|
+
const targetId = inlineReference.toId;
|
|
277
|
+
const resolvedInDocIds = docIdIndex.has(targetId);
|
|
278
|
+
const resolvedInSectionIds = sectionIdIndex.has(targetId);
|
|
279
|
+
if (!resolvedInDocIds && !resolvedInSectionIds) {
|
|
280
|
+
accumulator.items.push({
|
|
281
|
+
severity: 'error',
|
|
282
|
+
ruleId: 'corpus/unresolved-inline-target',
|
|
283
|
+
message: `Inline reference target '${targetId}' does not resolve to any DocID or SectionID in the corpus`,
|
|
284
|
+
uri: document.uri,
|
|
285
|
+
docId: document.extracted.docId,
|
|
286
|
+
data: {
|
|
287
|
+
unresolvedTargetId: targetId,
|
|
288
|
+
fromId: inlineReference.fromId,
|
|
289
|
+
kind: inlineReference.kind,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Validates that for each extracted inline reference whose target is a
|
|
298
|
+
* SectionID, the DocID of that SectionID is declared in the referring
|
|
299
|
+
* document's References section.
|
|
300
|
+
*
|
|
301
|
+
* Per 1#9.5 rule 2 and 1#11.2 step 6: if an inline reference targets
|
|
302
|
+
* `X#Y`, the DocID `X` MUST appear in the References section of the
|
|
303
|
+
* referring document.
|
|
304
|
+
*
|
|
305
|
+
* The linter itself never extracts an undeclared edge, so through the
|
|
306
|
+
* {@link Ecr} facade this cannot fire; undeclared targets are handled by
|
|
307
|
+
* {@link CorpusValidator.escalateUndeclaredInlineTargets}. It guards hosts
|
|
308
|
+
* that assemble extracted documents themselves.
|
|
309
|
+
*
|
|
310
|
+
* @param indexableDocuments - Documents with successfully extracted artefacts.
|
|
311
|
+
* @param docIdIndex - The global DocID-to-URI index.
|
|
312
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
313
|
+
*/
|
|
314
|
+
validateInlineReferenceParentDeclarations(indexableDocuments, docIdIndex, accumulator) {
|
|
315
|
+
for (const document of indexableDocuments) {
|
|
316
|
+
const declaredTargetDocIds = this.collectDeclaredReferenceDocIds(document.extracted);
|
|
317
|
+
for (const inlineReference of document.extracted.inlineReferences) {
|
|
318
|
+
const targetId = inlineReference.toId;
|
|
319
|
+
// If the target is itself a known DocID, no parent-declaration
|
|
320
|
+
// check is needed — the inline reference targets the document
|
|
321
|
+
// directly.
|
|
322
|
+
if (docIdIndex.has(targetId)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
// The target is a SectionID. Its DocID is the text before the `#`.
|
|
326
|
+
const parentDocId = this.findParentDocId(targetId, docIdIndex);
|
|
327
|
+
if (parentDocId === undefined) {
|
|
328
|
+
// No parent DocID found in corpus — the unresolved-inline-target
|
|
329
|
+
// diagnostic already covers this case (step 5). Skip here.
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
// If the parent DocID is the current document itself, no
|
|
333
|
+
// cross-document dependency declaration is needed — the
|
|
334
|
+
// section is local.
|
|
335
|
+
if (parentDocId === document.extracted.docId) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
if (!declaredTargetDocIds.has(parentDocId)) {
|
|
339
|
+
accumulator.items.push({
|
|
340
|
+
severity: 'error',
|
|
341
|
+
ruleId: 'corpus/undeclared-inline-target',
|
|
342
|
+
message: `Inline reference to '${targetId}' targets undeclared DocID '${parentDocId}' — declare it in the References section`,
|
|
343
|
+
uri: document.uri,
|
|
344
|
+
docId: document.extracted.docId,
|
|
345
|
+
data: {
|
|
346
|
+
targetId,
|
|
347
|
+
parentDocId,
|
|
348
|
+
fromId: inlineReference.fromId,
|
|
349
|
+
kind: inlineReference.kind,
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Raises a document-level undeclared-target warning to an error when the
|
|
358
|
+
* target turns out to be a document in the corpus.
|
|
359
|
+
*
|
|
360
|
+
* Per 1#9.5 rule 3: a single document cannot tell `per 3.1` (a reference
|
|
361
|
+
* whose declaration is missing) from `per 60 seconds` (prose), so ECR104
|
|
362
|
+
* only warns. Here the corpus is known. If DocID `3.1` exists, the author
|
|
363
|
+
* has referenced a real document without declaring it, which is an error.
|
|
364
|
+
* If no document `60` exists, the warning stands on its own.
|
|
365
|
+
*
|
|
366
|
+
* An undeclared SectionID target (`per 3.1#2`) is already an ECR104 error,
|
|
367
|
+
* so only warnings are considered here; raising it again would report the
|
|
368
|
+
* same mistake twice.
|
|
369
|
+
*
|
|
370
|
+
* @param documents - The per-document entries, including their diagnostics.
|
|
371
|
+
* @param docIdIndex - The global DocID-to-URI index.
|
|
372
|
+
* @param accumulator - Diagnostic accumulator for recording violations.
|
|
373
|
+
*/
|
|
374
|
+
escalateUndeclaredInlineTargets(documents, docIdIndex, accumulator) {
|
|
375
|
+
for (const entry of documents) {
|
|
376
|
+
for (const warning of entry.result.diagnostics) {
|
|
377
|
+
const targetDocId = warning.data?.targetDocId;
|
|
378
|
+
if (warning.ruleId !== INLINE_REFERENCE_RULE_ID ||
|
|
379
|
+
warning.severity !== 'warning' ||
|
|
380
|
+
warning.data?.reason !== UNDECLARED_TARGET_REASON ||
|
|
381
|
+
typeof targetDocId !== 'string' ||
|
|
382
|
+
!docIdIndex.has(targetDocId)) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const targetId = warning.data.targetId;
|
|
386
|
+
const cause = warning.data.cause;
|
|
387
|
+
// A citation no search can find (1#9.5 rule 4) warns for the same
|
|
388
|
+
// reason an undeclared one does, and is raised here for the same
|
|
389
|
+
// reason. Declaring the target would not make it findable, so the
|
|
390
|
+
// message has to name the source form as well. Only an undeclared
|
|
391
|
+
// target ever warns, so both parts always apply.
|
|
392
|
+
const message = cause === CITATION_SOURCE_FORM_CAUSE
|
|
393
|
+
? `Inline reference to '${String(targetId)}' is not written as adjacent literal ` +
|
|
394
|
+
`text on one line, so no search finds it, and DocID '${targetDocId}' exists in ` +
|
|
395
|
+
`the corpus, so it is certainly a citation — write it as literal text and ` +
|
|
396
|
+
`declare it in the References section`
|
|
397
|
+
: `Inline reference to '${String(targetId)}' targets DocID '${targetDocId}', ` +
|
|
398
|
+
`which exists in the corpus but is not declared — declare it in the References section`;
|
|
399
|
+
accumulator.items.push({
|
|
400
|
+
severity: 'error',
|
|
401
|
+
ruleId: 'corpus/undeclared-inline-target',
|
|
402
|
+
message,
|
|
403
|
+
uri: entry.uri,
|
|
404
|
+
...(warning.range !== undefined ? { range: warning.range } : {}),
|
|
405
|
+
...(entry.result.extracted !== undefined ? { docId: entry.result.extracted.docId } : {}),
|
|
406
|
+
data: {
|
|
407
|
+
...(cause !== undefined ? { cause } : {}),
|
|
408
|
+
targetId,
|
|
409
|
+
parentDocId: targetDocId,
|
|
410
|
+
fromId: warning.data.fromId,
|
|
411
|
+
kind: warning.data.kind,
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// -------------------------------------------------------------------------
|
|
418
|
+
// Private helpers — index construction
|
|
419
|
+
// -------------------------------------------------------------------------
|
|
420
|
+
/**
|
|
421
|
+
* Converts the mutable maps into a frozen {@link CorpusIndex}.
|
|
422
|
+
*
|
|
423
|
+
* @param docIdIndex - The global DocID-to-URI map.
|
|
424
|
+
* @param sectionIdIndex - The global SectionID-to-URI map.
|
|
425
|
+
* @returns A frozen corpus index suitable for graph construction.
|
|
426
|
+
*/
|
|
427
|
+
buildCorpusIndex(docIdIndex, sectionIdIndex) {
|
|
428
|
+
const docIds = {};
|
|
429
|
+
const sectionIds = {};
|
|
430
|
+
for (const [docId, uri] of docIdIndex) {
|
|
431
|
+
docIds[docId] = uri;
|
|
432
|
+
}
|
|
433
|
+
for (const [sectionId, uri] of sectionIdIndex) {
|
|
434
|
+
sectionIds[sectionId] = uri;
|
|
435
|
+
}
|
|
436
|
+
return { docIds, sectionIds };
|
|
437
|
+
}
|
|
438
|
+
// -------------------------------------------------------------------------
|
|
439
|
+
// Private helpers — identifier utilities
|
|
440
|
+
// -------------------------------------------------------------------------
|
|
441
|
+
/**
|
|
442
|
+
* Collects the set of target DocIDs declared in a document's References
|
|
443
|
+
* section.
|
|
444
|
+
*
|
|
445
|
+
* @param extracted - The extracted document whose references are inspected.
|
|
446
|
+
* @returns A set of DocIDs declared as reference targets.
|
|
447
|
+
*/
|
|
448
|
+
collectDeclaredReferenceDocIds(extracted) {
|
|
449
|
+
const declaredDocIds = new Set();
|
|
450
|
+
for (const reference of extracted.references) {
|
|
451
|
+
declaredDocIds.add(reference.toDocId);
|
|
452
|
+
}
|
|
453
|
+
return declaredDocIds;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Finds the DocID a SectionID belongs to.
|
|
457
|
+
*
|
|
458
|
+
* This is a direct read rather than a search: everything before the `#`
|
|
459
|
+
* separator is the DocID. A dotted-only grammar would have to try
|
|
460
|
+
* progressively longer dotted prefixes against the index, which cannot
|
|
461
|
+
* distinguish section `1` of document `0.0.1` from section `1.3` of
|
|
462
|
+
* document `0.0`, and silently prefers whichever the corpus happens to
|
|
463
|
+
* contain.
|
|
464
|
+
*
|
|
465
|
+
* @param sectionId - The SectionID whose DocID is sought.
|
|
466
|
+
* @param docIdIndex - The global DocID-to-URI index, used to confirm the document exists.
|
|
467
|
+
* @returns The DocID the section belongs to, or `undefined` if it is not in the corpus.
|
|
468
|
+
*/
|
|
469
|
+
findParentDocId(sectionId, docIdIndex) {
|
|
470
|
+
const separatorIndex = sectionId.indexOf('#');
|
|
471
|
+
if (separatorIndex < 0) {
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
const docId = sectionId.substring(0, separatorIndex);
|
|
475
|
+
return docIdIndex.has(docId) ? docId : undefined;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
export { CorpusValidator };
|
|
479
|
+
//# sourceMappingURL=corpus-validator.js.map
|