@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
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
|
package/dist/ecr.js.map
ADDED
|
@@ -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"}
|