@domorium/validator 2.2.1 → 2.3.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/CHANGELOG.md CHANGED
@@ -2,6 +2,50 @@
2
2
 
3
3
  All notable changes to `@domorium/validator` are documented here.
4
4
 
5
+ ## 2.3.0
6
+
7
+ - **What a structure may hold is answered without a document edit.**
8
+ `getStructureShape(node, tag?)` and `getStructureShapeAtPath(path)` give the
9
+ fields the dialect allows at a node or a schema path, with their cardinality
10
+ and payload kind; `checkPayload` judges one value without parsing the
11
+ document; `escapePayload` and `unescapePayload` write and read back a text
12
+ payload by dialect; `resolveValue` joins a value with its `CONT` and `CONC`
13
+ lines; `getValidatedDialect()` names the dialect the document was checked
14
+ against (#521).
15
+ - **A diagnostic carries the facts its message names in `data`.** `VAL014`
16
+ `{ day, month, monthLength }`, `VAL016` `{ year, calendar }`, `VAL002`
17
+ `{ missingTag, parentTag }`, `VAL001` `{ meantTag }` for a tag in the wrong
18
+ case, `VAL008` `{ tag }`, and `VAL012` `{ missingHeader }` when there is no
19
+ `HEAD` record, so a host can word the message in its reader's language. No
20
+ code, message or range changed (#502, #525).
21
+ - **A missing `TRLR` is reported on the last line**, where it is missing, not
22
+ on the first (#502).
23
+ - **A value is checked before it is written.** `checkPayload` refuses a line
24
+ break where the value may not span lines (`VAL017`), a value on a structure
25
+ that takes none (`VAL018`), an emptied GEDCOM 7 record that holds nothing
26
+ else (`VAL019`), and a GEDCOM 7 `FILE` path naming no file beside the
27
+ document (`VAL020`). These are answered for a value about to be written; a
28
+ file that already holds one gets no new diagnostic (#524, #527, #531).
29
+ - **A 5.5.1 `FORM` that is a known file extension is a warning.** `png`, `pdf`,
30
+ `mp4` and the like were the error `VAL005`, as any value outside
31
+ `[bmp gif jpg ole pcx tif wav]`; they are now the warning `VAL021`, and a
32
+ misspelling stays `VAL005`. `filePathProblem` and `KNOWN_FILE_EXTENSIONS` are
33
+ exported (#531).
34
+ - **A 5.5.1 `EVEN` is described as taking an optional description.** The
35
+ scheme gave the individual and the family `EVEN` no payload, so
36
+ `getStructureShape` answered the kind `none` for a line on which
37
+ `checkPayload` accepted `1 EVEN Appointed Zoning Committee Chairperson`.
38
+ Both now carry the payload type `type-EVENT_DESCRIPTOR`, read as text that
39
+ may be left out (#528).
40
+ - **GEDCOM 7 labels follow the specification.** The schema generator kept
41
+ whatever labels the schema already had, so the specification's later renames
42
+ never reached it. It now reads them from the specification's tag files: the
43
+ deprecated `ADR1`, `ADR2`, `ADR3` and `HEAD.SOUR.DATA` are labelled
44
+ `(deprecated) …`, the four Latter-day Saint ordinances are spelled as the
45
+ specification spells them, and the payload types gain labels. Labels the
46
+ specification does not give, such as `AFN` and the registered `EXID` types,
47
+ are kept.
48
+
5
49
  ## 2.2.1
6
50
 
7
51
  - **A month or a calendar written as a `SCHMA` alias is read as the one it
package/README.md CHANGED
@@ -53,7 +53,21 @@ interface GedcomError {
53
53
  code: string;
54
54
  message: string;
55
55
  hint?: string;
56
- data?: { xref?: string; requiredRecordTag?: string; expectedLevel?: number };
56
+ data?: {
57
+ xref?: string;
58
+ requiredRecordTag?: string;
59
+ expectedLevel?: number;
60
+ missingTag?: string;
61
+ parentTag?: string;
62
+ missingHeader?: boolean;
63
+ day?: number;
64
+ month?: string;
65
+ monthLength?: number;
66
+ year?: number;
67
+ calendar?: string;
68
+ meantTag?: string;
69
+ tag?: string;
70
+ };
57
71
  range: Range;
58
72
  level: "error" | "warning" | "info";
59
73
  }
@@ -64,13 +78,32 @@ interface GedcomError {
64
78
  | `code` | `string`, a `GedcomErrorCode` | What kind of problem this is. Match on this. |
65
79
  | `message` | `string` | Written for a person to read, and rewritten when a clearer wording is found. Do not match on it. |
66
80
  | `hint` | `string?` | Declared for advice alongside the message. Nothing in this package sets it yet, so treat it as absent. |
67
- | `data` | object? | The structured facts a quick fix is built from, where the diagnostic has any. |
81
+ | `data` | object? | The structured facts a quick fix or a message of your own is built from, where the diagnostic has any. |
68
82
  | `range` | `Range` | Zero-based line and character, `end` exclusive, over the smallest text that is wrong — the payload, not the line. |
69
83
  | `level` | `"error" \| "warning" \| "info"` | How much it matters. |
70
84
 
71
85
  `data` is what makes a fix offerable rather than merely reportable: an
72
86
  unresolved pointer reports the xref that failed and the record tag it needed, so
73
- a host can offer to create that record.
87
+ a host can offer to create that record. A missing `TRLR` names the tag, and a
88
+ document with no `HEAD` at all says so, which a header with no version does not.
89
+
90
+ It also holds the facts the message names, so a host can say them in its own
91
+ words and its reader's language without parsing the English:
92
+
93
+ | Code | `data` |
94
+ | ----------------- | -------------------------------------------------------------------------------------------------------------- |
95
+ | `VAL014` | `day`, `month`, `monthLength`: `31`, `"FEB"`, `28` |
96
+ | `VAL016` | `year`, `calendar`: `0`, `"GREGORIAN"` |
97
+ | `VAL002` | `missingTag`, and `parentTag` except at the root: `"LANG"`, `"TRAN"` |
98
+ | `VAL001` | `meantTag`, only for a standard tag in the wrong case: `"NICK"` |
99
+ | `VAL008` | `tag`, the undeclared extension tag: `"_X"` |
100
+ | `VAL018` | `tag`, the structure that takes no value: `"MAP"` |
101
+ | `VAL019` | `tag`, the record a 7.0 empty value would leave with nothing: `"SNOTE"` |
102
+ | `VAL020` | `tag`, `pathProblem` — `"absolute"`, `"parent-folder"` or `"unescaped-character"`: `"FILE"`, `"parent-folder"` |
103
+ | `VAL021` | `tag`, the structure holding the form: `"FORM"` |
104
+ | `unresolved-xref` | `xref`, `requiredRecordTag`: `"@S9@"`, `"SOUR"` |
105
+
106
+ `checkPayload` keeps the same `data` on the problems it answers.
74
107
 
75
108
  For this file, where `SEX` carries a value its schema does not permit and `FAMC`
76
109
  points at a record that is not there:
@@ -123,28 +156,33 @@ const unresolved = errors.filter(
123
156
  );
124
157
  ```
125
158
 
126
- | Code | Member | Reported when |
127
- | ----------------- | ----------------------- | ------------------------------------------------------------------------------ |
128
- | `VAL001` | `UnknownTag` | a tag the schema does not define in this position |
129
- | `VAL002` | `MissingTag` | a structure the schema requires is absent |
130
- | `VAL003` | `MissingValue` | the payload is absent where one is required |
131
- | `VAL004` | `IncorrectValue` | the payload does not match its data type |
132
- | `VAL005` | `ShouldBeSetValue` | the payload is not one of the permitted values |
133
- | `VAL006` | `MissingRef` | a pointer payload that is not written as a pointer |
134
- | `VAL007` | `ManyOccurrences` | more occurrences than the cardinality allows |
135
- | `VAL008` | `UndocumentedTag` | a GEDCOM 7 extension tag with no `HEAD`.`SCHMA` declaration |
136
- | `VAL009` | `DuplicateDeclaration` | a tag declared more than once in `HEAD`.`SCHMA` |
137
- | `VAL010` | `EmptyEvent` | an event with neither a payload nor substructures |
138
- | `VAL011` | `UnsupportedVersion` | the version is one no schema in this package describes |
139
- | `VAL012` | `UndeterminedVersion` | no version could be read from `HEAD`.`GEDC`.`VERS` |
140
- | `VAL013` | `SubstitutedVersion` | a version checked against a different version's schema |
141
- | `VAL014` | `ImpossibleDay` | a date naming a day its calendar does not have, such as `31 FEB` |
142
- | `VAL015` | `PersonalAncestralFile` | a header naming `SYST` before `GEDC`, so the file is a Personal Ancestral File |
143
- | `VAL016` | `ImpossibleYear` | a Gregorian or Julian date naming year 0, which neither calendar numbers |
144
- | `unresolved-xref` | `UnresolvedXref` | an xref naming no record of the required type |
145
- | `invalid-level` | `InvalidLevel` | a level that cannot follow the line above it |
146
- | `LEXER` | `Lexer` | the text could not be tokenized |
147
- | `PARSER` | `Parser` | the tokens could not be assembled into a tree |
159
+ | Code | Member | Reported when |
160
+ | ----------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------- |
161
+ | `VAL001` | `UnknownTag` | a tag the schema does not define in this position |
162
+ | `VAL002` | `MissingTag` | a structure the schema requires is absent |
163
+ | `VAL003` | `MissingValue` | the payload is absent where one is required |
164
+ | `VAL004` | `IncorrectValue` | the payload does not match its data type |
165
+ | `VAL005` | `ShouldBeSetValue` | the payload is not one of the permitted values |
166
+ | `VAL006` | `MissingRef` | a pointer payload that is not written as a pointer |
167
+ | `VAL007` | `ManyOccurrences` | more occurrences than the cardinality allows |
168
+ | `VAL008` | `UndocumentedTag` | a GEDCOM 7 extension tag with no `HEAD`.`SCHMA` declaration |
169
+ | `VAL009` | `DuplicateDeclaration` | a tag declared more than once in `HEAD`.`SCHMA` |
170
+ | `VAL010` | `EmptyEvent` | an event with neither a payload nor substructures |
171
+ | `VAL011` | `UnsupportedVersion` | the version is one no schema in this package describes |
172
+ | `VAL012` | `UndeterminedVersion` | no version could be read from `HEAD`.`GEDC`.`VERS` |
173
+ | `VAL013` | `SubstitutedVersion` | a version checked against a different version's schema |
174
+ | `VAL014` | `ImpossibleDay` | a date naming a day its calendar does not have, such as `31 FEB` |
175
+ | `VAL015` | `PersonalAncestralFile` | a header naming `SYST` before `GEDC`, so the file is a Personal Ancestral File |
176
+ | `VAL016` | `ImpossibleYear` | a Gregorian or Julian date naming year 0, which neither calendar numbers |
177
+ | `VAL017` | `SpansLines` | a line break in a value about to be written whose datatype keeps to one line |
178
+ | `VAL018` | `ValueNotAllowed` | a value about to be written for a structure the standard gives no value |
179
+ | `VAL019` | `EmptyStructure` | an empty value about to be written that would leave a 7.0 record with nothing |
180
+ | `VAL020` | `FileTargetUnsupported` | a 7.0 local file path about to be written that names no file beside the document |
181
+ | `VAL021` | `ToleratedForm` | a warning: a 5.5.1 multimedia `FORM` outside the standard's list that is a known file extension, such as `png` |
182
+ | `unresolved-xref` | `UnresolvedXref` | an xref naming no record of the required type |
183
+ | `invalid-level` | `InvalidLevel` | a level that cannot follow the line above it |
184
+ | `LEXER` | `Lexer` | the text could not be tokenized |
185
+ | `PARSER` | `Parser` | the tokens could not be assembled into a tree |
148
186
 
149
187
  ## Reading the tree
150
188
 
@@ -199,19 +237,31 @@ objects apiece. Read it freely at the edges, and use `startOffset` and
199
237
  Every method reads the document last given to `createDocument`, which returns
200
238
  the same instance so the two can be chained.
201
239
 
202
- | Method | Answers |
203
- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
204
- | `createDocument(text, options?)` | Parses and validates, and returns the document itself. |
205
- | `getErrors()` | Every diagnostic, in the order found. |
206
- | `getNodes()` | The records, each with its substructures. |
207
- | `getVersion()` | The version read from `HEAD`.`GEDC`.`VERS`, as written. |
208
- | `getDialect()` | Which rules were applied: `"5.5.1"` or `"7.0"`. |
209
- | `getVersionResolution()` | How that was decided — `supported`, `substituted`, `unsupported`, `undetermined`, or `paf` for a file whose header names a writing program before its version. |
210
- | `getLabel(node)` | The specification's name for a structure, for a UI to show instead of the tag: `HEAD` answers `Header`. |
211
- | `getPointerTargetTag(node)` | The record tag a pointer line must name: `FAMC` answers `FAM`. |
212
- | `isRecordDeclaration(node)` | Whether the node declares a record other lines can point at. |
213
- | `getCompletions(position, lineText)` | What may be written at a position — `{ label, kind: "tag" \| "enum" \| "pointer", detail? }`. |
214
- | `readDate(payload)` | What a date payload says, read in the document's dialect — or `null` where the diagnostics would reject it as a date value. A document checked against no dialect reads by 5.5.1, then 7.0. See `GedcomDateValue`. |
240
+ | Method | Answers |
241
+ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
242
+ | `createDocument(text, options?)` | Parses and validates, and returns the document itself. |
243
+ | `getErrors()` | Every diagnostic, in the order found. |
244
+ | `getNodes()` | The records, each with its substructures. |
245
+ | `getVersion()` | The version read from `HEAD`.`GEDC`.`VERS`, as written. |
246
+ | `getDialect()` | Which rules were applied: `"5.5.1"` or `"7.0"`. |
247
+ | `getVersionResolution()` | How that was decided — `supported`, `substituted`, `unsupported`, `undetermined` (with `missingHeader` set when there is no `HEAD` at all), or `paf` for a file whose header names a writing program before its version. |
248
+ | `getLabel(node)` | The specification's name for a structure, for a UI to show instead of the tag: `HEAD` answers `Header`. |
249
+ | `getPointerTargetTag(node)` | The record tag a pointer line must name: `FAMC` answers `FAM`. |
250
+ | `getValidatedDialect()` | The dialect the text was checked against, a fragment given one by its options included — `getDialect()` answers nothing for that fragment. |
251
+ | `getStructureShape(node, tag?)` | What the dialect allows at a node, or beneath it under `tag` before it is in the text: its fields with their minimum, maximum, payload kind, whether a value may span lines and whether that is its ordinary case, and whether its own payload is required. An enumeration that takes a known file extension with a warning says `tolerates: "file-extension"`. Nothing for a document with no dialect. |
252
+ | `getStructureShapeAtPath(path)` | The same by a path of tags from a record, `["INDI", "NAME", "TRAN"]`, with no node at all. |
253
+ | `checkPayload(parent, tag, value, replacing?)` | The rules a single value breaks — an unknown tag, one more occurrence than allowed, a value outside an enumeration, a datatype failure, a 7.0 file path that names no file beside the document — answered without parsing anything. |
254
+ | `isRecordDeclaration(node)` | Whether the node declares a record other lines can point at. |
255
+ | `getCompletions(position, lineText)` | What may be written at a position — `{ label, kind: "tag" \| "enum" \| "pointer", detail? }`. |
256
+ | `readDate(payload)` | What a date payload says, read in the document's dialect — or `null` where the diagnostics would reject it as a date value. A document checked against no dialect reads by 5.5.1, then 7.0. See `GedcomDateValue`. |
257
+
258
+ `escapePayload(value, dialect, kind)` and `unescapePayload(...)` write and read the at sign of a text payload as
259
+ the dialect does — every one in 5.5.1, the first of each line in 7.0 — and leave a pointer, a date and the
260
+ other kinds as given.
261
+
262
+ `filePathProblem(path)` answers why a GEDCOM 7 local file reference — a value with no URI scheme; a caller sets URLs aside first — names no file beside the document —
263
+ `"absolute"`, `"parent-folder"` or `"unescaped-character"` — or nothing, and `KNOWN_FILE_EXTENSIONS` holds the
264
+ extensions a 5.5.1 `FORM` may name with a warning rather than an error.
215
265
 
216
266
  ## Scripts
217
267
 
@@ -4,6 +4,7 @@ import { type GedcomDialect, type VersionResolution } from "../validator/version
4
4
  import { type GedcomCompletion } from "../completion/completion";
5
5
  import type { Position } from "../types/position";
6
6
  import type { GedcomDateValue } from "../types/date";
7
+ import type { PayloadProblem, StructureShape } from "../types/structure";
7
8
  export interface CreateDocumentOptions {
8
9
  /** Text that is part of a document rather than one of its own. */
9
10
  fragment?: boolean;
@@ -24,10 +25,16 @@ export declare class GedcomDocument {
24
25
  private applyScheme;
25
26
  private validateLevels;
26
27
  getLabel(node: ASTNode): string | undefined;
28
+ getStructureShape(node: ASTNode, tag?: string): StructureShape | undefined;
29
+ getStructureShapeAtPath(path: readonly string[]): StructureShape | undefined;
30
+ /** `replacing` is the occurrence a set overwrites; it does not count against the maximum. */
31
+ checkPayload(parent: ASTNode | readonly string[], tag: string, value: string, replacing?: ASTNode): PayloadProblem[] | undefined;
27
32
  getPointerTargetTag(node: ASTNode): string | undefined;
28
33
  isRecordDeclaration(node: ASTNode): boolean;
29
34
  getVersion(): string | undefined;
30
35
  getVersionResolution(): VersionResolution | undefined;
36
+ /** The dialect the text was checked against, a fragment's own included. */
37
+ getValidatedDialect(): GedcomDialect | undefined;
31
38
  getDialect(): GedcomDialect | undefined;
32
39
  getCompletions(position: Position, lineText: string): GedcomCompletion[];
33
40
  /**