@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 +10 -1
- package/dist/keywords.d.ts +1 -0
- package/dist/keywords.js +54 -3
- package/dist/module-resolver.js +11 -1
- package/dist/monarch.d.ts +1 -0
- package/dist/monarch.js +5 -1
- package/dist/name-resolver.d.ts +1 -1
- package/dist/name-resolver.js +127 -17
- package/dist/parser.js +15 -2
- package/package.json +1 -1
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
|
-
/**
|
|
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;
|
package/dist/keywords.d.ts
CHANGED
|
@@ -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
|
|
103
|
-
*
|
|
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
|
*
|
package/dist/module-resolver.js
CHANGED
|
@@ -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
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',
|
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' | '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.,
|
package/dist/name-resolver.js
CHANGED
|
@@ -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`),
|
|
100
|
-
*
|
|
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
|
|
103
|
-
*
|
|
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
|
-
|
|
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
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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: '
|
|
374
|
-
code: '
|
|
375
|
-
message: `Type '${expr.name}' is not
|
|
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
|
|
829
|
-
// undefined so the walker bails without emitting a
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|