@xdbml/parse 0.5.0 → 0.5.1

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 CHANGED
@@ -81,7 +81,11 @@ export interface FieldDeclaration {
81
81
  export type TypeExpression = ScalarType | ObjectType | ArrayType | TupleType | MapType | SetType | UnionType | OneOfType | AnyOfType | AllOfType | JsonType | NamedTypeReference;
82
82
  export interface ScalarType {
83
83
  kind: 'ScalarType';
84
- /** The base name: `int`, `varchar`, `decimal`, `objectId`, `Decimal128`, etc. */
84
+ /**
85
+ * The base name: `int`, `varchar`, `decimal`, `objectId`, `Decimal128`,
86
+ * etc. May be qualified, `core.job_status`, when it names an Enum
87
+ * declared in a container (spec §16).
88
+ */
85
89
  name: string;
86
90
  /** `(p, s)` parameters, e.g. for `decimal(19, 4)`. Numbers preserved as strings to keep round-trip fidelity. */
87
91
  params?: string[];
@@ -229,6 +233,11 @@ export interface EnumDeclaration {
229
233
  kind: 'EnumDeclaration';
230
234
  /** Source casing of `enum` or `Enum`; both are valid. */
231
235
  keywordCasing: string;
236
+ /**
237
+ * May be `container.enum` form when declared schema-qualified at the
238
+ * top level (`enum core.job_status`); the resolver files it under that
239
+ * container, like an Enum declared inside the Container block.
240
+ */
232
241
  name: string;
233
242
  values: EnumValue[];
234
243
  span: Span;
@@ -38,6 +38,7 @@ export declare const STRUCTURAL_TYPE_KEYWORDS: readonly ["object", "struct", "ar
38
38
  export declare const POLYMORPHISM_KEYWORDS: readonly ["union", "oneof", "anyof", "allof"];
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
+ 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"];
41
42
  export declare const SETTING_FLAGS: readonly ["pk", "primary", "key", "unique", "null", "not", "required", "increment", "inactive", "foreign_master"];
42
43
  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", "replication", "location", "default_charset", "cloned_at"];
43
44
  export declare const GRANULARITY_VALUES: readonly ["year", "quarter", "month", "week", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond"];
package/dist/keywords.js CHANGED
@@ -98,9 +98,15 @@ export const POLYMORPHISM_KEYWORDS = [
98
98
  /* -------------------------------------------------------------------------
99
99
  * Scalar and BSON types
100
100
  *
101
- * The parser accepts any identifier as a scalar type (open vocabulary),
102
- * so these lists drive color, not validation. Themes color them under
103
- * `storage.type` (TextMate) or `type` (Monarch).
101
+ * The parser accepts any identifier as a scalar type (open vocabulary,
102
+ * spec §1.2 principle 4), so these lists never reject a type name.
103
+ * They serve two purposes: themes color them under `storage.type`
104
+ * (TextMate) or `type` (Monarch), and the name resolver treats them as
105
+ * built-in names, which a Named Type cannot shadow (spec §15.2).
106
+ * Adding a name here therefore changes resolution as well as color: a
107
+ * document declaring `Type <that name>` would stop resolving to its own
108
+ * Type. Target-native names that only need color go in
109
+ * TARGET_NATIVE_TYPES below.
104
110
  * ----------------------------------------------------------------------- */
105
111
  export const SCALAR_TYPES = [
106
112
  // Integers
@@ -139,6 +145,51 @@ export const BSON_TYPES = [
139
145
  'long',
140
146
  'double',
141
147
  ];
148
+ /* -------------------------------------------------------------------------
149
+ * Target-native types (highlighting only)
150
+ *
151
+ * Common type names of specific targets, colored like SCALAR_TYPES so
152
+ * that a physical model reads the same in the editor as a generic one.
153
+ * Unlike SCALAR_TYPES, these are not built-in names: the resolver
154
+ * passes them through like any other target-native name, and a Named
155
+ * Type or Enum may be declared under one of them.
156
+ *
157
+ * Coloring is position-independent (Monarch and TextMate both match
158
+ * the bare word), so a field named after a listed type is colored as a
159
+ * type too. Generic words that often name fields -- `point`, `line`,
160
+ * `box`, `path`, `circle`, `image`, `counter`, `duration`, `name` --
161
+ * are left out for that reason. The list is a convenience, not a
162
+ * vocabulary: a name missing from it is still a valid type.
163
+ * ----------------------------------------------------------------------- */
164
+ export const TARGET_NATIVE_TYPES = [
165
+ // Oracle
166
+ 'number', 'binary_float', 'binary_double', 'clob', 'nclob', 'bfile',
167
+ 'raw', 'rowid', 'urowid', 'xmltype', 'sdo_geometry',
168
+ // Oracle, PostgreSQL, BigQuery, Databricks
169
+ 'interval',
170
+ // PostgreSQL
171
+ 'serial', 'smallserial', 'bigserial', 'serial2', 'serial4', 'serial8',
172
+ 'int2', 'int4', 'int8', 'float4', 'float8', 'bytea', 'citext',
173
+ 'inet', 'cidr', 'macaddr', 'macaddr8', 'tsvector', 'tsquery', 'hstore',
174
+ 'jsonpath', 'timetz', 'varbit',
175
+ 'int4range', 'int8range', 'numrange', 'tsrange', 'tstzrange', 'daterange',
176
+ // SQL Server
177
+ 'uniqueidentifier', 'datetimeoffset', 'smalldatetime', 'rowversion',
178
+ 'hierarchyid', 'sql_variant',
179
+ // Snowflake (plus `number`, `interval`, `geography`, `geometry`)
180
+ 'timestamp_ntz', 'timestamp_ltz', 'timestamp_tz', 'vector',
181
+ // BigQuery, Avro, Protobuf
182
+ 'float64', 'bignumeric', 'bigdecimal', 'bytes',
183
+ // Spatial, across several targets
184
+ 'geometry', 'geography',
185
+ // Cassandra
186
+ 'ascii', 'varint', 'timeuuid',
187
+ // Protobuf
188
+ 'uint32', 'uint64', 'sint32', 'sint64',
189
+ 'fixed32', 'fixed64', 'sfixed32', 'sfixed64',
190
+ // ClickHouse
191
+ 'uint8', 'uint16', 'int16', 'float32',
192
+ ];
142
193
  /* -------------------------------------------------------------------------
143
194
  * Settings vocabulary
144
195
  *
@@ -390,8 +390,18 @@ function findImportTarget(item, doc) {
390
390
  }
391
391
  return undefined;
392
392
  }
393
- // Enum: top-level OR container-scoped.
393
+ // Enum: top-level OR container-scoped. A top-level Enum declared with
394
+ // a qualified name (`enum core.job_status`, the DBML form) matches its
395
+ // full dotted path and imports under its bare name, like the
396
+ // container-scoped form: the source container does not survive into
397
+ // the importing file (spec §27.16).
394
398
  if (item.elementType === 'enum') {
399
+ if (segments.length > 1) {
400
+ const qualifiedTopLevel = doc.statements.find((s) => s.kind === 'EnumDeclaration' && s.name === path);
401
+ if (qualifiedTopLevel && qualifiedTopLevel.kind === 'EnumDeclaration') {
402
+ return { ...qualifiedTopLevel, name: segments[segments.length - 1] };
403
+ }
404
+ }
395
405
  if (segments.length === 1) {
396
406
  return doc.statements.find((s) => s.kind === 'EnumDeclaration' && s.name === segments[0]);
397
407
  }
package/dist/monarch.d.ts CHANGED
@@ -53,6 +53,7 @@ export interface XDbmlMonarchLanguage {
53
53
  polymorphismKeywords: string[];
54
54
  scalarTypes: string[];
55
55
  bsonTypes: string[];
56
+ nativeTypes: string[];
56
57
  settingFlags: string[];
57
58
  settingKeys: string[];
58
59
  granularityValues: string[];
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, 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, } from "./keywords.js";
22
22
  /* -------------------------------------------------------------------------
23
23
  * Language configuration
24
24
  * ----------------------------------------------------------------------- */
@@ -102,6 +102,9 @@ export const xdbmlMonarchTokensProvider = {
102
102
  scalarTypes: [...SCALAR_TYPES],
103
103
  // BSON / document-store types
104
104
  bsonTypes: [...BSON_TYPES],
105
+ // Target-native types (Oracle `number`, PostgreSQL `serial`, ...):
106
+ // colored like scalar types, but not built-ins -- see keywords.ts.
107
+ nativeTypes: [...TARGET_NATIVE_TYPES],
105
108
  // Bare-flag settings: `pk`, `unique`, `not null`, etc.
106
109
  // `not null` and `primary key` are two words but tokenized one at a time
107
110
  // here -- the highlighter colors each as `keyword.setting`.
@@ -165,6 +168,7 @@ export const xdbmlMonarchTokensProvider = {
165
168
  '@decls': 'keyword.declaration',
166
169
  '@scalarTypes': 'type',
167
170
  '@bsonTypes': 'type.bson',
171
+ '@nativeTypes': 'type',
168
172
  '@settingFlags': 'keyword.setting',
169
173
  '@settingKeys': 'keyword.setting',
170
174
  '@granularityValues': 'keyword.value',
@@ -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' | 'unresolved-type' | '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';
70
+ export type DiagnosticCode = 'duplicate-declaration' | 'unresolved-type' | 'possible-type-typo' | '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';
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.,
@@ -96,11 +96,18 @@ export class SymbolTable {
96
96
  * Built-in type recognition
97
97
  *
98
98
  * Field type expressions can name a builtin scalar (`int`, `varchar`),
99
- * a BSON type (`objectId`), or a user-defined Named Type (`Email`).
100
- * The parser doesn't distinguish at parse time -- they all land as
99
+ * a BSON type (`objectId`), a user-defined Named Type (`Email`) or Enum,
100
+ * or any other target-native type (`number`, `clob`, `serial`). The
101
+ * parser doesn't distinguish at parse time -- they all land as
101
102
  * ScalarType nodes (or NamedTypeReference in some contexts). The
102
- * resolver uses these sets to decide whether to look up a name in the
103
- * symbol table.
103
+ * resolver uses these sets only to skip the symbol-table lookup for
104
+ * names that can never be a Named Type reference.
105
+ *
106
+ * The sets are not a type vocabulary. Scalar type names pass through
107
+ * as written (spec §1.2, principle 4), so a name that is neither a
108
+ * builtin nor a declared Type or Enum is a target-native type, not an
109
+ * error. See `nearMissTypeName()` for the one diagnostic such a name
110
+ * can raise.
104
111
  *
105
112
  * Matching is case-insensitive: `Int`, `int`, `INT` all map to the same
106
113
  * builtin per spec §3.8.
@@ -112,6 +119,87 @@ const BUILTIN_TYPES = new Set([
112
119
  function isBuiltinType(name) {
113
120
  return BUILTIN_TYPES.has(name.toLowerCase());
114
121
  }
122
+ /**
123
+ * The declared Types and Enums a bare type name can refer to. More than
124
+ * one entry means the name is declared in several containers (Enums can
125
+ * be container-scoped); the name still refers to a declaration, so it
126
+ * is neither a target-native type nor a near miss.
127
+ */
128
+ function typeOrEnumDeclarations(name, symbols) {
129
+ const qualified = symbols.lookup(name);
130
+ if (qualified && (qualified.kind === 'type' || qualified.kind === 'enum'))
131
+ return [qualified];
132
+ return symbols.lookupAllBare(name).filter((e) => e.kind === 'type' || e.kind === 'enum');
133
+ }
134
+ /**
135
+ * Near-miss detection for target-native type names.
136
+ *
137
+ * A type name that is neither a builtin nor a declared Type or Enum is
138
+ * accepted as a target-native type. The one case worth flagging is a
139
+ * name that differs only slightly from a declared Type or Enum -- most
140
+ * likely a misspelling (`Adress` for `Address`, `email` for `Email`).
141
+ * Returns the declared name to suggest, or undefined when nothing is
142
+ * close enough.
143
+ *
144
+ * Comparison is case-insensitive, so a difference of case alone counts
145
+ * as a near miss (identifiers are case-sensitive, spec §3.8). The edit
146
+ * distance allowed grows with the length of the name: none beyond case
147
+ * for three characters or fewer, one edit up to seven characters, two
148
+ * edits from eight. An adjacent transposition counts as one edit.
149
+ */
150
+ function nearMissTypeName(name, symbols) {
151
+ const lower = name.toLowerCase();
152
+ const maxDistance = lower.length <= 3 ? 0 : lower.length <= 7 ? 1 : 2;
153
+ // A qualified name (`core.job_stauts`) compares against qualified
154
+ // declarations, a bare name against bare ones.
155
+ const qualified = name.includes('.');
156
+ let best;
157
+ for (const entry of symbols.entries()) {
158
+ if (entry.kind !== 'type' && entry.kind !== 'enum')
159
+ continue;
160
+ const declared = qualified ? entry.qualifiedName : entry.name;
161
+ const candidate = declared.toLowerCase();
162
+ if (Math.abs(candidate.length - lower.length) > maxDistance)
163
+ continue;
164
+ const distance = editDistance(lower, candidate, maxDistance);
165
+ if (distance <= maxDistance && (!best || distance < best.distance)) {
166
+ best = { name: declared, distance };
167
+ }
168
+ }
169
+ return best?.name;
170
+ }
171
+ /**
172
+ * Optimal string alignment distance (Levenshtein plus adjacent
173
+ * transposition). Returns `limit + 1` as soon as every alignment of a
174
+ * row exceeds `limit`, since callers only compare against the limit.
175
+ */
176
+ function editDistance(a, b, limit) {
177
+ const rows = a.length + 1;
178
+ const cols = b.length + 1;
179
+ const d = [];
180
+ for (let i = 0; i < rows; i++) {
181
+ d.push(new Array(cols).fill(0));
182
+ d[i][0] = i;
183
+ }
184
+ for (let j = 0; j < cols; j++)
185
+ d[0][j] = j;
186
+ for (let i = 1; i < rows; i++) {
187
+ let rowMin = Infinity;
188
+ for (let j = 1; j < cols; j++) {
189
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
190
+ let v = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
191
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
192
+ v = Math.min(v, d[i - 2][j - 2] + 1);
193
+ }
194
+ d[i][j] = v;
195
+ if (v < rowMin)
196
+ rowMin = v;
197
+ }
198
+ if (rowMin > limit)
199
+ return limit + 1;
200
+ }
201
+ return d[rows - 1][cols - 1];
202
+ }
115
203
  /* -------------------------------------------------------------------------
116
204
  * Main entry point
117
205
  * ----------------------------------------------------------------------- */
@@ -159,9 +247,20 @@ function addTopLevelDeclaration(stmt, entries, diagnostics, seen) {
159
247
  case 'TypeDeclaration':
160
248
  addEntry(stmt.name, undefined, 'type', stmt, stmt.span, entries, diagnostics, seen);
161
249
  return;
162
- case 'EnumDeclaration':
163
- addEntry(stmt.name, undefined, 'enum', stmt, stmt.span, entries, diagnostics, seen);
250
+ case 'EnumDeclaration': {
251
+ // `enum core.job_status { ... }` (the DBML form) files the Enum
252
+ // under container `core`, exactly as `Container core { Enum
253
+ // job_status { ... } }` does: same qualified name, same bare name,
254
+ // and declaring both is a duplicate (spec §16).
255
+ const dot = stmt.name.lastIndexOf('.');
256
+ if (dot > 0) {
257
+ addEntry(stmt.name.slice(dot + 1), stmt.name.slice(0, dot), 'enum', stmt, stmt.span, entries, diagnostics, seen);
258
+ }
259
+ else {
260
+ addEntry(stmt.name, undefined, 'enum', stmt, stmt.span, entries, diagnostics, seen);
261
+ }
164
262
  return;
263
+ }
165
264
  case 'EdgeDeclaration':
166
265
  addEntry(stmt.name, undefined, 'edge', stmt, stmt.span, entries, diagnostics, seen);
167
266
  return;
@@ -363,16 +462,18 @@ function resolveFieldDeclaration(field, symbols, diagnostics) {
363
462
  function resolveTypeExpression(expr, symbols, diagnostics) {
364
463
  switch (expr.kind) {
365
464
  case 'ScalarType':
366
- // A ScalarType is either a builtin or a reference to a Named Type
367
- // (the parser doesn't distinguish at parse time). Look up only
368
- // when the name isn't a builtin.
369
- if (!isBuiltinType(expr.name)) {
370
- const found = symbols.lookup(expr.name) ?? symbols.lookupBare(expr.name);
371
- if (!found || found.kind !== 'type') {
465
+ // A ScalarType is a builtin, a reference to a declared Type or
466
+ // Enum, or a target-native type passed through as written (spec
467
+ // §1.2, principle 4) -- the parser doesn't distinguish at parse
468
+ // time. A target-native name is valid; the only diagnostic is a
469
+ // warning when it is a near miss of a declared Type or Enum.
470
+ if (!isBuiltinType(expr.name) && typeOrEnumDeclarations(expr.name, symbols).length === 0) {
471
+ const suggestion = nearMissTypeName(expr.name, symbols);
472
+ if (suggestion) {
372
473
  diagnostics.push({
373
- severity: 'error',
374
- code: 'unresolved-type',
375
- message: `Type '${expr.name}' is not a built-in type or declared Named Type.`,
474
+ severity: 'warning',
475
+ code: 'possible-type-typo',
476
+ message: `Type '${expr.name}' is not declared; did you mean '${suggestion}'? Otherwise it is kept as a target-native type.`,
376
477
  span: expr.span,
377
478
  });
378
479
  }
@@ -824,9 +925,18 @@ function dereferenceNamedType(type, symbols, depth) {
824
925
  return type;
825
926
  }
826
927
  const sym = symbols.lookup(name) ?? symbols.lookupBare(name);
928
+ if (type.kind === 'ScalarType' && (!sym || sym.kind !== 'type')) {
929
+ // An Enum or a target-native type: an opaque scalar, so a path that
930
+ // navigates into it gets the walker's shape diagnostic. A name
931
+ // declared as a Type or Enum in several containers stays unresolved.
932
+ if (typeOrEnumDeclarations(name, symbols).length > 1)
933
+ return undefined;
934
+ return type;
935
+ }
827
936
  if (!sym || sym.kind !== 'type' || sym.declaration.kind !== 'TypeDeclaration') {
828
- // Unresolved -- the field-type pass will diagnose this. Return
829
- // undefined so the walker bails without emitting a duplicate error.
937
+ // Unresolved Named Type reference -- the field-type pass diagnoses
938
+ // this. Return undefined so the walker bails without emitting a
939
+ // duplicate error.
830
940
  return undefined;
831
941
  }
832
942
  const td = sym.declaration;
package/dist/parser.js CHANGED
@@ -1289,7 +1289,14 @@ export class Parser {
1289
1289
  throw new ParseError(`Expected type name, got ${t.kind} ${JSON.stringify(t.text)}`, t.start);
1290
1290
  }
1291
1291
  this.advance();
1292
- const name = t.kind === TokenKind.QuotedIdentifier ? (t.value ?? '') : t.text;
1292
+ let name = t.kind === TokenKind.QuotedIdentifier ? (t.value ?? '') : t.text;
1293
+ // A qualified type name, `core.job_status`, names an Enum declared in
1294
+ // a container (spec §16), or a target-native type such as
1295
+ // `public.geometry` that passes through as written.
1296
+ while (this.check(TokenKind.Dot)) {
1297
+ this.advance();
1298
+ name += `.${this.parseIdentLikeName('type name after dot')}`;
1299
+ }
1293
1300
  let params;
1294
1301
  if (this.check(TokenKind.LParen)) {
1295
1302
  this.advance();
@@ -1455,7 +1462,13 @@ export class Parser {
1455
1462
  parseEnum() {
1456
1463
  const start = this.peek().start;
1457
1464
  const kwTok = this.advance(); // enum
1458
- const name = this.parseIdentLikeName('enum name');
1465
+ // DBML form `enum core.job_status { ... }`: the qualifier names the
1466
+ // container, as for a schema-qualified Table (spec §7.3, §16).
1467
+ let name = this.parseIdentLikeName('enum name');
1468
+ while (this.check(TokenKind.Dot)) {
1469
+ this.advance();
1470
+ name += `.${this.parseIdentLikeName('enum name after dot')}`;
1471
+ }
1459
1472
  this.expect(TokenKind.LBrace, "Expected '{' after enum name");
1460
1473
  const values = [];
1461
1474
  while (!this.check(TokenKind.RBrace) && !this.check(TokenKind.EOF)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xdbml/parse",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
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",