@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
package/dist/ecr.js ADDED
@@ -0,0 +1,216 @@
1
+ /**
2
+ * ECR Public API -- Ecr Facade
3
+ *
4
+ * Top-level public API for the ECR structural linter.
5
+ *
6
+ * Derived from section 1#13 (Public Interface Contract) and
7
+ * section 1#10 (Output Schema) of the ECR Structural Specification (doc 1).
8
+ *
9
+ * This module provides the {@link Ecr} facade, which delegates
10
+ * per-document validation to {@link PerDocumentVisitor} and
11
+ * corpus-wide integrity checks to {@link CorpusValidator}.
12
+ *
13
+ * Per-document results are returned exactly as the visitor produces
14
+ * them: every severity is set by the rule that reports the finding.
15
+ *
16
+ * The facade applies one orchestration-level policy:
17
+ *
18
+ * **Cross-document duplicate SectionID detection**: The corpus
19
+ * validator only sees SectionIDs that survive per-document
20
+ * extraction (i.e., those that pass ECR102). A heading like
21
+ * `## 3.1#1 - X` inside a document with DocID `4.1` fails ECR102
22
+ * (it names another document) and is never extracted — yet its
23
+ * SectionID text is still globally significant. The facade
24
+ * performs an additional cross-document duplicate check by
25
+ * collecting SectionIDs from both extracted sections and
26
+ * per-document diagnostics that carry a `sectionId` field.
27
+ */
28
+ import { PerDocumentVisitor } from "./per-document-visitor.js";
29
+ import { CorpusValidator } from "./corpus-validator.js";
30
+ // ---------------------------------------------------------------------------
31
+ // Constants
32
+ // ---------------------------------------------------------------------------
33
+ /**
34
+ * The corpus-level rule ID for duplicate SectionIDs detected by the facade.
35
+ */
36
+ const CORPUS_DUPLICATE_SECTION_ID_RULE_ID = 'corpus/duplicate-section-id';
37
+ // ---------------------------------------------------------------------------
38
+ // Public Facade
39
+ // ---------------------------------------------------------------------------
40
+ /**
41
+ * Top-level public API for the ECR structural linter.
42
+ *
43
+ * Provides two operations:
44
+ * - Single-document linting via {@link Ecr.lintDocument}
45
+ * - Corpus-wide validation via {@link Ecr.validateCorpus}
46
+ *
47
+ * This class is stateless across invocations. Successive calls with
48
+ * identical inputs produce identical results.
49
+ */
50
+ export class Ecr {
51
+ /**
52
+ * Lint a single Markdown document against per-document ECR structural invariants.
53
+ *
54
+ * Creates a {@link PerDocumentVisitor} for the given document and delegates
55
+ * the linting operation to it.
56
+ *
57
+ * @param uri - Opaque, host-provided URI identifying the document instance.
58
+ * @param markdownText - Raw Markdown text of the document.
59
+ * @param version - Optional version tag propagated through to the result.
60
+ * @returns Structured lint result containing diagnostics and optionally extracted artefacts.
61
+ */
62
+ lintDocument(uri, markdownText, version) {
63
+ const visitor = new PerDocumentVisitor({
64
+ uri,
65
+ ...(version !== undefined ? { version } : {}),
66
+ });
67
+ return visitor.lint(markdownText);
68
+ }
69
+ /**
70
+ * Validate a corpus of Markdown documents.
71
+ *
72
+ * Executes per-document linting on every document (Pass 1), then performs
73
+ * corpus-wide integrity checks on successfully linted documents (Pass 2).
74
+ *
75
+ * After the standard corpus validation, the facade performs an additional
76
+ * cross-document duplicate SectionID check that considers SectionIDs from
77
+ * per-document diagnostics (not just extracted sections), ensuring that
78
+ * headings rejected by ECR102 (e.g., wrong DocID prefix) still participate
79
+ * in global uniqueness enforcement.
80
+ *
81
+ * @param documents - Collection of documents to validate.
82
+ * @returns Corpus result containing per-document results, corpus-wide diagnostics, and an optional global identifier index.
83
+ */
84
+ validateCorpus(documents) {
85
+ // Pass 1: Per-document linting
86
+ const entries = documents.map((document) => {
87
+ const lintResult = this.lintDocument(document.uri, document.markdownText, document.version);
88
+ return {
89
+ uri: document.uri,
90
+ result: lintResult,
91
+ };
92
+ });
93
+ // Pass 2: Corpus-wide validation (standard checks)
94
+ const validator = new CorpusValidator();
95
+ const baseCorpusResult = validator.validateCorpus(entries);
96
+ // Pass 2b: Additional facade-level cross-document duplicate SectionID check
97
+ const additionalDiagnostics = this.detectCrossDocumentDuplicateSectionIds(entries);
98
+ if (additionalDiagnostics.length === 0) {
99
+ return baseCorpusResult;
100
+ }
101
+ // Merge additional diagnostics into the corpus result
102
+ const mergedDiagnostics = [
103
+ ...baseCorpusResult.diagnostics,
104
+ ...additionalDiagnostics,
105
+ ];
106
+ const corpusResult = {
107
+ documents: baseCorpusResult.documents,
108
+ diagnostics: mergedDiagnostics,
109
+ ...(baseCorpusResult.index !== undefined
110
+ ? { index: baseCorpusResult.index }
111
+ : {}),
112
+ };
113
+ return corpusResult;
114
+ }
115
+ // -------------------------------------------------------------------------
116
+ // Private: Cross-document duplicate SectionID detection
117
+ // -------------------------------------------------------------------------
118
+ /**
119
+ * Detects duplicate SectionIDs across documents by examining both
120
+ * extracted sections and per-document diagnostics.
121
+ *
122
+ * The standard {@link CorpusValidator} only checks extracted sections
123
+ * (those that pass ECR102). This method extends coverage to include
124
+ * SectionIDs mentioned in per-document diagnostics — for example,
125
+ * a heading `## 3.1#1 - X` inside a document with DocID `4.1` fails
126
+ * ECR102 (it names another document) but the SectionID `3.1#1` is still
127
+ * present in the diagnostic's `sectionId` field. If document `3.1` also
128
+ * defines `3.1#1`, that is a corpus-level duplicate.
129
+ *
130
+ * This method only emits diagnostics for duplicates NOT already
131
+ * detected by the standard corpus validator (i.e., duplicates involving
132
+ * at least one SectionID from a diagnostic rather than an extracted section).
133
+ *
134
+ * @param entries - The per-document lint entries from Pass 1
135
+ * @returns Additional corpus-level diagnostics for duplicate SectionIDs
136
+ */
137
+ detectCrossDocumentDuplicateSectionIds(entries) {
138
+ // Collect all SectionIDs per document from both extracted sections
139
+ // and per-document diagnostics
140
+ const sectionIdToUris = new Map();
141
+ for (const entry of entries) {
142
+ const documentSectionIds = new Set();
143
+ // Collect from extracted sections
144
+ if (entry.result.extracted !== undefined) {
145
+ for (const section of entry.result.extracted.sections) {
146
+ // Skip root H1 nodes — their id is the DocID, handled separately
147
+ if (section.headingDepth === 1) {
148
+ continue;
149
+ }
150
+ documentSectionIds.add(section.id);
151
+ }
152
+ }
153
+ // Collect from per-document diagnostics that carry a sectionId field
154
+ for (const diagnostic of entry.result.diagnostics) {
155
+ if (diagnostic.sectionId !== undefined) {
156
+ documentSectionIds.add(diagnostic.sectionId);
157
+ }
158
+ }
159
+ // Register each SectionID from this document
160
+ for (const sectionId of documentSectionIds) {
161
+ const existingUris = sectionIdToUris.get(sectionId);
162
+ if (existingUris !== undefined) {
163
+ // Only add this URI if it is not already listed
164
+ // (a document may have both an extracted section and a diagnostic
165
+ // for the same SectionID)
166
+ if (!existingUris.includes(entry.uri)) {
167
+ existingUris.push(entry.uri);
168
+ }
169
+ }
170
+ else {
171
+ sectionIdToUris.set(sectionId, [entry.uri]);
172
+ }
173
+ }
174
+ }
175
+ // Find duplicates — SectionIDs that appear in more than one document
176
+ const additionalDiagnostics = [];
177
+ for (const [sectionId, uris] of sectionIdToUris) {
178
+ if (uris.length <= 1) {
179
+ continue;
180
+ }
181
+ // The corpus validator detects a duplicate when the SectionID
182
+ // appears in extracted.sections of 2+ documents. Count how many
183
+ // of the duplicate URIs have this SectionID in their extracted
184
+ // sections to avoid emitting redundant diagnostics.
185
+ let extractedCount = 0;
186
+ for (const uri of uris) {
187
+ const entryForUri = entries.find((candidateEntry) => candidateEntry.uri === uri);
188
+ if (entryForUri?.result.extracted !== undefined) {
189
+ const hasExtractedSection = entryForUri.result.extracted.sections.some((section) => section.headingDepth !== 1 && section.id === sectionId);
190
+ if (hasExtractedSection) {
191
+ extractedCount = extractedCount + 1;
192
+ }
193
+ }
194
+ }
195
+ // If 2+ documents have this SectionID in their extracted sections,
196
+ // the corpus validator already detected the duplicate. Skip.
197
+ if (extractedCount >= 2) {
198
+ continue;
199
+ }
200
+ // Emit diagnostics for this duplicate
201
+ for (const uri of uris) {
202
+ const diagnosticForUri = {
203
+ severity: 'error',
204
+ ruleId: CORPUS_DUPLICATE_SECTION_ID_RULE_ID,
205
+ message: `Duplicate SectionID '${sectionId}' found in multiple documents`,
206
+ uri,
207
+ sectionId,
208
+ data: { duplicateUris: uris },
209
+ };
210
+ additionalDiagnostics.push(diagnosticForUri);
211
+ }
212
+ }
213
+ return additionalDiagnostics;
214
+ }
215
+ }
216
+ //# sourceMappingURL=ecr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecr.js","sourceRoot":"","sources":["../src/ecr.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Glob Patterns
3
+ *
4
+ * The minimal glob syntax used by `--ignore` and `.ecrignore`: `*` matches any
5
+ * run of characters except `/`, `**` matches any run including `/`, a `**`
6
+ * immediately before a slash matches zero or more whole directories, and `?`
7
+ * matches one character other than `/`. Everything else matches literally, and
8
+ * a pattern must match a whole path.
9
+ *
10
+ * A pattern is matched by walking the path once for each part of the pattern,
11
+ * so the work is bounded by the pattern's length times the path's length. An
12
+ * equivalent regular expression is not: patterns such as `*a*a*a*a*a*a*a*ab`
13
+ * make a backtracking engine explore exponentially many ways to divide the
14
+ * path between the wildcards, which let a single crafted ignore pattern hang
15
+ * the linter. Matching here is a table, so no such pattern exists.
16
+ */
17
+ /**
18
+ * A compiled glob pattern that can be matched against a path.
19
+ */
20
+ export declare class GlobPattern {
21
+ /** The pattern's parts, in order. */
22
+ private readonly parts;
23
+ /**
24
+ * Compiles a pattern.
25
+ *
26
+ * @param pattern - The glob pattern
27
+ */
28
+ constructor(pattern: string);
29
+ /**
30
+ * Determines whether a path matches the whole pattern.
31
+ *
32
+ * @param path - The path to match, using forward slashes
33
+ * @returns `true` when the pattern matches all of it
34
+ */
35
+ matches(path: string): boolean;
36
+ /**
37
+ * Works out, for every position in the path, whether one part followed by
38
+ * the parts after it can match the rest of the path from there.
39
+ *
40
+ * @param part - The part being added in front
41
+ * @param path - The path being matched
42
+ * @param following - The same answer for the parts after this one
43
+ * @returns The answer for this part and everything after it
44
+ */
45
+ private static matchPart;
46
+ /**
47
+ * Matches characters that must appear exactly.
48
+ *
49
+ * @param text - The characters the pattern gives
50
+ * @param path - The path being matched
51
+ * @param following - Where the parts after this one can match from
52
+ * @returns Where this part and everything after it can match from
53
+ */
54
+ private static matchLiteral;
55
+ /**
56
+ * Matches `?`: exactly one character, which may not be a separator.
57
+ *
58
+ * @param path - The path being matched
59
+ * @param following - Where the parts after this one can match from
60
+ * @returns Where this part and everything after it can match from
61
+ */
62
+ private static matchOneCharacter;
63
+ /**
64
+ * Matches `*` or `**`: a run of any length.
65
+ *
66
+ * A wildcard either stops where it is and lets the parts after it match, or
67
+ * takes one more character and asks itself the same question. Answering
68
+ * from the end of the path backwards means each position is settled once,
69
+ * whereas a regular expression would try the divisions one after another.
70
+ *
71
+ * @param path - The path being matched
72
+ * @param following - Where the parts after this one can match from
73
+ * @param withinSegment - Whether the run must stay inside one path segment
74
+ * @returns Where this part and everything after it can match from
75
+ */
76
+ private static matchWildcard;
77
+ /**
78
+ * Matches a `**` immediately before a slash: zero or more whole directories.
79
+ *
80
+ * @param path - The path being matched
81
+ * @param following - Where the parts after this one can match from
82
+ * @returns Where this part and everything after it can match from
83
+ */
84
+ private static matchWholeDirectories;
85
+ /**
86
+ * Splits a pattern into its parts, gathering runs of ordinary characters
87
+ * into one literal.
88
+ *
89
+ * @param pattern - The glob pattern
90
+ * @returns The parts, in order
91
+ */
92
+ private static compile;
93
+ }
94
+ //# sourceMappingURL=glob-pattern.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob-pattern.d.ts","sourceRoot":"","sources":["../src/glob-pattern.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAeH;;GAEG;AACH,qBAAa,WAAW;IACtB,qCAAqC;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAE5C;;;;OAIG;gBACgB,OAAO,EAAE,MAAM;IAIlC;;;;;OAKG;IACI,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAkBrC;;;;;;;;OAQG;IACH,OAAO,CAAC,MAAM,CAAC,SAAS;IAmBxB;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,YAAY;IAc3B;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAUhC;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAkB5B;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,qBAAqB;IAgBpC;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,CAAC,OAAO;CA2CvB"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Glob Patterns
3
+ *
4
+ * The minimal glob syntax used by `--ignore` and `.ecrignore`: `*` matches any
5
+ * run of characters except `/`, `**` matches any run including `/`, a `**`
6
+ * immediately before a slash matches zero or more whole directories, and `?`
7
+ * matches one character other than `/`. Everything else matches literally, and
8
+ * a pattern must match a whole path.
9
+ *
10
+ * A pattern is matched by walking the path once for each part of the pattern,
11
+ * so the work is bounded by the pattern's length times the path's length. An
12
+ * equivalent regular expression is not: patterns such as `*a*a*a*a*a*a*a*ab`
13
+ * make a backtracking engine explore exponentially many ways to divide the
14
+ * path between the wildcards, which let a single crafted ignore pattern hang
15
+ * the linter. Matching here is a table, so no such pattern exists.
16
+ */
17
+ /**
18
+ * A compiled glob pattern that can be matched against a path.
19
+ */
20
+ export class GlobPattern {
21
+ /** The pattern's parts, in order. */
22
+ parts;
23
+ /**
24
+ * Compiles a pattern.
25
+ *
26
+ * @param pattern - The glob pattern
27
+ */
28
+ constructor(pattern) {
29
+ this.parts = GlobPattern.compile(pattern);
30
+ }
31
+ /**
32
+ * Determines whether a path matches the whole pattern.
33
+ *
34
+ * @param path - The path to match, using forward slashes
35
+ * @returns `true` when the pattern matches all of it
36
+ */
37
+ matches(path) {
38
+ const length = path.length;
39
+ // `reached[index]` says whether the parts considered so far can match the
40
+ // rest of the path from `index`. It starts as the answer for no parts at
41
+ // all: only a path already consumed to its end matches nothing more.
42
+ let reached = new Array(length + 1).fill(false);
43
+ reached[length] = true;
44
+ // Each part is added in front of the answer for the parts after it, so
45
+ // they are considered last one first.
46
+ for (const part of [...this.parts].reverse()) {
47
+ reached = GlobPattern.matchPart(part, path, reached);
48
+ }
49
+ return reached[0] ?? false;
50
+ }
51
+ /**
52
+ * Works out, for every position in the path, whether one part followed by
53
+ * the parts after it can match the rest of the path from there.
54
+ *
55
+ * @param part - The part being added in front
56
+ * @param path - The path being matched
57
+ * @param following - The same answer for the parts after this one
58
+ * @returns The answer for this part and everything after it
59
+ */
60
+ static matchPart(part, path, following) {
61
+ switch (part.kind) {
62
+ case 'literal':
63
+ return GlobPattern.matchLiteral(part.text, path, following);
64
+ case 'oneCharacter':
65
+ return GlobPattern.matchOneCharacter(path, following);
66
+ case 'withinSegment':
67
+ return GlobPattern.matchWildcard(path, following, true);
68
+ case 'acrossSegments':
69
+ return GlobPattern.matchWildcard(path, following, false);
70
+ case 'wholeDirectories':
71
+ return GlobPattern.matchWholeDirectories(path, following);
72
+ }
73
+ }
74
+ /**
75
+ * Matches characters that must appear exactly.
76
+ *
77
+ * @param text - The characters the pattern gives
78
+ * @param path - The path being matched
79
+ * @param following - Where the parts after this one can match from
80
+ * @returns Where this part and everything after it can match from
81
+ */
82
+ static matchLiteral(text, path, following) {
83
+ const reached = new Array(path.length + 1).fill(false);
84
+ for (let index = 0; index + text.length <= path.length; index += 1) {
85
+ reached[index] = path.startsWith(text, index) && (following[index + text.length] ?? false);
86
+ }
87
+ return reached;
88
+ }
89
+ /**
90
+ * Matches `?`: exactly one character, which may not be a separator.
91
+ *
92
+ * @param path - The path being matched
93
+ * @param following - Where the parts after this one can match from
94
+ * @returns Where this part and everything after it can match from
95
+ */
96
+ static matchOneCharacter(path, following) {
97
+ const reached = new Array(path.length + 1).fill(false);
98
+ for (let index = 0; index < path.length; index += 1) {
99
+ reached[index] = path[index] !== '/' && (following[index + 1] ?? false);
100
+ }
101
+ return reached;
102
+ }
103
+ /**
104
+ * Matches `*` or `**`: a run of any length.
105
+ *
106
+ * A wildcard either stops where it is and lets the parts after it match, or
107
+ * takes one more character and asks itself the same question. Answering
108
+ * from the end of the path backwards means each position is settled once,
109
+ * whereas a regular expression would try the divisions one after another.
110
+ *
111
+ * @param path - The path being matched
112
+ * @param following - Where the parts after this one can match from
113
+ * @param withinSegment - Whether the run must stay inside one path segment
114
+ * @returns Where this part and everything after it can match from
115
+ */
116
+ static matchWildcard(path, following, withinSegment) {
117
+ const reached = new Array(path.length + 1).fill(false);
118
+ for (let index = path.length; index >= 0; index -= 1) {
119
+ const canTakeOneMore = index < path.length && (!withinSegment || path[index] !== '/');
120
+ reached[index] =
121
+ (following[index] ?? false) || (canTakeOneMore && (reached[index + 1] ?? false));
122
+ }
123
+ return reached;
124
+ }
125
+ /**
126
+ * Matches a `**` immediately before a slash: zero or more whole directories.
127
+ *
128
+ * @param path - The path being matched
129
+ * @param following - Where the parts after this one can match from
130
+ * @returns Where this part and everything after it can match from
131
+ */
132
+ static matchWholeDirectories(path, following) {
133
+ // Whole directories are taken whole, so this part matches either nothing
134
+ // at all, or a run of characters ending at a separator.
135
+ const endsAtSlash = new Array(path.length + 1).fill(false);
136
+ for (let index = path.length - 1; index >= 0; index -= 1) {
137
+ endsAtSlash[index] =
138
+ (path[index] === '/' && (following[index + 1] ?? false)) ||
139
+ (endsAtSlash[index + 1] ?? false);
140
+ }
141
+ return endsAtSlash.map((endsHere, index) => (following[index] ?? false) || endsHere);
142
+ }
143
+ /**
144
+ * Splits a pattern into its parts, gathering runs of ordinary characters
145
+ * into one literal.
146
+ *
147
+ * @param pattern - The glob pattern
148
+ * @returns The parts, in order
149
+ */
150
+ static compile(pattern) {
151
+ const parts = [];
152
+ let literal = '';
153
+ /** Closes off any literal gathered so far. */
154
+ const flushLiteral = () => {
155
+ if (literal !== '') {
156
+ parts.push({ kind: 'literal', text: literal });
157
+ literal = '';
158
+ }
159
+ };
160
+ for (let index = 0; index < pattern.length; index += 1) {
161
+ const character = pattern[index] ?? '';
162
+ if (character === '*') {
163
+ flushLiteral();
164
+ if (pattern[index + 1] === '*' && pattern[index + 2] === '/') {
165
+ parts.push({ kind: 'wholeDirectories' });
166
+ index += 2;
167
+ }
168
+ else if (pattern[index + 1] === '*') {
169
+ parts.push({ kind: 'acrossSegments' });
170
+ index += 1;
171
+ }
172
+ else {
173
+ parts.push({ kind: 'withinSegment' });
174
+ }
175
+ continue;
176
+ }
177
+ if (character === '?') {
178
+ flushLiteral();
179
+ parts.push({ kind: 'oneCharacter' });
180
+ continue;
181
+ }
182
+ literal += character;
183
+ }
184
+ flushLiteral();
185
+ return parts;
186
+ }
187
+ }
188
+ //# sourceMappingURL=glob-pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob-pattern.js","sourceRoot":"","sources":["../src/glob-pattern.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Heading Source Form
3
+ *
4
+ * Checks that a heading carrying a DocID or SectionID is written in the one
5
+ * source form the published recipes can find (1#9.11 rule 1): an ATX heading
6
+ * whose line begins with its `#` characters, then exactly one space, then the
7
+ * identifier in literal characters.
8
+ *
9
+ * The Markdown parser accepts far more than that. A setext heading, an
10
+ * indented one, one inside a blockquote, one with a tab or two spaces after
11
+ * the hashes, and one whose identifier is emphasised, escaped or written as a
12
+ * character reference all parse to the same identifier -- and none of them is
13
+ * found by `^# 8\.1[^0-9.#]` or `^#+ 8\.1#3([^0-9]|$)`. So the check reads the
14
+ * heading's own source line and asks the question the recipe asks.
15
+ *
16
+ * It also asks whether that line is a line at all to a search engine. Two
17
+ * things can hide a correctly written heading from `^`: a lone carriage
18
+ * return before it, which Markdown treats as a line ending and the engines do
19
+ * not, and a byte-order mark before the first line, which ripgrep skips and
20
+ * GNU grep does not.
21
+ *
22
+ * Shared by ECR101 (the H1) and ECR102 (every deeper heading).
23
+ */
24
+ import type { PositionRange } from './types.js';
25
+ /** The `data.cause` of a heading whose source form no recipe can find. */
26
+ export declare const HEADING_SOURCE_FORM_CAUSE: string;
27
+ /**
28
+ * Why a heading cannot be found, carried as `data.obstruction`.
29
+ *
30
+ * - `form`: the heading line is not `#… identifier` from its first column.
31
+ * - `lone-carriage-return`: it is, but a lone CR precedes it, so to a search
32
+ * it is the middle of the line before.
33
+ * - `byte-order-mark`: it is, but it is the first line and a byte-order mark
34
+ * precedes it, which GNU grep reads as text before the `#`.
35
+ */
36
+ export type HeadingObstruction = 'form' | 'lone-carriage-return' | 'byte-order-mark';
37
+ /**
38
+ * Tells whether headings are written where the recipes look for them.
39
+ */
40
+ export declare class HeadingSourceForm {
41
+ /** The document's source, by line; absent when none was supplied. */
42
+ private readonly lines;
43
+ /**
44
+ * @param sourceText - The document's raw Markdown. When absent, as in unit
45
+ * tests that supply only parsed heading text, there is
46
+ * no source to consult and every heading passes.
47
+ */
48
+ constructor(sourceText: string | undefined);
49
+ /**
50
+ * Finds what, if anything, stops the recipe for a heading from finding it.
51
+ *
52
+ * @param range - The heading node's range
53
+ * @param depth - The heading's depth, 1 for the H1
54
+ * @param identifier - The DocID or SectionID parsed from the heading
55
+ * @returns The obstruction, or `undefined` when the recipe would find it
56
+ */
57
+ findObstruction(range: PositionRange | undefined, depth: number, identifier: string): HeadingObstruction | undefined;
58
+ /**
59
+ * Reports whether a line reads as the recipe for its heading expects.
60
+ *
61
+ * @param line - The heading's source line
62
+ * @param depth - The heading's depth
63
+ * @param identifier - The DocID or SectionID parsed from the heading
64
+ * @returns `true` when the line is `#… identifier` followed by an allowed character
65
+ */
66
+ private static tellLineMatches;
67
+ /**
68
+ * The explanation a diagnostic gives for an unnavigable heading.
69
+ *
70
+ * @param obstruction - What stops the recipe from finding it
71
+ * @param depth - The heading's depth
72
+ * @param identifier - The DocID or SectionID parsed from the heading
73
+ * @returns A message naming the obstruction and the fix
74
+ */
75
+ static explain(obstruction: HeadingObstruction, depth: number, identifier: string): string;
76
+ }
77
+ //# sourceMappingURL=heading-source-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-source-form.d.ts","sourceRoot":"","sources":["../src/heading-source-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,0EAA0E;AAC1E,eAAO,MAAM,yBAAyB,EAAE,MAA8B,CAAC;AAEvE;;;;;;;;GAQG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,sBAAsB,GAAG,iBAAiB,CAAC;AAkBrF;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,qEAAqE;IACrE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0B;IAEhD;;;;OAIG;gBACgB,UAAU,EAAE,MAAM,GAAG,SAAS;IAIjD;;;;;;;OAOG;IACI,eAAe,CACpB,KAAK,EAAE,aAAa,GAAG,SAAS,EAChC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,kBAAkB,GAAG,SAAS;IAyBjC;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,CAAC,eAAe;IAY9B;;;;;;;OAOG;WACW,OAAO,CACnB,WAAW,EAAE,kBAAkB,EAC/B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,MAAM;CA8BV"}