@timiagama/ecr 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +16 -0
  3. package/README.md +411 -7
  4. package/dist/bin.d.ts +20 -0
  5. package/dist/bin.d.ts.map +1 -0
  6. package/dist/bin.js +26 -0
  7. package/dist/bin.js.map +1 -0
  8. package/dist/cli/corpus-loader.d.ts +198 -0
  9. package/dist/cli/corpus-loader.d.ts.map +1 -0
  10. package/dist/cli/corpus-loader.js +351 -0
  11. package/dist/cli/corpus-loader.js.map +1 -0
  12. package/dist/cli/corpus-statistics.d.ts +113 -0
  13. package/dist/cli/corpus-statistics.d.ts.map +1 -0
  14. package/dist/cli/corpus-statistics.js +119 -0
  15. package/dist/cli/corpus-statistics.js.map +1 -0
  16. package/dist/cli/diagnostic-reporter.d.ts +103 -0
  17. package/dist/cli/diagnostic-reporter.d.ts.map +1 -0
  18. package/dist/cli/diagnostic-reporter.js +254 -0
  19. package/dist/cli/diagnostic-reporter.js.map +1 -0
  20. package/dist/cli/project-ignore.d.ts +113 -0
  21. package/dist/cli/project-ignore.d.ts.map +1 -0
  22. package/dist/cli/project-ignore.js +199 -0
  23. package/dist/cli/project-ignore.js.map +1 -0
  24. package/dist/cli/safe-text.d.ts +25 -0
  25. package/dist/cli/safe-text.d.ts.map +1 -0
  26. package/dist/cli/safe-text.js +34 -0
  27. package/dist/cli/safe-text.js.map +1 -0
  28. package/dist/cli/supervisor.d.ts +108 -0
  29. package/dist/cli/supervisor.d.ts.map +1 -0
  30. package/dist/cli/supervisor.js +139 -0
  31. package/dist/cli/supervisor.js.map +1 -0
  32. package/dist/cli.d.ts +247 -0
  33. package/dist/cli.d.ts.map +1 -0
  34. package/dist/cli.js +591 -0
  35. package/dist/cli.js.map +1 -0
  36. package/dist/corpus-validator.d.ts +183 -0
  37. package/dist/corpus-validator.d.ts.map +1 -0
  38. package/dist/corpus-validator.js +479 -0
  39. package/dist/corpus-validator.js.map +1 -0
  40. package/dist/document-identity-rule.d.ts +198 -0
  41. package/dist/document-identity-rule.d.ts.map +1 -0
  42. package/dist/document-identity-rule.js +244 -0
  43. package/dist/document-identity-rule.js.map +1 -0
  44. package/dist/ecr.d.ts +103 -0
  45. package/dist/ecr.d.ts.map +1 -0
  46. package/dist/ecr.js +216 -0
  47. package/dist/ecr.js.map +1 -0
  48. package/dist/glob-pattern.d.ts +94 -0
  49. package/dist/glob-pattern.d.ts.map +1 -0
  50. package/dist/glob-pattern.js +188 -0
  51. package/dist/glob-pattern.js.map +1 -0
  52. package/dist/heading-source-form.d.ts +77 -0
  53. package/dist/heading-source-form.d.ts.map +1 -0
  54. package/dist/heading-source-form.js +129 -0
  55. package/dist/heading-source-form.js.map +1 -0
  56. package/dist/identifier-grammar.d.ts +212 -0
  57. package/dist/identifier-grammar.d.ts.map +1 -0
  58. package/dist/identifier-grammar.js +273 -0
  59. package/dist/identifier-grammar.js.map +1 -0
  60. package/dist/index.d.ts +19 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +11 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/inline-reference-rule.d.ts +557 -0
  65. package/dist/inline-reference-rule.d.ts.map +1 -0
  66. package/dist/inline-reference-rule.js +802 -0
  67. package/dist/inline-reference-rule.js.map +1 -0
  68. package/dist/meta-documents.d.ts +91 -0
  69. package/dist/meta-documents.d.ts.map +1 -0
  70. package/dist/meta-documents.js +133 -0
  71. package/dist/meta-documents.js.map +1 -0
  72. package/dist/per-document-visitor.d.ts +510 -0
  73. package/dist/per-document-visitor.d.ts.map +1 -0
  74. package/dist/per-document-visitor.js +1032 -0
  75. package/dist/per-document-visitor.js.map +1 -0
  76. package/dist/references-section-rule.d.ts +482 -0
  77. package/dist/references-section-rule.d.ts.map +1 -0
  78. package/dist/references-section-rule.js +777 -0
  79. package/dist/references-section-rule.js.map +1 -0
  80. package/dist/section-hierarchy-rule.d.ts +299 -0
  81. package/dist/section-hierarchy-rule.d.ts.map +1 -0
  82. package/dist/section-hierarchy-rule.js +482 -0
  83. package/dist/section-hierarchy-rule.js.map +1 -0
  84. package/dist/source-alignment.d.ts +41 -0
  85. package/dist/source-alignment.d.ts.map +1 -0
  86. package/dist/source-alignment.js +166 -0
  87. package/dist/source-alignment.js.map +1 -0
  88. package/dist/source-lines.d.ts +69 -0
  89. package/dist/source-lines.d.ts.map +1 -0
  90. package/dist/source-lines.js +104 -0
  91. package/dist/source-lines.js.map +1 -0
  92. package/dist/spec-version.d.ts +19 -0
  93. package/dist/spec-version.d.ts.map +1 -0
  94. package/dist/spec-version.js +19 -0
  95. package/dist/spec-version.js.map +1 -0
  96. package/dist/types.d.ts +219 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +11 -0
  99. package/dist/types.js.map +1 -0
  100. package/examples/docs/0. Orientation/0.0 - System Overview.md +34 -0
  101. package/examples/docs/0. Orientation/0.0.1 - Glossary.md +21 -0
  102. package/examples/docs/0. Orientation/0.0.2 - Quality Gates.md +22 -0
  103. package/examples/docs/3. Ingestion/3.1 - Ingestion - Validation Rules.md +33 -0
  104. package/examples/docs/4. Payments/4.10 - Settlement.md +22 -0
  105. package/examples/docs/4. Payments/4.2 - Payment Processing Contract.md +33 -0
  106. package/examples/docs/8. Orchestration/8.1 - Workflow Orchestration Contract.md +38 -0
  107. package/examples/docs/8. Orchestration/8.1.3 - Retry Policy.md +17 -0
  108. package/examples/docs/8. Orchestration/8.10 - Dead Letter Queue.md +20 -0
  109. package/examples/docs/README.md +7 -0
  110. package/package.json +65 -7
  111. package/protocol/navigation-protocol.md +223 -0
  112. package/spec/v2/1 - ECR - Structural Specification.md +1467 -0
  113. package/spec/v2/2 - ECR - User Guide.md +845 -0
  114. package/spec/v2/3 - Design Rationale - The Section Separator.md +243 -0
  115. package/src/bin.ts +30 -0
  116. package/src/cli/corpus-loader.ts +482 -0
  117. package/src/cli/corpus-statistics.ts +196 -0
  118. package/src/cli/diagnostic-reporter.ts +344 -0
  119. package/src/cli/project-ignore.ts +238 -0
  120. package/src/cli/safe-text.ts +39 -0
  121. package/src/cli/supervisor.ts +204 -0
  122. package/src/cli.ts +836 -0
  123. package/src/corpus-validator.ts +642 -0
  124. package/src/document-identity-rule.ts +422 -0
  125. package/src/ecr.ts +298 -0
  126. package/src/glob-pattern.ts +242 -0
  127. package/src/heading-source-form.ts +173 -0
  128. package/src/identifier-grammar.ts +380 -0
  129. package/src/index.ts +78 -0
  130. package/src/inline-reference-rule.ts +1235 -0
  131. package/src/meta-documents.ts +159 -0
  132. package/src/per-document-visitor.ts +1528 -0
  133. package/src/references-section-rule.ts +1182 -0
  134. package/src/section-hierarchy-rule.ts +705 -0
  135. package/src/source-alignment.ts +209 -0
  136. package/src/source-lines.ts +123 -0
  137. package/src/spec-version.ts +19 -0
  138. package/src/types.ts +267 -0
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Document Identity Rule (ECR101)
3
+ *
4
+ * Validates the document identity constraint defined in the ECR specification:
5
+ * - 1#9.3 -- Document Identity Rule [ECR101]
6
+ * - 1#9.8 -- Per-Document Structural Invariants ("Exactly one valid H1 DocID")
7
+ *
8
+ * This rule consumes heading node data from an AST traversal and validates
9
+ * the "exactly one H1 with a valid DocID and title" constraint. It delegates
10
+ * identifier parsing to {@link IdentifierGrammar} and emits {@link Diagnostic}
11
+ * objects for any violations.
12
+ */
13
+
14
+ import type { DocID, Diagnostic, DiagnosticSeverity, PositionRange } from './types.js';
15
+ import type {
16
+ HeadingParseResult,
17
+ SeparatorValidationResult,
18
+ } from './identifier-grammar.js';
19
+ import { IdentifierGrammar } from './identifier-grammar.js';
20
+ import { HEADING_SOURCE_FORM_CAUSE, HeadingSourceForm } from './heading-source-form.js';
21
+ import type { HeadingObstruction } from './heading-source-form.js';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Rule identifier constant
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Canonical rule identifier for the Document Identity Rule.
29
+ *
30
+ * Referenced as [ECR101] in the ECR specification (1#9.3).
31
+ */
32
+ export const DOCUMENT_IDENTITY_RULE_ID: string = 'ECR101';
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Input type
36
+ // ---------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Data extracted from a single heading AST node, provided by the
40
+ * document traversal layer.
41
+ *
42
+ * The rule does not parse Markdown itself; it operates on heading
43
+ * data supplied by the visitor/traversal infrastructure.
44
+ */
45
+ export interface HeadingNodeData {
46
+ /** Markdown heading depth (1 for H1, 2 for H2, etc.). */
47
+ readonly depth: number;
48
+ /** Plain text content of the heading node (e.g. `"3.1 - Scenario Authoring"`). */
49
+ readonly text: string;
50
+ /**
51
+ * Positional range of the heading node within the source document.
52
+ * Optional; depends on whether the Markdown parser provides positional metadata.
53
+ */
54
+ readonly range?: PositionRange;
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Result types
59
+ // ---------------------------------------------------------------------------
60
+
61
+ /**
62
+ * Represents the successfully extracted document identity from a valid H1 heading.
63
+ */
64
+ export interface DocumentIdentity {
65
+ /** The DocID extracted from the H1 heading. */
66
+ readonly docId: DocID;
67
+ /** The title extracted from the H1 heading (display/diagnostic metadata only). */
68
+ readonly title: string;
69
+ /** Positional range of the H1 heading, when available. */
70
+ readonly range?: PositionRange;
71
+ }
72
+
73
+ /**
74
+ * The complete result produced by finalising the Document Identity Rule
75
+ * after all headings have been evaluated.
76
+ *
77
+ * Contains zero or more diagnostics and, when the rule passes,
78
+ * the extracted document identity.
79
+ */
80
+ export interface DocumentIdentityRuleResult {
81
+ /** Diagnostics emitted during evaluation (errors for violations, info/warnings as applicable). */
82
+ readonly diagnostics: readonly Diagnostic[];
83
+ /**
84
+ * The extracted document identity.
85
+ * Present only when exactly one valid H1 heading was found.
86
+ */
87
+ readonly identity?: DocumentIdentity;
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Constructor options
92
+ // ---------------------------------------------------------------------------
93
+
94
+ /**
95
+ * Configuration options for constructing a {@link DocumentIdentityRule} instance.
96
+ */
97
+ export interface DocumentIdentityRuleOptions {
98
+ /**
99
+ * The opaque, host-provided URI identifying the document being validated.
100
+ * Attached to all emitted diagnostics.
101
+ */
102
+ readonly uri: string;
103
+ /**
104
+ * The {@link IdentifierGrammar} instance used for DocID parsing
105
+ * and separator validation.
106
+ */
107
+ readonly grammar: IdentifierGrammar;
108
+ /**
109
+ * The document's raw Markdown, for the heading source-form check of 1#9.11
110
+ * rule 1. When absent, as in unit tests that supply only parsed heading
111
+ * text, the check is skipped.
112
+ */
113
+ readonly sourceText?: string;
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // Internal collected heading record
118
+ // ---------------------------------------------------------------------------
119
+
120
+ /**
121
+ * Internal record capturing the data from a single H1 heading
122
+ * encountered during evaluation.
123
+ */
124
+ interface CollectedH1Heading {
125
+ /** Plain text content of the H1 heading. */
126
+ readonly text: string;
127
+ /** Positional range of the H1 heading, when available. */
128
+ readonly range?: PositionRange;
129
+ }
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // Diagnostic severity constant
133
+ // ---------------------------------------------------------------------------
134
+
135
+ /**
136
+ * The severity used for all Document Identity Rule diagnostics.
137
+ *
138
+ * Per 1#9.8, violation of any per-document structural invariant is an ERROR.
139
+ */
140
+ const DIAGNOSTIC_SEVERITY: DiagnosticSeverity = 'error';
141
+
142
+ /**
143
+ * Matches heading text that begins with a number, i.e. one that at least
144
+ * attempts a DocID.
145
+ */
146
+ const STARTS_WITH_DIGIT_PATTERN: RegExp = /^\s*\d/;
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Rule class
150
+ // ---------------------------------------------------------------------------
151
+
152
+ /**
153
+ * Validates the Document Identity Rule as defined in the ECR specification (1#9.3, 1#9.8).
154
+ *
155
+ * The rule enforces that a document contains exactly one H1 heading whose
156
+ * text matches `DocID <dash> Title`, where DocID conforms to the identifier
157
+ * grammar and Title is non-empty.
158
+ *
159
+ * Usage:
160
+ * 1. Construct a rule instance with the document URI and an {@link IdentifierGrammar}.
161
+ * 2. Call {@link evaluateHeading} for every heading node encountered during AST traversal.
162
+ * 3. Call {@link finalise} after all headings have been evaluated to obtain
163
+ * the complete result including any "missing H1" diagnostics.
164
+ *
165
+ * @example
166
+ * ```ts
167
+ * const grammar = new IdentifierGrammar();
168
+ * const rule = new DocumentIdentityRule({ uri: 'file:///doc.md', grammar });
169
+ *
170
+ * rule.evaluateHeading({ depth: 1, text: '3.1 - My Document', range: someRange });
171
+ * rule.evaluateHeading({ depth: 2, text: '3.1#1 - Section', range: anotherRange });
172
+ *
173
+ * const result: DocumentIdentityRuleResult = rule.finalise();
174
+ * ```
175
+ */
176
+ export class DocumentIdentityRule {
177
+ /** The opaque, host-provided URI identifying the document being validated. */
178
+ private readonly uri: string;
179
+
180
+ /** The grammar instance used for identifier parsing and separator validation. */
181
+ private readonly grammar: IdentifierGrammar;
182
+
183
+ /** All H1 headings encountered during evaluation, in traversal order. */
184
+ private readonly collectedH1Headings: CollectedH1Heading[];
185
+
186
+ /** Checks the H1's source line against the document recipe. */
187
+ private readonly headingSourceForm: HeadingSourceForm;
188
+
189
+ /**
190
+ * Constructs a new Document Identity Rule evaluator.
191
+ *
192
+ * @param options - Configuration including the document URI and grammar instance
193
+ */
194
+ public constructor(options: DocumentIdentityRuleOptions) {
195
+ this.uri = options.uri;
196
+ this.grammar = options.grammar;
197
+ this.collectedH1Headings = [];
198
+ this.headingSourceForm = new HeadingSourceForm(options.sourceText);
199
+ }
200
+
201
+ /**
202
+ * Evaluates a single heading node against the document identity constraint.
203
+ *
204
+ * For H1 headings (depth === 1), the rule records the heading for
205
+ * analysis during {@link finalise}. Headings with depth \> 1 are
206
+ * ignored by this rule.
207
+ *
208
+ * @param headingNodeData - Data extracted from a heading AST node
209
+ */
210
+ public evaluateHeading(headingNodeData: HeadingNodeData): void {
211
+ if (headingNodeData.depth !== 1) {
212
+ return;
213
+ }
214
+
215
+ const collectedHeading: CollectedH1Heading = {
216
+ text: headingNodeData.text,
217
+ ...(headingNodeData.range !== undefined ? { range: headingNodeData.range } : {}),
218
+ };
219
+
220
+ this.collectedH1Headings.push(collectedHeading);
221
+ }
222
+
223
+ /**
224
+ * Finalises the rule evaluation and produces the complete result.
225
+ *
226
+ * This method must be called after all heading nodes have been supplied
227
+ * via {@link evaluateHeading}. It analyses all collected H1 headings
228
+ * and emits appropriate diagnostics:
229
+ *
230
+ * - Zero H1 headings: emits a "missing H1" error diagnostic
231
+ * - Multiple H1 headings: emits a "multiple H1" error diagnostic
232
+ * - Exactly one H1 with invalid separator: emits a separator error diagnostic
233
+ * - Exactly one H1 with invalid format: emits a format error diagnostic
234
+ * - Exactly one valid H1: extracts the document identity
235
+ *
236
+ * @returns The complete rule result including all diagnostics and,
237
+ * when valid, the extracted document identity
238
+ */
239
+ public finalise(): DocumentIdentityRuleResult {
240
+ const headingCount: number = this.collectedH1Headings.length;
241
+
242
+ if (headingCount === 0) {
243
+ return this.buildMissingH1Result();
244
+ }
245
+
246
+ if (headingCount > 1) {
247
+ return this.buildMultipleH1Result();
248
+ }
249
+
250
+ return this.buildSingleH1Result();
251
+ }
252
+
253
+ // -------------------------------------------------------------------------
254
+ // Private helpers
255
+ // -------------------------------------------------------------------------
256
+
257
+ /**
258
+ * Builds the result for the case where no H1 headings were found.
259
+ *
260
+ * @returns A rule result containing a "missing H1" error diagnostic
261
+ */
262
+ private buildMissingH1Result(): DocumentIdentityRuleResult {
263
+ const diagnostic: Diagnostic = this.createDiagnostic(
264
+ 'Document is missing an H1 heading with a valid DocID.',
265
+ );
266
+
267
+ return { diagnostics: [diagnostic] };
268
+ }
269
+
270
+ /**
271
+ * Builds the result for the case where multiple H1 headings were found.
272
+ *
273
+ * @returns A rule result containing a "multiple H1" error diagnostic
274
+ */
275
+ private buildMultipleH1Result(): DocumentIdentityRuleResult {
276
+ const firstHeading: CollectedH1Heading | undefined = this.collectedH1Headings[0];
277
+
278
+ const headingCount: string = String(this.collectedH1Headings.length);
279
+
280
+ const diagnostic: Diagnostic = this.createDiagnostic(
281
+ `Document contains ${headingCount} H1 headings, but exactly one is required.`,
282
+ firstHeading?.range,
283
+ );
284
+
285
+ return { diagnostics: [diagnostic] };
286
+ }
287
+
288
+ /**
289
+ * Builds the result for the case where exactly one H1 heading was found.
290
+ *
291
+ * Validates the separator and heading format, emitting appropriate
292
+ * diagnostics for violations or extracting the document identity on success.
293
+ *
294
+ * @returns A rule result containing diagnostics and/or the extracted identity
295
+ */
296
+ private buildSingleH1Result(): DocumentIdentityRuleResult {
297
+ const heading: CollectedH1Heading | undefined = this.collectedH1Headings[0];
298
+
299
+ if (heading === undefined) {
300
+ return this.buildMissingH1Result();
301
+ }
302
+
303
+ const separatorDiagnostic: Diagnostic | undefined =
304
+ this.validateHeadingSeparator(heading);
305
+
306
+ if (separatorDiagnostic !== undefined) {
307
+ return { diagnostics: [separatorDiagnostic] };
308
+ }
309
+
310
+ const headingParseResult: HeadingParseResult =
311
+ this.grammar.parseHeading(heading.text);
312
+
313
+ if (!headingParseResult.valid) {
314
+ const formatDiagnostic: Diagnostic = this.createDiagnostic(
315
+ `H1 heading does not match the required format: DocID <dash> Title.`,
316
+ heading.range,
317
+ );
318
+
319
+ return { diagnostics: [formatDiagnostic] };
320
+ }
321
+
322
+ const identity: DocumentIdentity = {
323
+ docId: headingParseResult.docId,
324
+ title: headingParseResult.title,
325
+ ...(heading.range !== undefined ? { range: heading.range } : {}),
326
+ };
327
+
328
+ // An H1 no recipe can find is reported, but its identity is still
329
+ // established. Withholding it would skip every other rule for the
330
+ // document and turn each citation of it elsewhere into an unresolved
331
+ // target -- one mistake reported many times over. The document fails
332
+ // either way, and the guarantee covers only corpora that pass.
333
+ const obstruction: HeadingObstruction | undefined = this.headingSourceForm.findObstruction(
334
+ heading.range,
335
+ 1,
336
+ identity.docId,
337
+ );
338
+
339
+ if (obstruction !== undefined) {
340
+ return {
341
+ diagnostics: [
342
+ this.createDiagnostic(
343
+ HeadingSourceForm.explain(obstruction, 1, identity.docId),
344
+ heading.range,
345
+ { cause: HEADING_SOURCE_FORM_CAUSE, obstruction, docId: identity.docId },
346
+ ),
347
+ ],
348
+ identity,
349
+ };
350
+ }
351
+
352
+ return { diagnostics: [], identity };
353
+ }
354
+
355
+ /**
356
+ * Validates the separator used in an H1 heading.
357
+ *
358
+ * Delegates to {@link IdentifierGrammar.validateSeparator} and, when an
359
+ * invalid separator is detected, returns an error diagnostic describing
360
+ * the violation.
361
+ *
362
+ * @param heading - The collected H1 heading to validate
363
+ * @returns A diagnostic if the separator is invalid, or `undefined` if valid
364
+ */
365
+ private validateHeadingSeparator(
366
+ heading: CollectedH1Heading,
367
+ ): Diagnostic | undefined {
368
+ const separatorResult: SeparatorValidationResult =
369
+ this.grammar.validateSeparator(heading.text);
370
+
371
+ if (separatorResult.valid) {
372
+ return undefined;
373
+ }
374
+
375
+ // An H1 with no number at all is the usual case in an unconverted
376
+ // document, and deserves a message that says so.
377
+ if (!STARTS_WITH_DIGIT_PATTERN.test(heading.text)) {
378
+ return this.createDiagnostic(
379
+ `H1 "${heading.text.trim()}" is not numbered. The H1 carries the document's DocID, ` +
380
+ `for example "3.1 - ${heading.text.trim()}".`,
381
+ heading.range,
382
+ );
383
+ }
384
+
385
+ const diagnostic: Diagnostic = this.createDiagnostic(
386
+ `H1 heading has no recognisable separator. ` +
387
+ `Expected: a number, a dash (-, – or —), then the title.`,
388
+ heading.range,
389
+ );
390
+
391
+ return diagnostic;
392
+ }
393
+
394
+
395
+ /**
396
+ * Creates a diagnostic object for the Document Identity Rule.
397
+ *
398
+ * All diagnostics share the same rule ID ({@link DOCUMENT_IDENTITY_RULE_ID}),
399
+ * severity (error), and document URI.
400
+ *
401
+ * @param message - Human-readable description of the issue
402
+ * @param range - Optional positional range of the heading within the source document
403
+ * @param data - Optional machine-readable detail, such as a `cause`
404
+ * @returns A fully populated diagnostic object
405
+ */
406
+ private createDiagnostic(
407
+ message: string,
408
+ range?: PositionRange,
409
+ data?: Readonly<Record<string, unknown>>,
410
+ ): Diagnostic {
411
+ const diagnostic: Diagnostic = {
412
+ ruleId: DOCUMENT_IDENTITY_RULE_ID,
413
+ severity: DIAGNOSTIC_SEVERITY,
414
+ message,
415
+ uri: this.uri,
416
+ ...(range !== undefined ? { range } : {}),
417
+ ...(data !== undefined ? { data } : {}),
418
+ };
419
+
420
+ return diagnostic;
421
+ }
422
+ }