@xdbml/parse 0.6.2 → 0.6.4
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/README.md +9 -0
- package/dist/ast.d.ts +31 -1
- package/dist/diagram-views.d.ts +48 -0
- package/dist/diagram-views.js +411 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/keywords.d.ts +1 -0
- package/dist/keywords.js +16 -0
- package/dist/module-resolver.d.ts +6 -0
- package/dist/module-resolver.js +40 -10
- package/dist/monarch.js +4 -1
- package/dist/name-resolver.d.ts +1 -1
- package/dist/name-resolver.js +39 -1
- package/dist/parser.d.ts +18 -1
- package/dist/parser.js +158 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,6 +48,15 @@ try {
|
|
|
48
48
|
|
|
49
49
|
The package also exports `tokenize`, `LexError`, `Parser`, `SymbolTable`, and the AST types.
|
|
50
50
|
|
|
51
|
+
For diagram views (spec §18), `diagramViews(doc)` lists the `DiagramView` declarations, and `diagramViewMembers(doc, name)` returns the entities, database views and sticky notes a diagram view contains, with the Containers, TableGroups and supertype groups a renderer frames or draws around them.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { parse, diagramViewMembers } from '@xdbml/parse';
|
|
55
|
+
|
|
56
|
+
const members = diagramViewMembers(parse(source), 'order_flow');
|
|
57
|
+
console.log(members?.entities); // ['sales.orders', 'billing.invoices', ...]
|
|
58
|
+
```
|
|
59
|
+
|
|
51
60
|
## Learn more
|
|
52
61
|
|
|
53
62
|
- [xdbml.org](https://xdbml.org), and the language [in 5 minutes](https://xdbml.org/learn/)
|
package/dist/ast.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface ExperimentalDeclaration {
|
|
|
38
38
|
features: string[];
|
|
39
39
|
span: Span;
|
|
40
40
|
}
|
|
41
|
-
export type TopLevelStatement = ProjectDeclaration | ContainerDeclaration | EntityDeclaration | TypeDeclaration | EdgeDeclaration | ViewDeclaration | EnumDeclaration | RefDeclaration | TablePartialDeclaration | TableGroupDeclaration | SupertypeGroupDeclaration | NoteDeclaration | TopLevelRecordsDeclaration | ModuleImportDirective;
|
|
41
|
+
export type TopLevelStatement = ProjectDeclaration | ContainerDeclaration | EntityDeclaration | TypeDeclaration | EdgeDeclaration | ViewDeclaration | EnumDeclaration | RefDeclaration | TablePartialDeclaration | TableGroupDeclaration | SupertypeGroupDeclaration | DiagramViewDeclaration | NoteDeclaration | TopLevelRecordsDeclaration | ModuleImportDirective;
|
|
42
42
|
export interface ProjectDeclaration {
|
|
43
43
|
kind: 'ProjectDeclaration';
|
|
44
44
|
name: string;
|
|
@@ -385,6 +385,36 @@ export interface SupertypeGroupMember {
|
|
|
385
385
|
settings: Setting[];
|
|
386
386
|
span: Span;
|
|
387
387
|
}
|
|
388
|
+
export type DiagramViewCategoryName = 'Tables' | 'Views' | 'Containers' | 'TableGroups' | 'SupertypeGroups' | 'Notes';
|
|
389
|
+
export interface DiagramViewDeclaration {
|
|
390
|
+
kind: 'DiagramViewDeclaration';
|
|
391
|
+
name: string;
|
|
392
|
+
/** Bracket settings: `note` and custom `x_` properties (spec §18.1). */
|
|
393
|
+
settings: Setting[];
|
|
394
|
+
/** `Note:` / `Note { }` elements of the body. `diagramViewNote()` gives the note. */
|
|
395
|
+
notes: NoteBlock[];
|
|
396
|
+
/** True when the whole body is `{ * }`: every element of every category. */
|
|
397
|
+
wildcardBody: boolean;
|
|
398
|
+
categories: DiagramViewCategory[];
|
|
399
|
+
span: Span;
|
|
400
|
+
}
|
|
401
|
+
export interface DiagramViewCategory {
|
|
402
|
+
kind: 'DiagramViewCategory';
|
|
403
|
+
/** Canonical category: `Schemas` reads as `Containers`. */
|
|
404
|
+
category: DiagramViewCategoryName;
|
|
405
|
+
/** The keyword as written, for round trips and the DBML-form check of §18.5. */
|
|
406
|
+
keyword: string;
|
|
407
|
+
/** True when the list holds `*`; names written beside it have no effect. */
|
|
408
|
+
wildcard: boolean;
|
|
409
|
+
items: DiagramViewItem[];
|
|
410
|
+
span: Span;
|
|
411
|
+
}
|
|
412
|
+
export interface DiagramViewItem {
|
|
413
|
+
kind: 'DiagramViewItem';
|
|
414
|
+
/** Name as written: `orders` or `sales.orders`. */
|
|
415
|
+
name: string;
|
|
416
|
+
span: Span;
|
|
417
|
+
}
|
|
388
418
|
export interface PartialInjection {
|
|
389
419
|
kind: 'PartialInjection';
|
|
390
420
|
/** Identifier after the `~`. */
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagram views (spec §18, defined in v0.6.3).
|
|
3
|
+
*
|
|
4
|
+
* A diagram view is a named subset of the model's diagram. Its body lists
|
|
5
|
+
* categories of elements; `diagramViewMembers()` turns those lists into the
|
|
6
|
+
* members of §18.2, and `checkDiagramViews()` reports the conditions of
|
|
7
|
+
* §18.6. Both take a document and flatten it, so elements imported from
|
|
8
|
+
* other files count like the ones declared in place (§18.3).
|
|
9
|
+
*
|
|
10
|
+
* Element ids follow the renderer and the supertype group resolver: an
|
|
11
|
+
* entity or database view inside a Container is `container.name`, one at
|
|
12
|
+
* the top level is its name as declared, which for the DBML form
|
|
13
|
+
* `Table core.users` is `core.users` in the implicit Container `core`.
|
|
14
|
+
*/
|
|
15
|
+
import type { DiagramViewDeclaration, XDbmlDocument } from './ast.ts';
|
|
16
|
+
import type { Diagnostic } from './name-resolver.ts';
|
|
17
|
+
/** The members of a diagram view (spec §18.2) and what a renderer draws with them (§18.4). */
|
|
18
|
+
export interface DiagramViewMembers {
|
|
19
|
+
/** The diagram view these members belong to. */
|
|
20
|
+
declaration: DiagramViewDeclaration;
|
|
21
|
+
/** Ids of the member entities, in declaration order. */
|
|
22
|
+
entities: string[];
|
|
23
|
+
/** Ids of the member database views, in declaration order. */
|
|
24
|
+
views: string[];
|
|
25
|
+
/** Names of the member sticky notes, in declaration order. */
|
|
26
|
+
notes: string[];
|
|
27
|
+
/** Containers holding at least one member: their frames surround those members only. */
|
|
28
|
+
containers: string[];
|
|
29
|
+
/** TableGroups with at least one member entity, named in the diagram view or not. */
|
|
30
|
+
tableGroups: string[];
|
|
31
|
+
/** Supertype groups whose supertype and at least one subtype are members (§12.9). */
|
|
32
|
+
supertypeGroups: string[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The note of a diagram view (spec §18.1): its first `Note` in the body,
|
|
36
|
+
* otherwise the `note` setting in its brackets, otherwise undefined.
|
|
37
|
+
*/
|
|
38
|
+
export declare function diagramViewNote(view: DiagramViewDeclaration): string | undefined;
|
|
39
|
+
/** Every DiagramView of a document, in declaration order, after module resolution. */
|
|
40
|
+
export declare function diagramViews(doc: XDbmlDocument): DiagramViewDeclaration[];
|
|
41
|
+
/**
|
|
42
|
+
* The members of the diagram view named `name`, or undefined when the
|
|
43
|
+
* document declares none of that name. With two of one name (an error),
|
|
44
|
+
* the first one counts.
|
|
45
|
+
*/
|
|
46
|
+
export declare function diagramViewMembers(doc: XDbmlDocument, name: string): DiagramViewMembers | undefined;
|
|
47
|
+
/** The conditions of spec §18.6 that a parse leaves to name resolution. */
|
|
48
|
+
export declare function checkDiagramViews(doc: XDbmlDocument): Diagnostic[];
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagram views (spec §18, defined in v0.6.3).
|
|
3
|
+
*
|
|
4
|
+
* A diagram view is a named subset of the model's diagram. Its body lists
|
|
5
|
+
* categories of elements; `diagramViewMembers()` turns those lists into the
|
|
6
|
+
* members of §18.2, and `checkDiagramViews()` reports the conditions of
|
|
7
|
+
* §18.6. Both take a document and flatten it, so elements imported from
|
|
8
|
+
* other files count like the ones declared in place (§18.3).
|
|
9
|
+
*
|
|
10
|
+
* Element ids follow the renderer and the supertype group resolver: an
|
|
11
|
+
* entity or database view inside a Container is `container.name`, one at
|
|
12
|
+
* the top level is its name as declared, which for the DBML form
|
|
13
|
+
* `Table core.users` is `core.users` in the implicit Container `core`.
|
|
14
|
+
*/
|
|
15
|
+
import { flatten } from "./module-resolver.js";
|
|
16
|
+
import { resolveSupertypeGroups } from "./supertypes.js";
|
|
17
|
+
/**
|
|
18
|
+
* The note of a diagram view (spec §18.1): its first `Note` in the body,
|
|
19
|
+
* otherwise the `note` setting in its brackets, otherwise undefined.
|
|
20
|
+
*/
|
|
21
|
+
export function diagramViewNote(view) {
|
|
22
|
+
if (view.notes.length > 0)
|
|
23
|
+
return view.notes[0].body;
|
|
24
|
+
const s = view.settings.find((x) => x.name === 'note');
|
|
25
|
+
const v = s?.value;
|
|
26
|
+
return v && v.kind === 'StringValue' ? v.value : undefined;
|
|
27
|
+
}
|
|
28
|
+
/** Every DiagramView of a document, in declaration order, after module resolution. */
|
|
29
|
+
export function diagramViews(doc) {
|
|
30
|
+
return flatten(doc).statements.filter((s) => s.kind === 'DiagramViewDeclaration');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The members of the diagram view named `name`, or undefined when the
|
|
34
|
+
* document declares none of that name. With two of one name (an error),
|
|
35
|
+
* the first one counts.
|
|
36
|
+
*/
|
|
37
|
+
export function diagramViewMembers(doc, name) {
|
|
38
|
+
const flat = flatten(doc);
|
|
39
|
+
const decl = flat.statements.find((s) => s.kind === 'DiagramViewDeclaration' && s.name === name);
|
|
40
|
+
if (!decl)
|
|
41
|
+
return undefined;
|
|
42
|
+
return computeMembers(decl, buildIndex(flat), flat);
|
|
43
|
+
}
|
|
44
|
+
const key = (kind, id) => `${kind}:${id}`;
|
|
45
|
+
function buildIndex(doc) {
|
|
46
|
+
const elements = [];
|
|
47
|
+
const byKindId = new Map();
|
|
48
|
+
const entitiesOf = new Map();
|
|
49
|
+
const viewsOf = new Map();
|
|
50
|
+
const tableGroupMembers = new Map();
|
|
51
|
+
const aliasCandidates = new Map();
|
|
52
|
+
const takenNames = new Set();
|
|
53
|
+
const add = (kind, id, name, container, topLevel) => {
|
|
54
|
+
if (byKindId.has(key(kind, id)))
|
|
55
|
+
return;
|
|
56
|
+
const el = { kind, id, name, container, topLevel };
|
|
57
|
+
elements.push(el);
|
|
58
|
+
byKindId.set(key(kind, id), el);
|
|
59
|
+
if (container && (kind === 'entity' || kind === 'view')) {
|
|
60
|
+
const map = kind === 'entity' ? entitiesOf : viewsOf;
|
|
61
|
+
const list = map.get(container) ?? [];
|
|
62
|
+
list.push(id);
|
|
63
|
+
map.set(container, list);
|
|
64
|
+
if (!byKindId.has(key('container', container))) {
|
|
65
|
+
add('container', container, container, undefined, true);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const addBoxed = (kind, decl, container) => {
|
|
70
|
+
let id;
|
|
71
|
+
let bare;
|
|
72
|
+
let owner = container;
|
|
73
|
+
if (container) {
|
|
74
|
+
id = `${container}.${decl.name}`;
|
|
75
|
+
bare = decl.name;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
id = decl.name;
|
|
79
|
+
const dot = decl.name.lastIndexOf('.');
|
|
80
|
+
bare = dot > 0 ? decl.name.slice(dot + 1) : decl.name;
|
|
81
|
+
owner = dot > 0 ? decl.name.slice(0, dot) : undefined;
|
|
82
|
+
}
|
|
83
|
+
add(kind, id, bare, owner, owner === undefined);
|
|
84
|
+
takenNames.add(bare);
|
|
85
|
+
if (kind === 'entity') {
|
|
86
|
+
const alias = decl.alias;
|
|
87
|
+
if (alias)
|
|
88
|
+
aliasCandidates.set(alias, aliasCandidates.has(alias) ? null : id);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
for (const stmt of doc.statements) {
|
|
92
|
+
switch (stmt.kind) {
|
|
93
|
+
case 'ContainerDeclaration': {
|
|
94
|
+
const c = stmt;
|
|
95
|
+
add('container', c.name, c.name, undefined, true);
|
|
96
|
+
takenNames.add(c.name);
|
|
97
|
+
for (const item of c.body) {
|
|
98
|
+
if (item.kind === 'EntityDeclaration')
|
|
99
|
+
addBoxed('entity', item, c.name);
|
|
100
|
+
else if (item.kind === 'ViewDeclaration')
|
|
101
|
+
addBoxed('view', item, c.name);
|
|
102
|
+
else if (item.kind === 'EdgeDeclaration')
|
|
103
|
+
add('edge', `${c.name}.${item.name}`, item.name, c.name, false);
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case 'EntityDeclaration':
|
|
108
|
+
addBoxed('entity', stmt);
|
|
109
|
+
break;
|
|
110
|
+
case 'ViewDeclaration':
|
|
111
|
+
addBoxed('view', stmt);
|
|
112
|
+
break;
|
|
113
|
+
case 'EdgeDeclaration':
|
|
114
|
+
add('edge', stmt.name, stmt.name, undefined, true);
|
|
115
|
+
break;
|
|
116
|
+
case 'TableGroupDeclaration':
|
|
117
|
+
add('tablegroup', stmt.name, stmt.name, undefined, true);
|
|
118
|
+
if (!tableGroupMembers.has(stmt.name))
|
|
119
|
+
tableGroupMembers.set(stmt.name, stmt.members);
|
|
120
|
+
break;
|
|
121
|
+
case 'SupertypeGroupDeclaration':
|
|
122
|
+
add('supertypegroup', stmt.name, stmt.name, undefined, true);
|
|
123
|
+
break;
|
|
124
|
+
case 'NoteDeclaration':
|
|
125
|
+
if (stmt.name)
|
|
126
|
+
add('note', stmt.name, stmt.name, undefined, true);
|
|
127
|
+
break;
|
|
128
|
+
default: break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// An alias that repeats the name of an entity or a Container, or that two
|
|
132
|
+
// entities declare, names nothing (§7.4).
|
|
133
|
+
const aliases = new Map();
|
|
134
|
+
for (const [alias, id] of aliasCandidates) {
|
|
135
|
+
if (id !== null && !takenNames.has(alias))
|
|
136
|
+
aliases.set(alias, id);
|
|
137
|
+
}
|
|
138
|
+
return { elements, byKindId, entitiesOf, viewsOf, aliases, tableGroupMembers };
|
|
139
|
+
}
|
|
140
|
+
/* --------------------------------------------------------- resolution */
|
|
141
|
+
const CATEGORY_KIND = {
|
|
142
|
+
Tables: 'entity',
|
|
143
|
+
Views: 'view',
|
|
144
|
+
Containers: 'container',
|
|
145
|
+
TableGroups: 'tablegroup',
|
|
146
|
+
SupertypeGroups: 'supertypegroup',
|
|
147
|
+
Notes: 'note',
|
|
148
|
+
};
|
|
149
|
+
const KIND_CATEGORY = {
|
|
150
|
+
entity: 'Tables',
|
|
151
|
+
view: 'Views',
|
|
152
|
+
container: 'Containers',
|
|
153
|
+
tablegroup: 'TableGroups',
|
|
154
|
+
supertypegroup: 'SupertypeGroups',
|
|
155
|
+
note: 'Notes',
|
|
156
|
+
};
|
|
157
|
+
const KIND_LABEL = {
|
|
158
|
+
entity: 'an entity',
|
|
159
|
+
view: 'a database view',
|
|
160
|
+
container: 'a Container',
|
|
161
|
+
tablegroup: 'a TableGroup',
|
|
162
|
+
supertypegroup: 'a supertype group',
|
|
163
|
+
note: 'a sticky note',
|
|
164
|
+
edge: 'an Edge',
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Resolve a name of one kind (spec §18.3). A qualified name names the
|
|
168
|
+
* element with that id. An unqualified name names the element declared
|
|
169
|
+
* outside any Container under that name, if any, and otherwise the one
|
|
170
|
+
* element of that name inside a Container. Under Tables, an alias names
|
|
171
|
+
* its entity.
|
|
172
|
+
*/
|
|
173
|
+
function resolveName(name, kind, index) {
|
|
174
|
+
const direct = index.byKindId.get(key(kind, name));
|
|
175
|
+
if (direct)
|
|
176
|
+
return { ok: true, id: direct.id };
|
|
177
|
+
if (!name.includes('.') && (kind === 'entity' || kind === 'view')) {
|
|
178
|
+
const top = index.elements.find((e) => e.kind === kind && e.topLevel && e.name === name);
|
|
179
|
+
if (top)
|
|
180
|
+
return { ok: true, id: top.id };
|
|
181
|
+
const inside = index.elements.filter((e) => e.kind === kind && e.name === name);
|
|
182
|
+
if (inside.length === 1)
|
|
183
|
+
return { ok: true, id: inside[0].id };
|
|
184
|
+
if (inside.length > 1)
|
|
185
|
+
return { ok: false, reason: 'ambiguous', candidates: inside.map((e) => e.id) };
|
|
186
|
+
}
|
|
187
|
+
if (kind === 'entity') {
|
|
188
|
+
const aliased = index.aliases.get(name);
|
|
189
|
+
if (aliased)
|
|
190
|
+
return { ok: true, id: aliased };
|
|
191
|
+
}
|
|
192
|
+
for (const other of ['entity', 'view', 'container', 'tablegroup', 'supertypegroup', 'note', 'edge']) {
|
|
193
|
+
if (other === kind)
|
|
194
|
+
continue;
|
|
195
|
+
if (resolveSimple(name, other, index))
|
|
196
|
+
return { ok: false, reason: 'other-kind', kind: other };
|
|
197
|
+
}
|
|
198
|
+
return { ok: false, reason: 'unresolved' };
|
|
199
|
+
}
|
|
200
|
+
function resolveSimple(name, kind, index) {
|
|
201
|
+
if (index.byKindId.has(key(kind, name)))
|
|
202
|
+
return true;
|
|
203
|
+
if (!name.includes('.'))
|
|
204
|
+
return index.elements.some((e) => e.kind === kind && e.name === name);
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
/** The categories of a diagram view, with a body-level `*` read as `*` in every category (§28.8). */
|
|
208
|
+
function effectiveCategories(decl) {
|
|
209
|
+
const out = new Map();
|
|
210
|
+
if (decl.wildcardBody) {
|
|
211
|
+
for (const name of Object.keys(CATEGORY_KIND)) {
|
|
212
|
+
out.set(name, { wildcard: true, items: [] });
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
// A category written twice is reported by checkDiagramViews(); its lists combine.
|
|
217
|
+
for (const c of decl.categories) {
|
|
218
|
+
const prev = out.get(c.category);
|
|
219
|
+
out.set(c.category, {
|
|
220
|
+
wildcard: (prev?.wildcard ?? false) || c.wildcard,
|
|
221
|
+
items: [...(prev?.items ?? []), ...c.items],
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
function computeMembers(decl, index, doc) {
|
|
227
|
+
const cats = effectiveCategories(decl);
|
|
228
|
+
const all = (kind) => index.elements.filter((e) => e.kind === kind).map((e) => e.id);
|
|
229
|
+
const named = (category) => {
|
|
230
|
+
const c = cats.get(category);
|
|
231
|
+
if (!c)
|
|
232
|
+
return new Set();
|
|
233
|
+
if (c.wildcard)
|
|
234
|
+
return new Set(all(CATEGORY_KIND[category]));
|
|
235
|
+
const ids = new Set();
|
|
236
|
+
for (const item of c.items) {
|
|
237
|
+
const r = resolveName(item.name, CATEGORY_KIND[category], index);
|
|
238
|
+
if (r.ok)
|
|
239
|
+
ids.add(r.id);
|
|
240
|
+
}
|
|
241
|
+
return ids;
|
|
242
|
+
};
|
|
243
|
+
const entities = named('Tables');
|
|
244
|
+
const views = named('Views');
|
|
245
|
+
// Rule 2: a listed Container contributes its entities and its database
|
|
246
|
+
// views, each list narrowed separately to the names under Tables or Views
|
|
247
|
+
// when they name at least one of the Container's own.
|
|
248
|
+
for (const container of named('Containers')) {
|
|
249
|
+
const ownEntities = index.entitiesOf.get(container) ?? [];
|
|
250
|
+
if (!ownEntities.some((id) => entities.has(id)))
|
|
251
|
+
ownEntities.forEach((id) => entities.add(id));
|
|
252
|
+
const ownViews = index.viewsOf.get(container) ?? [];
|
|
253
|
+
if (!ownViews.some((id) => views.has(id)))
|
|
254
|
+
ownViews.forEach((id) => views.add(id));
|
|
255
|
+
}
|
|
256
|
+
// Rule 3: each named TableGroup contributes all of its members.
|
|
257
|
+
for (const group of named('TableGroups')) {
|
|
258
|
+
for (const member of index.tableGroupMembers.get(group) ?? []) {
|
|
259
|
+
const r = resolveName(member, 'entity', index);
|
|
260
|
+
if (r.ok)
|
|
261
|
+
entities.add(r.id);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Rule 4: each named supertype group contributes its supertype and subtypes.
|
|
265
|
+
const groups = resolveSupertypeGroups(doc);
|
|
266
|
+
const namedGroups = named('SupertypeGroups');
|
|
267
|
+
for (const g of groups) {
|
|
268
|
+
if (!namedGroups.has(g.declaration.name))
|
|
269
|
+
continue;
|
|
270
|
+
if (g.supertype)
|
|
271
|
+
entities.add(g.supertype);
|
|
272
|
+
for (const s of g.subtypes)
|
|
273
|
+
if (s.entity)
|
|
274
|
+
entities.add(s.entity);
|
|
275
|
+
}
|
|
276
|
+
const notes = named('Notes');
|
|
277
|
+
const ordered = (kind, set) => all(kind).filter((id) => set.has(id));
|
|
278
|
+
const memberEntities = ordered('entity', entities);
|
|
279
|
+
const memberViews = ordered('view', views);
|
|
280
|
+
const boxed = new Set([...memberEntities, ...memberViews]);
|
|
281
|
+
const containers = all('container').filter((c) => [...(index.entitiesOf.get(c) ?? []), ...(index.viewsOf.get(c) ?? [])].some((id) => boxed.has(id)));
|
|
282
|
+
const tableGroups = all('tablegroup').filter((g) => (index.tableGroupMembers.get(g) ?? []).some((m) => {
|
|
283
|
+
const r = resolveName(m, 'entity', index);
|
|
284
|
+
return r.ok && entities.has(r.id);
|
|
285
|
+
}));
|
|
286
|
+
const supertypeGroups = groups
|
|
287
|
+
.filter((g) => g.supertype && entities.has(g.supertype) && g.subtypes.some((s) => s.entity && entities.has(s.entity)))
|
|
288
|
+
.map((g) => g.declaration.name);
|
|
289
|
+
return {
|
|
290
|
+
declaration: decl,
|
|
291
|
+
entities: memberEntities,
|
|
292
|
+
views: memberViews,
|
|
293
|
+
notes: ordered('note', notes),
|
|
294
|
+
containers,
|
|
295
|
+
tableGroups,
|
|
296
|
+
supertypeGroups: [...new Set(supertypeGroups)],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/* --------------------------------------------------------- diagnostics */
|
|
300
|
+
/** The conditions of spec §18.6 that a parse leaves to name resolution. */
|
|
301
|
+
export function checkDiagramViews(doc) {
|
|
302
|
+
const views = doc.statements.filter((s) => s.kind === 'DiagramViewDeclaration');
|
|
303
|
+
if (views.length === 0)
|
|
304
|
+
return [];
|
|
305
|
+
const index = buildIndex(doc);
|
|
306
|
+
const diagnostics = [];
|
|
307
|
+
const declared = !!doc.version;
|
|
308
|
+
const seen = new Set();
|
|
309
|
+
for (const view of views) {
|
|
310
|
+
if (seen.has(view.name)) {
|
|
311
|
+
diagnostics.push({
|
|
312
|
+
severity: 'error',
|
|
313
|
+
code: 'duplicate-diagram-view',
|
|
314
|
+
message: `A diagram view named '${view.name}' is already declared. Diagram view names are unique (spec §18.1).`,
|
|
315
|
+
span: view.span,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
seen.add(view.name);
|
|
319
|
+
// §18.1: the brackets take `note` and custom properties only.
|
|
320
|
+
for (const setting of view.settings) {
|
|
321
|
+
if (setting.name === 'note' || setting.name.startsWith('x_'))
|
|
322
|
+
continue;
|
|
323
|
+
diagnostics.push({
|
|
324
|
+
severity: 'error',
|
|
325
|
+
code: 'unknown-diagram-view-setting',
|
|
326
|
+
message: `Diagram view '${view.name}' writes the setting '${setting.nameSource}'. A diagram view takes ` +
|
|
327
|
+
'a note and custom x_ properties in its brackets, and no other setting (spec §18.1).',
|
|
328
|
+
span: setting.span,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
// §18.5: a note and the brackets are xDBML extensions.
|
|
332
|
+
if (!declared && (view.settings.length > 0 || view.notes.length > 0)) {
|
|
333
|
+
diagnostics.push({
|
|
334
|
+
severity: 'error',
|
|
335
|
+
code: 'construct-requires-version',
|
|
336
|
+
message: `Diagram view '${view.name}' carries a note or settings, an xDBML extension that needs a ` +
|
|
337
|
+
'version declaration such as xdbml: 0.6 at the top of the document (spec §18.5).',
|
|
338
|
+
span: (view.settings[0] ?? view.notes[0]).span,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
const written = new Map();
|
|
342
|
+
for (const c of view.categories) {
|
|
343
|
+
const first = written.get(c.category);
|
|
344
|
+
if (first) {
|
|
345
|
+
const same = first.keyword.toLowerCase() === c.keyword.toLowerCase();
|
|
346
|
+
diagnostics.push({
|
|
347
|
+
severity: declared ? 'error' : 'warning',
|
|
348
|
+
code: 'duplicate-diagram-view-category',
|
|
349
|
+
message: same
|
|
350
|
+
? `Diagram view '${view.name}' writes ${c.keyword} twice. Write each category once (spec §18.1).`
|
|
351
|
+
: `Diagram view '${view.name}' writes both ${first.keyword} and ${c.keyword}, which are one ` +
|
|
352
|
+
'category: Schemas is the DBML name of Containers (spec §18.1).',
|
|
353
|
+
span: c.span,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
written.set(c.category, c);
|
|
358
|
+
}
|
|
359
|
+
// §18.5: Containers, Views and SupertypeGroups are xDBML extensions.
|
|
360
|
+
if (!declared && c.keyword.toLowerCase() !== 'schemas'
|
|
361
|
+
&& (c.category === 'Containers' || c.category === 'Views' || c.category === 'SupertypeGroups')) {
|
|
362
|
+
diagnostics.push({
|
|
363
|
+
severity: 'error',
|
|
364
|
+
code: 'construct-requires-version',
|
|
365
|
+
message: `The ${c.keyword} category of a diagram view is an xDBML extension and needs a version ` +
|
|
366
|
+
'declaration such as xdbml: 0.6 at the top of the document' +
|
|
367
|
+
(c.category === 'Containers' ? ', or the DBML name Schemas' : '') + ' (spec §18.5).',
|
|
368
|
+
span: c.span,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
if (c.wildcard)
|
|
372
|
+
continue;
|
|
373
|
+
for (const item of c.items) {
|
|
374
|
+
const r = resolveName(item.name, CATEGORY_KIND[c.category], index);
|
|
375
|
+
if (r.ok)
|
|
376
|
+
continue;
|
|
377
|
+
if (r.reason === 'ambiguous') {
|
|
378
|
+
diagnostics.push({
|
|
379
|
+
severity: 'error',
|
|
380
|
+
code: 'ambiguous-diagram-view-name',
|
|
381
|
+
message: `'${item.name}' under ${c.keyword} matches ${r.candidates.map((x) => `'${x}'`).join(' and ')}, ` +
|
|
382
|
+
'and none is declared outside a Container: write the qualified name (spec §18.3).',
|
|
383
|
+
span: item.span,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
else if (r.reason === 'other-kind') {
|
|
387
|
+
const right = KIND_CATEGORY[r.kind];
|
|
388
|
+
diagnostics.push({
|
|
389
|
+
severity: 'error',
|
|
390
|
+
code: 'diagram-view-wrong-category',
|
|
391
|
+
message: `'${item.name}' under ${c.keyword} is ${KIND_LABEL[r.kind]}` +
|
|
392
|
+
(right
|
|
393
|
+
? `: list it under ${right} (spec §18.3).`
|
|
394
|
+
: ', which appears in a diagram view when both of its ends do and is never listed (spec §18.1, §18.4).'),
|
|
395
|
+
span: item.span,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
diagnostics.push({
|
|
400
|
+
severity: 'error',
|
|
401
|
+
code: 'unresolved-diagram-view-name',
|
|
402
|
+
message: `'${item.name}' under ${c.keyword} matches no ${KIND_LABEL[CATEGORY_KIND[c.category]].replace(/^an? /, '')} ` +
|
|
403
|
+
'in the project (spec §18.3).',
|
|
404
|
+
span: item.span,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return diagnostics;
|
|
411
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export { classifyModuleSource, isUrlKey, ModuleSourceError, } from './module-res
|
|
|
33
33
|
export type { ModuleSource } from './module-resolver.ts';
|
|
34
34
|
export { resolveNames, SymbolTable } from './name-resolver.ts';
|
|
35
35
|
export { checkViews, viewSourceQuery } from './views.ts';
|
|
36
|
+
export { checkDiagramViews, diagramViewMembers, diagramViewNote, diagramViews } from './diagram-views.ts';
|
|
37
|
+
export type { DiagramViewMembers } from './diagram-views.ts';
|
|
36
38
|
export { bodyConstraints, checkConstraints, entityConstraints, isRelationalTarget, keyPathString, markPrimaryKeyNotNull, primaryKey, } from './constraints.ts';
|
|
37
39
|
export type { CheckConstraint, Constraint, ConstraintSource, KeyConstraint } from './constraints.ts';
|
|
38
40
|
export { FOREIGN_MASTER_FLAG, checkRelationships, hasForeignMasterFlag, isForeignMaster, pathToString, refChildEndpoint, refParentEndpoint, relationshipType, versionAtLeast, CONSTRAINT_TYPES, V04_RELATIONSHIP_SETTINGS, constraintType, entityNames, isEntityLevelEndpoint, isUndirected, } from './relationships.ts';
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ export { flatten } from "./module-resolver.js";
|
|
|
31
31
|
export { classifyModuleSource, isUrlKey, ModuleSourceError, } from "./module-resolver.js";
|
|
32
32
|
export { resolveNames, SymbolTable } from "./name-resolver.js";
|
|
33
33
|
export { checkViews, viewSourceQuery } from "./views.js";
|
|
34
|
+
export { checkDiagramViews, diagramViewMembers, diagramViewNote, diagramViews } from "./diagram-views.js";
|
|
34
35
|
export { bodyConstraints, checkConstraints, entityConstraints, isRelationalTarget, keyPathString, markPrimaryKeyNotNull, primaryKey, } from "./constraints.js";
|
|
35
36
|
export { FOREIGN_MASTER_FLAG, checkRelationships, hasForeignMasterFlag, isForeignMaster, pathToString, refChildEndpoint, refParentEndpoint, relationshipType, versionAtLeast, CONSTRAINT_TYPES, V04_RELATIONSHIP_SETTINGS, constraintType, entityNames, isEntityLevelEndpoint, isUndirected, } from "./relationships.js";
|
|
36
37
|
export { SUPERTYPE_GROUP_VALUES, canonicalSupertypeGroupValue, checkSupertypeGroups, resolveSupertypeGroups, subtypeStrategy, supertypeChains, supertypeGroupSettings, } from "./supertypes.js";
|
package/dist/keywords.d.ts
CHANGED
|
@@ -39,6 +39,7 @@ export declare const POLYMORPHISM_KEYWORDS: readonly ["union", "oneof", "anyof",
|
|
|
39
39
|
export declare const SCALAR_TYPES: readonly ["tinyint", "smallint", "mediumint", "int", "integer", "bigint", "int32", "int64", "float", "double", "decimal", "dec", "numeric", "real", "bit", "bool", "boolean", "char", "varchar", "varchar2", "nvarchar", "nvarchar2", "nchar", "text", "mediumtext", "longtext", "string", "ntext", "binary", "varbinary", "blob", "mediumblob", "longblob", "tinyblob", "tinytext", "json", "jsonb", "variant", "xml", "date", "time", "datetime", "datetime2", "timestamp", "timestamptz", "year", "uuid", "inet6", "money", "smallmoney", "enum"];
|
|
40
40
|
export declare const BSON_TYPES: readonly ["objectid", "decimal128", "bindata", "minkey", "maxkey", "symbol", "regex", "long", "double"];
|
|
41
41
|
export declare const BLOCK_KEYWORDS: readonly ["indexes", "checks", "constraints", "records"];
|
|
42
|
+
export declare const DIAGRAM_VIEW_CATEGORIES: readonly ["tables", "views", "containers", "schemas", "tablegroups", "supertypegroups", "notes"];
|
|
42
43
|
export declare const TARGET_NATIVE_TYPES: readonly ["number", "binary_float", "binary_double", "clob", "nclob", "bfile", "raw", "rowid", "urowid", "xmltype", "sdo_geometry", "interval", "serial", "smallserial", "bigserial", "serial2", "serial4", "serial8", "int2", "int4", "int8", "float4", "float8", "bytea", "citext", "inet", "cidr", "macaddr", "macaddr8", "tsvector", "tsquery", "hstore", "jsonpath", "timetz", "varbit", "int4range", "int8range", "numrange", "tsrange", "tstzrange", "daterange", "uniqueidentifier", "datetimeoffset", "smalldatetime", "rowversion", "hierarchyid", "sql_variant", "timestamp_ntz", "timestamp_ltz", "timestamp_tz", "vector", "float64", "bignumeric", "bigdecimal", "bytes", "geometry", "geography", "ascii", "varint", "timeuuid", "uint32", "uint64", "sint32", "sint64", "fixed32", "fixed64", "sfixed32", "sfixed64", "uint8", "uint16", "int16", "float32"];
|
|
43
44
|
export declare const SETTING_FLAGS: readonly ["pk", "primary", "key", "unique", "null", "not", "required", "increment", "inactive", "foreign_master"];
|
|
44
45
|
export declare const SETTING_KEYS: readonly ["note", "default", "ref", "name", "color", "headercolor", "as", "check", "type", "target", "targets", "database_type", "source", "source_cardinality", "target_cardinality", "min_source", "max_source", "min_target", "max_target", "undirected", "source_role", "target_role", "source_verb", "target_verb", "constraint_type", "discriminator", "supertype", "completeness", "exclusivity", "strategy", "merge", "source_query", "materialized", "refresh_schedule", "refresh_on", "source_database", "storage_options", "pattern", "format", "minlength", "maxlength", "minimum", "maximum", "exclusiveminimum", "exclusivemaximum", "multipleof", "minitems", "maxitems", "uniqueitems", "minproperties", "maxproperties", "synonyms", "business_term", "granularity", "tags", "delete", "update", "indexes", "checks", "constraints", "replication", "location", "default_charset", "cloned_at"];
|
package/dist/keywords.js
CHANGED
|
@@ -170,6 +170,22 @@ export const BSON_TYPES = [
|
|
|
170
170
|
* and only when `{` follows, since a field may carry one of these names.
|
|
171
171
|
* ----------------------------------------------------------------------- */
|
|
172
172
|
export const BLOCK_KEYWORDS = ['indexes', 'checks', 'constraints', 'records'];
|
|
173
|
+
/* -------------------------------------------------------------------------
|
|
174
|
+
* DiagramView categories (spec §18.1, v0.6.3)
|
|
175
|
+
*
|
|
176
|
+
* The words that open a category inside a DiagramView body. `schemas` is
|
|
177
|
+
* the DBML name of `containers`. Both highlighters color them only when `{`
|
|
178
|
+
* follows, since an entity or a field may carry one of these names.
|
|
179
|
+
* ----------------------------------------------------------------------- */
|
|
180
|
+
export const DIAGRAM_VIEW_CATEGORIES = [
|
|
181
|
+
'tables',
|
|
182
|
+
'views',
|
|
183
|
+
'containers',
|
|
184
|
+
'schemas',
|
|
185
|
+
'tablegroups',
|
|
186
|
+
'supertypegroups',
|
|
187
|
+
'notes',
|
|
188
|
+
];
|
|
173
189
|
export const TARGET_NATIVE_TYPES = [
|
|
174
190
|
// Oracle
|
|
175
191
|
'number', 'binary_float', 'binary_double', 'clob', 'nclob', 'bfile',
|
|
@@ -107,6 +107,12 @@ export declare function isUrlKey(s: string): boolean;
|
|
|
107
107
|
* union; throws ModuleSourceError for disallowed forms. Pure and sync.
|
|
108
108
|
*/
|
|
109
109
|
export declare function classifyModuleSource(from: string): ModuleSource;
|
|
110
|
+
/**
|
|
111
|
+
* A local file path with Windows backslashes turned into forward slashes;
|
|
112
|
+
* a URL key is returned unchanged. The parser keys modules, and detects
|
|
113
|
+
* import cycles, on paths in this form.
|
|
114
|
+
*/
|
|
115
|
+
export declare function toForwardSlashes(p: string): string;
|
|
110
116
|
/**
|
|
111
117
|
* Default recursion depth limit for module resolution. Enough for any
|
|
112
118
|
* realistic module graph; small enough to bound stack usage on
|
package/dist/module-resolver.js
CHANGED
|
@@ -78,7 +78,18 @@ function flattenTopLevel(stmt, out) {
|
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
if (stmt.kind === 'ContainerDeclaration') {
|
|
81
|
-
|
|
81
|
+
// A `reuse *` inside a Container body can clone a DiagramView into it.
|
|
82
|
+
// A DiagramView is top-level only (spec §18.1), so it moves out to the
|
|
83
|
+
// top level, after its Container.
|
|
84
|
+
const flat = flattenContainer(stmt);
|
|
85
|
+
const hoisted = flat.body.filter((b) => b.kind === 'DiagramViewDeclaration');
|
|
86
|
+
if (hoisted.length === 0) {
|
|
87
|
+
out.push(flat);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
out.push({ ...flat, body: flat.body.filter((b) => !hoisted.includes(b)) });
|
|
91
|
+
for (const view of hoisted)
|
|
92
|
+
out.push(view);
|
|
82
93
|
return;
|
|
83
94
|
}
|
|
84
95
|
// All other top-level statement kinds pass through unchanged.
|
|
@@ -376,8 +387,13 @@ function findImportTarget(item, doc) {
|
|
|
376
387
|
}
|
|
377
388
|
return undefined;
|
|
378
389
|
}
|
|
379
|
-
//
|
|
380
|
-
|
|
390
|
+
// DiagramView (spec §18): top-level only, bare name. Its names resolve
|
|
391
|
+
// in the importing document, like the members of a TableGroup.
|
|
392
|
+
if (item.elementType === 'diagramview') {
|
|
393
|
+
return doc.statements.find((s) => s.kind === 'DiagramViewDeclaration' && s.name === path);
|
|
394
|
+
}
|
|
395
|
+
// View: top-level OR container-scoped.
|
|
396
|
+
if (item.elementType === 'view') {
|
|
381
397
|
if (segments.length === 1) {
|
|
382
398
|
return doc.statements.find((s) => s.kind === 'ViewDeclaration' && s.name === segments[0]);
|
|
383
399
|
}
|
|
@@ -614,6 +630,8 @@ function applyAlias(stmt, item) {
|
|
|
614
630
|
return { ...stmt, name: item.alias };
|
|
615
631
|
case 'SupertypeGroupDeclaration':
|
|
616
632
|
return { ...stmt, name: item.alias };
|
|
633
|
+
case 'DiagramViewDeclaration':
|
|
634
|
+
return { ...stmt, name: item.alias };
|
|
617
635
|
case 'TablePartialDeclaration':
|
|
618
636
|
return { ...stmt, name: item.alias };
|
|
619
637
|
case 'NoteDeclaration':
|
|
@@ -713,8 +731,11 @@ export function classifyModuleSource(from) {
|
|
|
713
731
|
* exactly as in v0.2.
|
|
714
732
|
*
|
|
715
733
|
* Uses pure string / WHATWG-URL manipulation (no node:path) so the same
|
|
716
|
-
* code runs in Node and the browser.
|
|
717
|
-
*
|
|
734
|
+
* code runs in Node and the browser. A local path may use backslashes, as
|
|
735
|
+
* a native Windows path does (`C:\\repo\\main.xdbml`): they read as forward
|
|
736
|
+
* slashes, and the key handed to `readFile` uses forward slashes, which
|
|
737
|
+
* Node's fs accepts on Windows. Until v0.6.4 a backslash path lost its
|
|
738
|
+
* directory, so './lib' was read from the working directory instead.
|
|
718
739
|
*/
|
|
719
740
|
function resolveModulePath(fromClause, importerPath) {
|
|
720
741
|
const source = classifyModuleSource(fromClause);
|
|
@@ -731,21 +752,30 @@ function resolveModulePath(fromClause, importerPath) {
|
|
|
731
752
|
// also what we want for the cycle-detection / de-duplication key.
|
|
732
753
|
return new URL(rel, importerPath).href;
|
|
733
754
|
}
|
|
734
|
-
// Local relative source (
|
|
735
|
-
|
|
755
|
+
// Local relative source (v0.2 behavior, with Windows separators read as
|
|
756
|
+
// forward slashes from v0.6.4).
|
|
757
|
+
let withExt = toForwardSlashes(source.from);
|
|
736
758
|
if (!withExt.endsWith('.xdbml')) {
|
|
737
759
|
withExt = `${withExt}.xdbml`;
|
|
738
760
|
}
|
|
739
|
-
// Absolute path: return as-is.
|
|
740
|
-
if (withExt.startsWith('/'))
|
|
761
|
+
// Absolute path, POSIX or with a Windows drive letter: return as-is.
|
|
762
|
+
if (withExt.startsWith('/') || /^[A-Za-z]:\//.test(withExt))
|
|
741
763
|
return withExt;
|
|
742
764
|
// No importer context: return as-is (resolver handles it).
|
|
743
765
|
if (!importerPath)
|
|
744
766
|
return withExt;
|
|
745
767
|
// Resolve relative to importer's directory.
|
|
746
|
-
const importerDir = posixDirname(importerPath);
|
|
768
|
+
const importerDir = posixDirname(toForwardSlashes(importerPath));
|
|
747
769
|
return posixJoin(importerDir, withExt);
|
|
748
770
|
}
|
|
771
|
+
/**
|
|
772
|
+
* A local file path with Windows backslashes turned into forward slashes;
|
|
773
|
+
* a URL key is returned unchanged. The parser keys modules, and detects
|
|
774
|
+
* import cycles, on paths in this form.
|
|
775
|
+
*/
|
|
776
|
+
export function toForwardSlashes(p) {
|
|
777
|
+
return isUrlKey(p) ? p : p.replace(/\\/g, '/');
|
|
778
|
+
}
|
|
749
779
|
function posixDirname(p) {
|
|
750
780
|
const idx = p.lastIndexOf('/');
|
|
751
781
|
if (idx < 0)
|
package/dist/monarch.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* dependency on consumers that just want to parse. The Monaco types
|
|
19
19
|
* are structurally compatible -- the consumer casts at the boundary.
|
|
20
20
|
*/
|
|
21
|
-
import { CONTAINER_KEYWORDS, ENTITY_KEYWORDS, DECLARATION_KEYWORDS, STRUCTURAL_TYPE_KEYWORDS, POLYMORPHISM_KEYWORDS, SCALAR_TYPES, BSON_TYPES, TARGET_NATIVE_TYPES, SETTING_FLAGS, SETTING_KEYS, GRANULARITY_VALUES, } from "./keywords.js";
|
|
21
|
+
import { CONTAINER_KEYWORDS, ENTITY_KEYWORDS, DECLARATION_KEYWORDS, STRUCTURAL_TYPE_KEYWORDS, POLYMORPHISM_KEYWORDS, SCALAR_TYPES, BSON_TYPES, TARGET_NATIVE_TYPES, SETTING_FLAGS, SETTING_KEYS, GRANULARITY_VALUES, DIAGRAM_VIEW_CATEGORIES, } from "./keywords.js";
|
|
22
22
|
/* -------------------------------------------------------------------------
|
|
23
23
|
* Language configuration
|
|
24
24
|
* ----------------------------------------------------------------------- */
|
|
@@ -160,6 +160,9 @@ export const xdbmlMonarchTokensProvider = {
|
|
|
160
160
|
// or at the top level before a name and '{'. Anywhere else it names a
|
|
161
161
|
// field, so it takes the identifier color like any other field name.
|
|
162
162
|
[/note(?![\w$])(?!\s*[:{])(?!\s+(?:[A-Za-z_][\w$]*|"[^"]*")\s*\{)/, 'identifier'],
|
|
163
|
+
// Spec §18.1 (v0.6.3): a DiagramView category keyword opens its list
|
|
164
|
+
// only before '{'. Elsewhere the same word names an entity or a field.
|
|
165
|
+
[new RegExp(`(?:${DIAGRAM_VIEW_CATEGORIES.join('|')})(?![\\w$])(?=\\s*\\{)`), 'keyword.declaration'],
|
|
163
166
|
// Identifiers and keyword recognition.
|
|
164
167
|
// The parser is the authority on keyword vs identifier disambiguation;
|
|
165
168
|
// Monarch does coarse highlighting based on lowercase comparison.
|
package/dist/name-resolver.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ export interface SymbolEntry {
|
|
|
67
67
|
* Stable diagnostic code. Tooling can match on these to filter or style
|
|
68
68
|
* messages without parsing the human-readable text.
|
|
69
69
|
*/
|
|
70
|
-
export type DiagnosticCode = 'duplicate-declaration' | 'name-collision' | 'container-target-missing' | 'container-target-not-in-project' | 'unresolved-type' | 'possible-type-typo' | 'unresolved-key-field' | 'unresolved-index-field' | 'key-path-crosses-collection' | 'invalid-key-flags' | 'duplicate-primary-key' | 'duplicate-unique-key' | 'null-in-primary-key' | 'duplicate-constraints-block' | 'ref-target-not-key' | 'unresolved-entity' | 'unresolved-field' | 'unresolved-partial' | 'unresolved-tablegroup-member' | 'unresolved-records-entity' | 'unresolved-records-column' | 'empty-import' | 'invalid-nested-path' | 'foreign-master-composite' | 'foreign-master-duplicate-child' | 'foreign-master-without-ref' | 'construct-requires-version' | 'ambiguous-ref-endpoint' | 'invalid-constraint-type' | 'constraint-type-on-foreign-master' | 'invalid-undirected' | 'entity-level-many-to-many' | 'missing-supertype' | 'unresolved-supertype-group-member' | 'invalid-supertype-group-value' | 'duplicate-subtype' | 'supertype-is-subtype' | 'subtype-in-multiple-groups' | 'supertype-cycle' | 'supertype-attribute-redeclared' | 'discriminator-on-overlapping-group' | 'merge-without-roll-up' | 'empty-supertype-group' | 'source-query-in-settings' | 'duplicate-source-query';
|
|
70
|
+
export type DiagnosticCode = 'duplicate-declaration' | 'name-collision' | 'container-target-missing' | 'container-target-not-in-project' | 'unresolved-type' | 'possible-type-typo' | 'unresolved-key-field' | 'unresolved-index-field' | 'key-path-crosses-collection' | 'invalid-key-flags' | 'duplicate-primary-key' | 'duplicate-unique-key' | 'null-in-primary-key' | 'duplicate-constraints-block' | 'ref-target-not-key' | 'unresolved-entity' | 'unresolved-field' | 'unresolved-partial' | 'unresolved-tablegroup-member' | 'unresolved-records-entity' | 'unresolved-records-column' | 'empty-import' | 'invalid-nested-path' | 'foreign-master-composite' | 'foreign-master-duplicate-child' | 'foreign-master-without-ref' | 'construct-requires-version' | 'ambiguous-ref-endpoint' | 'invalid-constraint-type' | 'constraint-type-on-foreign-master' | 'invalid-undirected' | 'entity-level-many-to-many' | 'missing-supertype' | 'unresolved-supertype-group-member' | 'invalid-supertype-group-value' | 'duplicate-subtype' | 'supertype-is-subtype' | 'subtype-in-multiple-groups' | 'supertype-cycle' | 'supertype-attribute-redeclared' | 'discriminator-on-overlapping-group' | 'merge-without-roll-up' | 'empty-supertype-group' | 'source-query-in-settings' | 'duplicate-source-query' | 'duplicate-diagram-view' | 'duplicate-diagram-view-category' | 'unresolved-diagram-view-name' | 'ambiguous-diagram-view-name' | 'diagram-view-wrong-category' | 'unknown-diagram-view-setting' | 'invalid-tuple-positions' | 'named-type-shadows-builtin';
|
|
71
71
|
/**
|
|
72
72
|
* A single resolution diagnostic. Severity is currently always `error`,
|
|
73
73
|
* but the field is included to leave room for future warnings (e.g.,
|
package/dist/name-resolver.js
CHANGED
|
@@ -45,6 +45,7 @@ import { checkSupertypeGroups } from "./supertypes.js";
|
|
|
45
45
|
import { checkConstraints, checkTargets } from "./constraints.js";
|
|
46
46
|
import { versionAtLeast } from "./relationships.js";
|
|
47
47
|
import { checkViews } from "./views.js";
|
|
48
|
+
import { checkDiagramViews } from "./diagram-views.js";
|
|
48
49
|
/**
|
|
49
50
|
* Read-only handle on the collected symbol table.
|
|
50
51
|
*
|
|
@@ -236,6 +237,30 @@ export function resolveNames(doc) {
|
|
|
236
237
|
diagnostics.push(...checkTargets(flat));
|
|
237
238
|
// Pass 6: view source queries (spec §14.7).
|
|
238
239
|
diagnostics.push(...checkViews(flat));
|
|
240
|
+
// Pass 7: diagram views (spec §18.6).
|
|
241
|
+
diagnostics.push(...checkDiagramViews(flat));
|
|
242
|
+
// Pass 8: Types under the name of a built-in type (spec §15.2).
|
|
243
|
+
for (const t of flat.statements) {
|
|
244
|
+
if (t.kind !== 'TypeDeclaration' || !BUILTIN_TYPES.has(t.name.toLowerCase()))
|
|
245
|
+
continue;
|
|
246
|
+
{
|
|
247
|
+
diagnostics.push({
|
|
248
|
+
severity: 'error',
|
|
249
|
+
code: 'named-type-shadows-builtin',
|
|
250
|
+
message: `Type '${t.name}' takes the name of a built-in type. Built-in types take precedence, so a field ` +
|
|
251
|
+
`typed ${t.name.toLowerCase()} keeps the built-in type: rename the Type (spec §15.2).`,
|
|
252
|
+
span: t.span,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// The parser checked neither rule before v0.6.4, so a document declaring
|
|
257
|
+
// a version earlier than 0.6 draws a warning and stays valid (§8.6, §15.2).
|
|
258
|
+
if (!versionAtLeast(flat, '0.6')) {
|
|
259
|
+
for (const d of diagnostics) {
|
|
260
|
+
if (d.code === 'invalid-tuple-positions' || d.code === 'named-type-shadows-builtin')
|
|
261
|
+
d.severity = 'warning';
|
|
262
|
+
}
|
|
263
|
+
}
|
|
239
264
|
return { diagnostics, symbols };
|
|
240
265
|
}
|
|
241
266
|
/* -------------------------------------------------------------------------
|
|
@@ -566,11 +591,24 @@ function resolveTypeExpression(expr, symbols, diagnostics) {
|
|
|
566
591
|
case 'SetType':
|
|
567
592
|
resolveTypeExpression(expr.elementType, symbols, diagnostics);
|
|
568
593
|
return;
|
|
569
|
-
case 'TupleType':
|
|
594
|
+
case 'TupleType': {
|
|
595
|
+
// Spec §8.6: positions form a contiguous range from 0, each once.
|
|
596
|
+
const positions = expr.elements.map((e) => e.position);
|
|
597
|
+
const sorted = [...positions].sort((a, b) => a - b);
|
|
598
|
+
if (sorted.some((p, i) => p !== i)) {
|
|
599
|
+
diagnostics.push({
|
|
600
|
+
severity: 'error',
|
|
601
|
+
code: 'invalid-tuple-positions',
|
|
602
|
+
message: `Tuple positions ${positions.map((p) => `[${p}]`).join(', ')} do not run from [0] to ` +
|
|
603
|
+
`[${positions.length - 1}], each once: a tuple numbers its elements from 0 without a gap (spec §8.6).`,
|
|
604
|
+
span: expr.span,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
570
607
|
for (const elem of expr.elements) {
|
|
571
608
|
resolveTypeExpression(elem.type, symbols, diagnostics);
|
|
572
609
|
}
|
|
573
610
|
return;
|
|
611
|
+
}
|
|
574
612
|
case 'JsonType':
|
|
575
613
|
// No nested type expressions.
|
|
576
614
|
return;
|
package/dist/parser.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export declare class ParseError extends Error {
|
|
|
26
26
|
* declaring a later version is refused (spec 4.1) rather than parsed with
|
|
27
27
|
* semantics it does not have.
|
|
28
28
|
*/
|
|
29
|
-
export declare const SUPPORTED_XDBML_VERSION = "0.6.
|
|
29
|
+
export declare const SUPPORTED_XDBML_VERSION = "0.6.4";
|
|
30
30
|
/** Compare dotted version strings numerically: -1, 0 or 1. */
|
|
31
31
|
export declare function compareVersions(a: string, b: string): number;
|
|
32
32
|
export declare class Parser {
|
|
@@ -40,6 +40,8 @@ export declare class Parser {
|
|
|
40
40
|
* 1-arg form).
|
|
41
41
|
*/
|
|
42
42
|
private options;
|
|
43
|
+
/** The version the document declares, once read; undefined without a declaration. */
|
|
44
|
+
private declaredVersion;
|
|
43
45
|
/**
|
|
44
46
|
* The set of file paths currently being parsed in the resolution chain.
|
|
45
47
|
* Used for cycle detection: when resolving a directive whose `from` path
|
|
@@ -345,6 +347,21 @@ export declare class Parser {
|
|
|
345
347
|
* rather than stopping the parse.
|
|
346
348
|
*/
|
|
347
349
|
private parseSupertypeGroup;
|
|
350
|
+
/**
|
|
351
|
+
* `DiagramView <name> { <Category> { <names> | * } ... }`, or
|
|
352
|
+
* `DiagramView <name> { * }` for every element of every category.
|
|
353
|
+
* Category keywords are case-insensitive; `Schemas` reads as `Containers`.
|
|
354
|
+
* Lists follow spec §3.9: whitespace between names, with commas and
|
|
355
|
+
* semicolons allowed and meaningless. Names stay unresolved here; the
|
|
356
|
+
* checks of §18.6 run after parsing, in `checkDiagramViews()`, so a bad
|
|
357
|
+
* name gets a located diagnostic and the rest of the diagram still draws.
|
|
358
|
+
* Two conditions stop the parse because they leave no category to read:
|
|
359
|
+
* an unknown category, `Edges` included, and a body-level `*` with a
|
|
360
|
+
* category beside it. A note may sit in the brackets or in the body
|
|
361
|
+
* (spec §18.1, v0.6.3).
|
|
362
|
+
*/
|
|
363
|
+
private parseDiagramView;
|
|
364
|
+
private parseDiagramViewCategory;
|
|
348
365
|
private parseIndexes;
|
|
349
366
|
private parseIndexEntry;
|
|
350
367
|
private parseIndexComponent;
|
package/dist/parser.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { markPrimaryKeyNotNull } from "./constraints.js";
|
|
13
13
|
import { TokenKind, tokenize, } from "./lexer.js";
|
|
14
|
-
import { resolveImport, classifyModuleSource, ModuleSourceError } from "./module-resolver.js";
|
|
14
|
+
import { resolveImport, classifyModuleSource, ModuleSourceError, toForwardSlashes } from "./module-resolver.js";
|
|
15
15
|
export class ParseError extends Error {
|
|
16
16
|
position;
|
|
17
17
|
/**
|
|
@@ -32,7 +32,7 @@ export class ParseError extends Error {
|
|
|
32
32
|
* declaring a later version is refused (spec 4.1) rather than parsed with
|
|
33
33
|
* semantics it does not have.
|
|
34
34
|
*/
|
|
35
|
-
export const SUPPORTED_XDBML_VERSION = '0.6.
|
|
35
|
+
export const SUPPORTED_XDBML_VERSION = '0.6.4';
|
|
36
36
|
/** Compare dotted version strings numerically: -1, 0 or 1. */
|
|
37
37
|
export function compareVersions(a, b) {
|
|
38
38
|
const pa = a.split('.').map((n) => Number(n) || 0);
|
|
@@ -74,6 +74,19 @@ const IMPORT_ELEMENT_TYPES = new Set([
|
|
|
74
74
|
'type', 'edge', 'view', 'diagramview',
|
|
75
75
|
'field', 'supertypegroup',
|
|
76
76
|
]);
|
|
77
|
+
/**
|
|
78
|
+
* DiagramView categories (spec §18.1), by lowercased keyword. `Schemas`, the
|
|
79
|
+
* DBML name, reads as `Containers`, after the `Schema` keyword (§6.2).
|
|
80
|
+
*/
|
|
81
|
+
const DIAGRAM_VIEW_CATEGORIES = {
|
|
82
|
+
tables: 'Tables',
|
|
83
|
+
views: 'Views',
|
|
84
|
+
containers: 'Containers',
|
|
85
|
+
schemas: 'Containers',
|
|
86
|
+
tablegroups: 'TableGroups',
|
|
87
|
+
supertypegroups: 'SupertypeGroups',
|
|
88
|
+
notes: 'Notes',
|
|
89
|
+
};
|
|
77
90
|
const STRUCTURAL_TYPE_KEYWORDS = new Set([
|
|
78
91
|
'object', 'struct', 'record', 'array', 'list', 'map', 'dict', 'dictionary',
|
|
79
92
|
'set', 'union', 'oneof', 'anyof', 'allof', 'json', 'jsonb', 'variant',
|
|
@@ -121,6 +134,8 @@ export class Parser {
|
|
|
121
134
|
* 1-arg form).
|
|
122
135
|
*/
|
|
123
136
|
options;
|
|
137
|
+
/** The version the document declares, once read; undefined without a declaration. */
|
|
138
|
+
declaredVersion;
|
|
124
139
|
/**
|
|
125
140
|
* The set of file paths currently being parsed in the resolution chain.
|
|
126
141
|
* Used for cycle detection: when resolving a directive whose `from` path
|
|
@@ -261,6 +276,7 @@ export class Parser {
|
|
|
261
276
|
this.advance(); // .
|
|
262
277
|
version += `.${this.advance().text}`;
|
|
263
278
|
}
|
|
279
|
+
this.declaredVersion = version;
|
|
264
280
|
if (compareVersions(version, SUPPORTED_XDBML_VERSION) > 0) {
|
|
265
281
|
throw new ParseError(`This document declares 'xdbml: ${version}', which is newer than the ` +
|
|
266
282
|
`latest version this parser supports (${SUPPORTED_XDBML_VERSION}). ` +
|
|
@@ -320,6 +336,8 @@ export class Parser {
|
|
|
320
336
|
return this.parseTableGroup();
|
|
321
337
|
if (k === 'supertypegroup')
|
|
322
338
|
return this.parseSupertypeGroup();
|
|
339
|
+
if (k === 'diagramview')
|
|
340
|
+
return this.parseDiagramView();
|
|
323
341
|
if (k === 'note')
|
|
324
342
|
return this.parseNoteDeclaration();
|
|
325
343
|
if (k === 'records')
|
|
@@ -471,6 +489,10 @@ export class Parser {
|
|
|
471
489
|
else if (k === 'use' || k === 'reuse') {
|
|
472
490
|
body.push(this.parseModuleDirective('container-body'));
|
|
473
491
|
}
|
|
492
|
+
else if (k === 'diagramview') {
|
|
493
|
+
throw new ParseError(`A DiagramView is a top-level declaration and never appears in a Container (spec §18.1). ` +
|
|
494
|
+
`Move it out of Container '${name}'.`, t.start);
|
|
495
|
+
}
|
|
474
496
|
else {
|
|
475
497
|
// Unknown line; tolerate as no-op rather than fail the whole parse.
|
|
476
498
|
throw new ParseError(`Unexpected token in Container body: ${t.kind} ${JSON.stringify(t.text)}`, t.start);
|
|
@@ -714,6 +736,12 @@ export class Parser {
|
|
|
714
736
|
const start = this.peek().start;
|
|
715
737
|
const modeTok = this.advance(); // 'use' or 'reuse'
|
|
716
738
|
const mode = modeTok.text.toLowerCase();
|
|
739
|
+
// Appendix D, item 3: the module system belongs to the v0.2 feature set,
|
|
740
|
+
// so a document declaring xdbml: 0.1 cannot use it (checked from v0.6.4).
|
|
741
|
+
if (this.declaredVersion && compareVersions(this.declaredVersion, '0.2') < 0) {
|
|
742
|
+
throw new ParseError(`The module system (use and reuse) needs xdbml: 0.2 or later; this document declares ` +
|
|
743
|
+
`xdbml: ${this.declaredVersion} (spec §27, Appendix D).`, modeTok.start);
|
|
744
|
+
}
|
|
717
745
|
// Import spec: '*' or '{ ... }'
|
|
718
746
|
let spec;
|
|
719
747
|
if (this.check(TokenKind.Star)) {
|
|
@@ -723,16 +751,15 @@ export class Parser {
|
|
|
723
751
|
else if (this.check(TokenKind.LBrace)) {
|
|
724
752
|
this.advance();
|
|
725
753
|
const items = [];
|
|
726
|
-
//
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
else {
|
|
754
|
+
// A list body (spec §3.9, §27.2, v0.6.4): items one per line, or
|
|
755
|
+
// separated by commas, with commas and semicolons optional anywhere
|
|
756
|
+
// between items. Until v0.6.4 the parser required a comma between
|
|
757
|
+
// two items, against the multi-line form §27.2 shows.
|
|
758
|
+
for (;;) {
|
|
759
|
+
this.skipListSeparators();
|
|
760
|
+
if (this.check(TokenKind.RBrace) || this.check(TokenKind.EOF))
|
|
734
761
|
break;
|
|
735
|
-
|
|
762
|
+
items.push(this.parseImportItem(context));
|
|
736
763
|
}
|
|
737
764
|
this.expect(TokenKind.RBrace, "Expected '}' closing import item list");
|
|
738
765
|
if (items.length === 0) {
|
|
@@ -860,6 +887,11 @@ export class Parser {
|
|
|
860
887
|
throw new ParseError(`Unknown import element type '${elemTok.text}'. ` +
|
|
861
888
|
`Expected one of: ${Array.from(IMPORT_ELEMENT_TYPES).join(', ')}.`, elemTok.start);
|
|
862
889
|
}
|
|
890
|
+
if (elementType === 'diagramview' && context !== 'file-scope') {
|
|
891
|
+
// Spec §18.1, §27.5: a DiagramView is a top-level declaration, so its
|
|
892
|
+
// import goes at file scope, where the declaration itself would.
|
|
893
|
+
throw new ParseError('A DiagramView is a top-level declaration: import it at file scope, not inside a Container body (spec §18.1, §27.5).', elemTok.start);
|
|
894
|
+
}
|
|
863
895
|
if (elementType === 'field' && context !== 'file-scope') {
|
|
864
896
|
// Spec §27.8: field imports must appear at file scope. Inside a
|
|
865
897
|
// Container body, the field's eventual placement (as a Named Type)
|
|
@@ -1951,6 +1983,120 @@ export class Parser {
|
|
|
1951
1983
|
span: this.spanFrom(start),
|
|
1952
1984
|
};
|
|
1953
1985
|
}
|
|
1986
|
+
/* ----- DiagramView (spec §18, defined in v0.6.3) ----- */
|
|
1987
|
+
/**
|
|
1988
|
+
* `DiagramView <name> { <Category> { <names> | * } ... }`, or
|
|
1989
|
+
* `DiagramView <name> { * }` for every element of every category.
|
|
1990
|
+
* Category keywords are case-insensitive; `Schemas` reads as `Containers`.
|
|
1991
|
+
* Lists follow spec §3.9: whitespace between names, with commas and
|
|
1992
|
+
* semicolons allowed and meaningless. Names stay unresolved here; the
|
|
1993
|
+
* checks of §18.6 run after parsing, in `checkDiagramViews()`, so a bad
|
|
1994
|
+
* name gets a located diagnostic and the rest of the diagram still draws.
|
|
1995
|
+
* Two conditions stop the parse because they leave no category to read:
|
|
1996
|
+
* an unknown category, `Edges` included, and a body-level `*` with a
|
|
1997
|
+
* category beside it. A note may sit in the brackets or in the body
|
|
1998
|
+
* (spec §18.1, v0.6.3).
|
|
1999
|
+
*/
|
|
2000
|
+
parseDiagramView() {
|
|
2001
|
+
const start = this.peek().start;
|
|
2002
|
+
this.advance(); // DiagramView
|
|
2003
|
+
const nameTok = this.peek();
|
|
2004
|
+
if (nameTok.kind !== TokenKind.Identifier && nameTok.kind !== TokenKind.QuotedIdentifier) {
|
|
2005
|
+
throw new ParseError('Expected a name after DiagramView (spec §18.1)', nameTok.start);
|
|
2006
|
+
}
|
|
2007
|
+
const name = this.parseIdentLikeName('DiagramView name');
|
|
2008
|
+
// Spec §18.1 (v0.6.3): `note` and custom properties in the brackets.
|
|
2009
|
+
// Other settings are reported by checkDiagramViews(), not here.
|
|
2010
|
+
const settings = this.maybeSettingsBlock();
|
|
2011
|
+
this.expect(TokenKind.LBrace, `Expected '{' after DiagramView ${name}`);
|
|
2012
|
+
const categories = [];
|
|
2013
|
+
const notes = [];
|
|
2014
|
+
let wildcardBody = false;
|
|
2015
|
+
for (;;) {
|
|
2016
|
+
this.skipListSeparators();
|
|
2017
|
+
const t = this.peek();
|
|
2018
|
+
if (t.kind === TokenKind.RBrace || t.kind === TokenKind.EOF)
|
|
2019
|
+
break;
|
|
2020
|
+
if (this.atNoteElement()) {
|
|
2021
|
+
notes.push(this.parseNoteBlockOrSetting());
|
|
2022
|
+
continue;
|
|
2023
|
+
}
|
|
2024
|
+
if (t.kind === TokenKind.Star) {
|
|
2025
|
+
if (categories.length > 0 || wildcardBody) {
|
|
2026
|
+
throw new ParseError(`A '*' body lists every element of every category, so no category goes beside it in DiagramView ${name} (spec §18.1)`, t.start);
|
|
2027
|
+
}
|
|
2028
|
+
this.advance();
|
|
2029
|
+
wildcardBody = true;
|
|
2030
|
+
continue;
|
|
2031
|
+
}
|
|
2032
|
+
const k = kw(t);
|
|
2033
|
+
const category = k ? DIAGRAM_VIEW_CATEGORIES[k] : undefined;
|
|
2034
|
+
if (!category) {
|
|
2035
|
+
if (k === 'edges') {
|
|
2036
|
+
throw new ParseError('Edges is not a DiagramView category: a Ref or an Edge appears in a diagram view when both ' +
|
|
2037
|
+
'of its ends do, so there is nothing to list (spec §18.1, §18.4)', t.start);
|
|
2038
|
+
}
|
|
2039
|
+
throw new ParseError(`Unknown DiagramView category ${JSON.stringify(t.text)}. Expected Tables, Views, Containers ` +
|
|
2040
|
+
'(or Schemas), TableGroups, SupertypeGroups or Notes, each followed by { names }, or a Note (spec §18.1)', t.start);
|
|
2041
|
+
}
|
|
2042
|
+
if (wildcardBody) {
|
|
2043
|
+
throw new ParseError(`A '*' body lists every element of every category, so no category goes beside it in DiagramView ${name} (spec §18.1)`, t.start);
|
|
2044
|
+
}
|
|
2045
|
+
categories.push(this.parseDiagramViewCategory(category));
|
|
2046
|
+
}
|
|
2047
|
+
this.expect(TokenKind.RBrace, "Expected '}' closing DiagramView");
|
|
2048
|
+
return {
|
|
2049
|
+
kind: 'DiagramViewDeclaration',
|
|
2050
|
+
name,
|
|
2051
|
+
settings,
|
|
2052
|
+
notes,
|
|
2053
|
+
wildcardBody,
|
|
2054
|
+
categories,
|
|
2055
|
+
span: this.spanFrom(start),
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
parseDiagramViewCategory(category) {
|
|
2059
|
+
const start = this.peek().start;
|
|
2060
|
+
const keywordTok = this.advance();
|
|
2061
|
+
this.expect(TokenKind.LBrace, `Expected '{' after ${keywordTok.text} in a DiagramView`);
|
|
2062
|
+
const items = [];
|
|
2063
|
+
let wildcard = false;
|
|
2064
|
+
for (;;) {
|
|
2065
|
+
this.skipListSeparators();
|
|
2066
|
+
const t = this.peek();
|
|
2067
|
+
if (t.kind === TokenKind.RBrace || t.kind === TokenKind.EOF)
|
|
2068
|
+
break;
|
|
2069
|
+
if (t.kind === TokenKind.Star) {
|
|
2070
|
+
this.advance();
|
|
2071
|
+
wildcard = true;
|
|
2072
|
+
continue;
|
|
2073
|
+
}
|
|
2074
|
+
if (t.kind !== TokenKind.Identifier && t.kind !== TokenKind.QuotedIdentifier) {
|
|
2075
|
+
throw new ParseError(`Expected a name or '*' in ${keywordTok.text} { }, got ${t.kind} ${JSON.stringify(t.text)}`, t.start);
|
|
2076
|
+
}
|
|
2077
|
+
this.advance();
|
|
2078
|
+
let n = t.kind === TokenKind.QuotedIdentifier ? (t.value ?? '') : t.text;
|
|
2079
|
+
while (this.check(TokenKind.Dot)) {
|
|
2080
|
+
this.advance();
|
|
2081
|
+
const next = this.peek();
|
|
2082
|
+
if (next.kind !== TokenKind.Identifier && next.kind !== TokenKind.QuotedIdentifier) {
|
|
2083
|
+
throw new ParseError(`Expected identifier after '.' in ${keywordTok.text} { }`, next.start);
|
|
2084
|
+
}
|
|
2085
|
+
this.advance();
|
|
2086
|
+
n += `.${next.kind === TokenKind.QuotedIdentifier ? (next.value ?? '') : next.text}`;
|
|
2087
|
+
}
|
|
2088
|
+
items.push({ kind: 'DiagramViewItem', name: n, span: this.spanFrom(t.start) });
|
|
2089
|
+
}
|
|
2090
|
+
this.expect(TokenKind.RBrace, `Expected '}' closing ${keywordTok.text}`);
|
|
2091
|
+
return {
|
|
2092
|
+
kind: 'DiagramViewCategory',
|
|
2093
|
+
category,
|
|
2094
|
+
keyword: keywordTok.text,
|
|
2095
|
+
wildcard,
|
|
2096
|
+
items,
|
|
2097
|
+
span: this.spanFrom(start),
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
1954
2100
|
/* ----- Indexes ----- */
|
|
1955
2101
|
parseIndexes() {
|
|
1956
2102
|
const start = this.peek().start;
|
|
@@ -2386,7 +2532,7 @@ export function parse(source, options = {}) {
|
|
|
2386
2532
|
// the outer level too). If no filePath is provided, the stack is empty.
|
|
2387
2533
|
const initialStack = new Set();
|
|
2388
2534
|
if (options.filePath)
|
|
2389
|
-
initialStack.add(options.filePath);
|
|
2535
|
+
initialStack.add(toForwardSlashes(options.filePath));
|
|
2390
2536
|
const doc = new Parser(tokens, options, initialStack, 0).parseDocument();
|
|
2391
2537
|
applyEntityAliases(doc);
|
|
2392
2538
|
return doc;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xdbml/parse",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "Parser for xDBML (eXtended Database Markup Language), a strict superset of DBML 3.13.6: tokenizer, parser, module resolver, and name resolver.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Hackolade",
|