@xdbml/parse 0.4.0 → 0.5.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/dist/ast.d.ts +24 -8
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/keywords.d.ts +2 -2
- package/dist/keywords.js +7 -0
- package/dist/module-resolver.d.ts +3 -3
- package/dist/module-resolver.js +21 -11
- package/dist/name-resolver.d.ts +2 -2
- package/dist/name-resolver.js +4 -1
- package/dist/parser.d.ts +31 -7
- package/dist/parser.js +111 -17
- package/dist/supertypes.d.ts +110 -0
- package/dist/supertypes.js +526 -0
- package/package.json +1 -1
package/dist/parser.js
CHANGED
|
@@ -13,11 +13,37 @@ import { TokenKind, tokenize, } from "./lexer.js";
|
|
|
13
13
|
import { resolveImport, classifyModuleSource, ModuleSourceError } from "./module-resolver.js";
|
|
14
14
|
export class ParseError extends Error {
|
|
15
15
|
position;
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Optional machine-readable reason. Set for the errors a caller may want
|
|
18
|
+
* to tell apart from a plain syntax error: `unsupported-version` when a
|
|
19
|
+
* document declares a newer version than this parser supports (spec 4.1).
|
|
20
|
+
*/
|
|
21
|
+
code;
|
|
22
|
+
constructor(message, position, code) {
|
|
17
23
|
super(`${message} (line ${position.line}, column ${position.column})`);
|
|
18
24
|
this.position = position;
|
|
25
|
+
if (code)
|
|
26
|
+
this.code = code;
|
|
19
27
|
}
|
|
20
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The newest specification version this parser implements. A document
|
|
31
|
+
* declaring a later version is refused (spec 4.1) rather than parsed with
|
|
32
|
+
* semantics it does not have.
|
|
33
|
+
*/
|
|
34
|
+
export const SUPPORTED_XDBML_VERSION = '0.5';
|
|
35
|
+
/** Compare dotted version strings numerically: -1, 0 or 1. */
|
|
36
|
+
export function compareVersions(a, b) {
|
|
37
|
+
const pa = a.split('.').map((n) => Number(n) || 0);
|
|
38
|
+
const pb = b.split('.').map((n) => Number(n) || 0);
|
|
39
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
40
|
+
const x = pa[i] ?? 0;
|
|
41
|
+
const y = pb[i] ?? 0;
|
|
42
|
+
if (x !== y)
|
|
43
|
+
return x < y ? -1 : 1;
|
|
44
|
+
}
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
21
47
|
/* -------------------------------------------------------------------------
|
|
22
48
|
* Keyword recognition.
|
|
23
49
|
*
|
|
@@ -31,13 +57,13 @@ const CONTAINER_KEYWORDS = new Set([
|
|
|
31
57
|
const ENTITY_KEYWORDS = new Set(['table', 'entity', 'collection', 'record']);
|
|
32
58
|
/**
|
|
33
59
|
* Element-type keywords accepted in module-system import items
|
|
34
|
-
* (spec §
|
|
60
|
+
* (spec §27.3). Stored lowercased; matching is case-insensitive.
|
|
35
61
|
*
|
|
36
62
|
* `field` is recognized but explicitly rejected by parseImportItem in P4
|
|
37
63
|
* (field-level imports have special declaration-vs-placement semantics
|
|
38
64
|
* that will land in a later batch).
|
|
39
65
|
*
|
|
40
|
-
* `project` is intentionally excluded -- spec §
|
|
66
|
+
* `project` is intentionally excluded -- spec §27.1 forbids importing
|
|
41
67
|
* Project declarations.
|
|
42
68
|
*/
|
|
43
69
|
const IMPORT_ELEMENT_TYPES = new Set([
|
|
@@ -45,7 +71,7 @@ const IMPORT_ELEMENT_TYPES = new Set([
|
|
|
45
71
|
'enum', 'tablepartial', 'note',
|
|
46
72
|
'schema', 'container', 'tablegroup',
|
|
47
73
|
'type', 'edge', 'view', 'diagramview',
|
|
48
|
-
'field',
|
|
74
|
+
'field', 'supertypegroup',
|
|
49
75
|
]);
|
|
50
76
|
const STRUCTURAL_TYPE_KEYWORDS = new Set([
|
|
51
77
|
'object', 'struct', 'record', 'array', 'list', 'map', 'dict', 'dictionary',
|
|
@@ -98,7 +124,7 @@ export class Parser {
|
|
|
98
124
|
* The set of file paths currently being parsed in the resolution chain.
|
|
99
125
|
* Used for cycle detection: when resolving a directive whose `from` path
|
|
100
126
|
* is already in this set, the parser produces an empty clone for that
|
|
101
|
-
* directive rather than recursing (matching spec §
|
|
127
|
+
* directive rather than recursing (matching spec §27.15: cycles are
|
|
102
128
|
* allowed; name resolution handles them). The set is passed by reference
|
|
103
129
|
* across recursive parse() calls so all transitive levels see it.
|
|
104
130
|
*
|
|
@@ -179,6 +205,11 @@ export class Parser {
|
|
|
179
205
|
this.advance(); // xdbml
|
|
180
206
|
this.expect(TokenKind.Colon, "Expected ':' after 'xdbml'");
|
|
181
207
|
const numTok = this.expect(TokenKind.NumberLiteral, 'Expected version number');
|
|
208
|
+
if (compareVersions(numTok.text, SUPPORTED_XDBML_VERSION) > 0) {
|
|
209
|
+
throw new ParseError(`This document declares 'xdbml: ${numTok.text}', which is newer than the ` +
|
|
210
|
+
`latest version this parser supports (${SUPPORTED_XDBML_VERSION}). ` +
|
|
211
|
+
'Use a newer parser, or declare a supported version.', numTok.start, 'unsupported-version');
|
|
212
|
+
}
|
|
182
213
|
return {
|
|
183
214
|
kind: 'VersionDeclaration',
|
|
184
215
|
version: numTok.text,
|
|
@@ -231,6 +262,8 @@ export class Parser {
|
|
|
231
262
|
return this.parseTablePartial();
|
|
232
263
|
if (k === 'tablegroup')
|
|
233
264
|
return this.parseTableGroup();
|
|
265
|
+
if (k === 'supertypegroup')
|
|
266
|
+
return this.parseSupertypeGroup();
|
|
234
267
|
if (k === 'note')
|
|
235
268
|
return this.parseNoteDeclaration();
|
|
236
269
|
if (k === 'records')
|
|
@@ -477,7 +510,7 @@ export class Parser {
|
|
|
477
510
|
};
|
|
478
511
|
}
|
|
479
512
|
/**
|
|
480
|
-
* Parse a `records { ... }` block inside an entity body (§
|
|
513
|
+
* Parse a `records { ... }` block inside an entity body (§26.1, implicit
|
|
481
514
|
* column list). Values are stored as SettingValue cells; row boundaries
|
|
482
515
|
* are determined by source line (see `parseRecordRow`).
|
|
483
516
|
*/
|
|
@@ -497,7 +530,7 @@ export class Parser {
|
|
|
497
530
|
};
|
|
498
531
|
}
|
|
499
532
|
/**
|
|
500
|
-
* Top-level records declaration (§
|
|
533
|
+
* Top-level records declaration (§26.2, new in v0.2):
|
|
501
534
|
*
|
|
502
535
|
* records users (id, name, email) { ... }
|
|
503
536
|
* records core.users (id, name, email) { ... }
|
|
@@ -587,7 +620,7 @@ export class Parser {
|
|
|
587
620
|
span: this.spanFrom(start),
|
|
588
621
|
};
|
|
589
622
|
}
|
|
590
|
-
/* ----- Module-system directives (spec §
|
|
623
|
+
/* ----- Module-system directives (spec §27, new in v0.2) ----- */
|
|
591
624
|
/**
|
|
592
625
|
* Parse a `use` or `reuse` directive. Called from both the top-level
|
|
593
626
|
* dispatcher and the Container body dispatcher; the caller indicates
|
|
@@ -701,7 +734,7 @@ export class Parser {
|
|
|
701
734
|
resolvedPath = result.resolvedPath;
|
|
702
735
|
break;
|
|
703
736
|
case 'cycle':
|
|
704
|
-
// Per spec §
|
|
737
|
+
// Per spec §27.15, cycles are allowed; the parser produces a
|
|
705
738
|
// directive with no clone, and name resolution (P6+) is
|
|
706
739
|
// expected to bridge the cycle. We leave clone undefined.
|
|
707
740
|
resolvedPath = result.resolvedPath;
|
|
@@ -759,12 +792,12 @@ export class Parser {
|
|
|
759
792
|
`Expected one of: ${Array.from(IMPORT_ELEMENT_TYPES).join(', ')}.`, elemTok.start);
|
|
760
793
|
}
|
|
761
794
|
if (elementType === 'field' && context !== 'file-scope') {
|
|
762
|
-
// Spec §
|
|
795
|
+
// Spec §27.8: field imports must appear at file scope. Inside a
|
|
763
796
|
// Container body, the field's eventual placement (as a Named Type)
|
|
764
797
|
// would have no meaningful container scope -- field imports are
|
|
765
798
|
// always lifted to file scope by flatten(), regardless of where
|
|
766
799
|
// the directive sits.
|
|
767
|
-
throw new ParseError(`Field-level imports must appear at file scope, not inside a Container body (spec §
|
|
800
|
+
throw new ParseError(`Field-level imports must appear at file scope, not inside a Container body (spec §27.8).`, elemTok.start);
|
|
768
801
|
}
|
|
769
802
|
this.advance(); // consume element type keyword
|
|
770
803
|
// Dotted source path.
|
|
@@ -795,12 +828,12 @@ export class Parser {
|
|
|
795
828
|
* that match the import items by name and element type (matching is
|
|
796
829
|
* downstream-consumer's job; the parser is permissive).
|
|
797
830
|
*
|
|
798
|
-
* Per spec §
|
|
831
|
+
* Per spec §27.6, clone content uses the importing file's vocabulary
|
|
799
832
|
* (aliases already applied) and is parsed under the importing file's
|
|
800
833
|
* xdbml version directive.
|
|
801
834
|
*
|
|
802
835
|
* Most clone-block content uses TopLevelStatement shapes (Entity, Type,
|
|
803
|
-
* Container, etc.). The exception is field imports (§
|
|
836
|
+
* Container, etc.). The exception is field imports (§27.8): when the
|
|
804
837
|
* directive imports one or more fields via `field <path>` items, the
|
|
805
838
|
* clone block holds each field as a bare FieldDeclaration with no entity
|
|
806
839
|
* wrapper. The dispatch below checks whether the next token starts a
|
|
@@ -816,7 +849,7 @@ export class Parser {
|
|
|
816
849
|
statements.push(this.parseTopLevelStatement());
|
|
817
850
|
}
|
|
818
851
|
else {
|
|
819
|
-
// Bare field declaration -- the field-import case. Per spec §
|
|
852
|
+
// Bare field declaration -- the field-import case. Per spec §27.6
|
|
820
853
|
// the field appears without an entity wrapper.
|
|
821
854
|
statements.push(this.parseFieldDeclaration());
|
|
822
855
|
}
|
|
@@ -1284,7 +1317,7 @@ export class Parser {
|
|
|
1284
1317
|
}
|
|
1285
1318
|
throw new ParseError(`Expected type parameter, got ${t.kind}`, t.start);
|
|
1286
1319
|
}
|
|
1287
|
-
/* ----- Type declaration (§
|
|
1320
|
+
/* ----- Type declaration (§15) ----- */
|
|
1288
1321
|
parseTypeDecl() {
|
|
1289
1322
|
const start = this.peek().start;
|
|
1290
1323
|
this.advance(); // Type
|
|
@@ -1293,7 +1326,7 @@ export class Parser {
|
|
|
1293
1326
|
//
|
|
1294
1327
|
// { ... } v0.1 object form, no pre-body settings
|
|
1295
1328
|
// [ settings ] { ... } v0.1 object form, pre-body settings (permissive)
|
|
1296
|
-
// typeExpression v0.2 scalar form (spec §
|
|
1329
|
+
// typeExpression v0.2 scalar form (spec §15.7)
|
|
1297
1330
|
// typeExpression [ settings ] v0.2 scalar form with field-level settings
|
|
1298
1331
|
//
|
|
1299
1332
|
// Note that LBrace and LBracket are distinct from any start-of-type-expression
|
|
@@ -1572,7 +1605,7 @@ export class Parser {
|
|
|
1572
1605
|
};
|
|
1573
1606
|
}
|
|
1574
1607
|
/**
|
|
1575
|
-
* Parse a dotted path with the §
|
|
1608
|
+
* Parse a dotted path with the §20 segment vocabulary:
|
|
1576
1609
|
*
|
|
1577
1610
|
* IDENTIFIER -- a field segment
|
|
1578
1611
|
* .IDENTIFIER -- field
|
|
@@ -1746,6 +1779,67 @@ export class Parser {
|
|
|
1746
1779
|
span: this.spanFrom(start),
|
|
1747
1780
|
};
|
|
1748
1781
|
}
|
|
1782
|
+
/* ----- SupertypeGroup (spec §12, new in v0.5) ----- */
|
|
1783
|
+
/**
|
|
1784
|
+
* `SupertypeGroup <name> [supertype: X, ...] { Sub1 Sub2 [strategy: y] }`.
|
|
1785
|
+
* Members are separated like TableGroup members: newline, comma or
|
|
1786
|
+
* semicolon (spec §3.9). A name is required (§12.1); a writer exporting a
|
|
1787
|
+
* group that has none emits `undefinedGroup1`, `undefinedGroup2`, ... so
|
|
1788
|
+
* the parser never supplies one. Values are validated after parsing, by
|
|
1789
|
+
* `checkSupertypeGroups()`, so an unknown value gets a located diagnostic
|
|
1790
|
+
* rather than stopping the parse.
|
|
1791
|
+
*/
|
|
1792
|
+
parseSupertypeGroup() {
|
|
1793
|
+
const start = this.peek().start;
|
|
1794
|
+
this.advance(); // SupertypeGroup
|
|
1795
|
+
const nameTok = this.peek();
|
|
1796
|
+
if (nameTok.kind !== TokenKind.Identifier && nameTok.kind !== TokenKind.QuotedIdentifier) {
|
|
1797
|
+
throw new ParseError('Expected a name after SupertypeGroup. Every group has a name (spec §12.1); ' +
|
|
1798
|
+
'a group exported without one is written undefinedGroup1, undefinedGroup2, ...', nameTok.start);
|
|
1799
|
+
}
|
|
1800
|
+
const name = this.parseIdentLikeName('SupertypeGroup name');
|
|
1801
|
+
const settings = this.maybeSettingsBlock();
|
|
1802
|
+
this.expect(TokenKind.LBrace, "Expected '{' after SupertypeGroup name and settings");
|
|
1803
|
+
const members = [];
|
|
1804
|
+
while (!this.check(TokenKind.RBrace) && !this.check(TokenKind.EOF)) {
|
|
1805
|
+
const t = this.peek();
|
|
1806
|
+
if (t.kind === TokenKind.Identifier || t.kind === TokenKind.QuotedIdentifier) {
|
|
1807
|
+
const memberStart = t.start;
|
|
1808
|
+
this.advance();
|
|
1809
|
+
let n = t.kind === TokenKind.QuotedIdentifier ? (t.value ?? '') : t.text;
|
|
1810
|
+
while (this.check(TokenKind.Dot)) {
|
|
1811
|
+
this.advance();
|
|
1812
|
+
const next = this.peek();
|
|
1813
|
+
if (next.kind !== TokenKind.Identifier && next.kind !== TokenKind.QuotedIdentifier) {
|
|
1814
|
+
throw new ParseError('Expected identifier after dot in subtype path', next.start);
|
|
1815
|
+
}
|
|
1816
|
+
this.advance();
|
|
1817
|
+
n += `.${next.kind === TokenKind.QuotedIdentifier ? (next.value ?? '') : next.text}`;
|
|
1818
|
+
}
|
|
1819
|
+
const memberSettings = this.maybeSettingsBlock();
|
|
1820
|
+
members.push({
|
|
1821
|
+
kind: 'SupertypeGroupMember',
|
|
1822
|
+
name: n,
|
|
1823
|
+
settings: memberSettings,
|
|
1824
|
+
span: this.spanFrom(memberStart),
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
else if (t.kind === TokenKind.Semicolon || t.kind === TokenKind.Comma) {
|
|
1828
|
+
this.advance();
|
|
1829
|
+
}
|
|
1830
|
+
else {
|
|
1831
|
+
throw new ParseError(`Unexpected ${t.kind} in SupertypeGroup body; expected a subtype entity name`, t.start);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
this.expect(TokenKind.RBrace, "Expected '}' closing SupertypeGroup");
|
|
1835
|
+
return {
|
|
1836
|
+
kind: 'SupertypeGroupDeclaration',
|
|
1837
|
+
name,
|
|
1838
|
+
settings,
|
|
1839
|
+
members,
|
|
1840
|
+
span: this.spanFrom(start),
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1749
1843
|
/* ----- Indexes ----- */
|
|
1750
1844
|
parseIndexes() {
|
|
1751
1845
|
const start = this.peek().start;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supertype groups (spec §12, new in v0.5).
|
|
3
|
+
*
|
|
4
|
+
* Three things live here:
|
|
5
|
+
*
|
|
6
|
+
* - Setting normalization. `completeness`, `exclusivity`, `strategy` and
|
|
7
|
+
* `merge` accept a canonical value and a set of aliases (spec §12.2).
|
|
8
|
+
* The AST keeps what was written; `supertypeGroupSettings()` returns
|
|
9
|
+
* the canonical values.
|
|
10
|
+
* - Member resolution. The supertype and every subtype name an entity by
|
|
11
|
+
* path, bare (`Person`) or container-qualified (`crm.Person`), with the
|
|
12
|
+
* same reading as an entity-level `Ref` endpoint (spec §11.16.3).
|
|
13
|
+
* - The validation rules summarized in spec §12.8, run by `resolveNames()`
|
|
14
|
+
* on the flattened document so imported and cloned groups are checked
|
|
15
|
+
* like local ones.
|
|
16
|
+
*
|
|
17
|
+
* Nothing here computes the attributes or keys a subtype would hold in a
|
|
18
|
+
* physical model. A subtype declares its own attributes only; placing the
|
|
19
|
+
* supertype's attributes in stored structures is derivation output (spec
|
|
20
|
+
* §12.5, §12.7). Supertype chains are computed for the structural checks
|
|
21
|
+
* and for tools that display the hierarchy.
|
|
22
|
+
*/
|
|
23
|
+
import type { Diagnostic } from './name-resolver.ts';
|
|
24
|
+
import type { SupertypeGroupDeclaration, SupertypeGroupMember, XDbmlDocument } from './ast.ts';
|
|
25
|
+
export type Completeness = 'total' | 'partial';
|
|
26
|
+
export type Exclusivity = 'disjoint' | 'overlapping';
|
|
27
|
+
export type MaterializationStrategy = 'preserved_hierarchy' | 'roll_up' | 'roll_down';
|
|
28
|
+
export type MergeOption = 'flat' | 'nested';
|
|
29
|
+
/**
|
|
30
|
+
* Accepted spellings, keyed by lowercase spelling, mapped to the canonical
|
|
31
|
+
* value. Canonical values map to themselves.
|
|
32
|
+
*/
|
|
33
|
+
export declare const SUPERTYPE_GROUP_VALUES: {
|
|
34
|
+
readonly completeness: {
|
|
35
|
+
readonly total: "total";
|
|
36
|
+
readonly complete: "total";
|
|
37
|
+
readonly partial: "partial";
|
|
38
|
+
readonly incomplete: "partial";
|
|
39
|
+
};
|
|
40
|
+
readonly exclusivity: {
|
|
41
|
+
readonly disjoint: "disjoint";
|
|
42
|
+
readonly exclusive: "disjoint";
|
|
43
|
+
readonly overlapping: "overlapping";
|
|
44
|
+
readonly non_exclusive: "overlapping";
|
|
45
|
+
};
|
|
46
|
+
readonly strategy: {
|
|
47
|
+
readonly preserved_hierarchy: "preserved_hierarchy";
|
|
48
|
+
readonly class_table: "preserved_hierarchy";
|
|
49
|
+
readonly joined: "preserved_hierarchy";
|
|
50
|
+
readonly roll_up: "roll_up";
|
|
51
|
+
readonly single_table: "roll_up";
|
|
52
|
+
readonly roll_down: "roll_down";
|
|
53
|
+
readonly concrete_table: "roll_down";
|
|
54
|
+
readonly table_per_class: "roll_down";
|
|
55
|
+
};
|
|
56
|
+
readonly merge: {
|
|
57
|
+
readonly flat: "flat";
|
|
58
|
+
readonly flat_with_discriminator: "flat";
|
|
59
|
+
readonly nested: "nested";
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export type SupertypeGroupValueSetting = keyof typeof SUPERTYPE_GROUP_VALUES;
|
|
63
|
+
/** The canonical value for a spelling, or undefined when it is not recognized. */
|
|
64
|
+
export declare function canonicalSupertypeGroupValue(setting: SupertypeGroupValueSetting, raw: string): string | undefined;
|
|
65
|
+
/** A group's settings with canonical values. Unrecognized values are left out. */
|
|
66
|
+
export interface SupertypeGroupSettings {
|
|
67
|
+
supertype?: string;
|
|
68
|
+
completeness?: Completeness;
|
|
69
|
+
exclusivity?: Exclusivity;
|
|
70
|
+
strategy?: MaterializationStrategy;
|
|
71
|
+
merge?: MergeOption;
|
|
72
|
+
discriminator?: string;
|
|
73
|
+
note?: string;
|
|
74
|
+
}
|
|
75
|
+
export declare function supertypeGroupSettings(decl: SupertypeGroupDeclaration): SupertypeGroupSettings;
|
|
76
|
+
/** A subtype member's own `strategy` (spec §12.7.4), canonical, or undefined. */
|
|
77
|
+
export declare function subtypeStrategy(member: SupertypeGroupMember): MaterializationStrategy | undefined;
|
|
78
|
+
/** One group with its members resolved to entity ids where they resolve. */
|
|
79
|
+
export interface ResolvedSupertypeGroup {
|
|
80
|
+
declaration: SupertypeGroupDeclaration;
|
|
81
|
+
settings: SupertypeGroupSettings;
|
|
82
|
+
/** Entity id of the supertype, when it resolves. */
|
|
83
|
+
supertype?: string;
|
|
84
|
+
subtypes: Array<{
|
|
85
|
+
member: SupertypeGroupMember;
|
|
86
|
+
/** Entity id of the subtype, when it resolves. */
|
|
87
|
+
entity?: string;
|
|
88
|
+
/** The member's own strategy, canonical, when stated and recognized. */
|
|
89
|
+
strategy?: MaterializationStrategy;
|
|
90
|
+
}>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Every SupertypeGroup of a document with its members resolved. Pass the
|
|
94
|
+
* flattened document (see `flatten()`) so imported and cloned declarations
|
|
95
|
+
* are visible. Entity ids are container-qualified for entities declared in
|
|
96
|
+
* a Container, bare otherwise.
|
|
97
|
+
*/
|
|
98
|
+
export declare function resolveSupertypeGroups(doc: XDbmlDocument): ResolvedSupertypeGroup[];
|
|
99
|
+
/**
|
|
100
|
+
* For each entity that is a subtype, its chain of supertypes, nearest
|
|
101
|
+
* first: `Employee -> [Person, Party]`. When an entity is listed as a
|
|
102
|
+
* subtype in several groups (an error), the first group wins; a chain that
|
|
103
|
+
* loops stops before repeating an entity.
|
|
104
|
+
*/
|
|
105
|
+
export declare function supertypeChains(doc: XDbmlDocument): Map<string, string[]>;
|
|
106
|
+
/**
|
|
107
|
+
* The rules of spec §12.8. Run by `resolveNames()` on the flattened
|
|
108
|
+
* document; exported for callers that check groups on their own.
|
|
109
|
+
*/
|
|
110
|
+
export declare function checkSupertypeGroups(doc: XDbmlDocument): Diagnostic[];
|