@xdbml/parse 0.6.3 → 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/dist/module-resolver.d.ts +6 -0
- package/dist/module-resolver.js +19 -7
- package/dist/name-resolver.d.ts +1 -1
- package/dist/name-resolver.js +36 -1
- package/dist/parser.d.ts +3 -1
- package/dist/parser.js +20 -12
- package/package.json +1 -1
|
@@ -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
|
@@ -731,8 +731,11 @@ export function classifyModuleSource(from) {
|
|
|
731
731
|
* exactly as in v0.2.
|
|
732
732
|
*
|
|
733
733
|
* Uses pure string / WHATWG-URL manipulation (no node:path) so the same
|
|
734
|
-
* code runs in Node and the browser.
|
|
735
|
-
*
|
|
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.
|
|
736
739
|
*/
|
|
737
740
|
function resolveModulePath(fromClause, importerPath) {
|
|
738
741
|
const source = classifyModuleSource(fromClause);
|
|
@@ -749,21 +752,30 @@ function resolveModulePath(fromClause, importerPath) {
|
|
|
749
752
|
// also what we want for the cycle-detection / de-duplication key.
|
|
750
753
|
return new URL(rel, importerPath).href;
|
|
751
754
|
}
|
|
752
|
-
// Local relative source (
|
|
753
|
-
|
|
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);
|
|
754
758
|
if (!withExt.endsWith('.xdbml')) {
|
|
755
759
|
withExt = `${withExt}.xdbml`;
|
|
756
760
|
}
|
|
757
|
-
// Absolute path: return as-is.
|
|
758
|
-
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))
|
|
759
763
|
return withExt;
|
|
760
764
|
// No importer context: return as-is (resolver handles it).
|
|
761
765
|
if (!importerPath)
|
|
762
766
|
return withExt;
|
|
763
767
|
// Resolve relative to importer's directory.
|
|
764
|
-
const importerDir = posixDirname(importerPath);
|
|
768
|
+
const importerDir = posixDirname(toForwardSlashes(importerPath));
|
|
765
769
|
return posixJoin(importerDir, withExt);
|
|
766
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
|
+
}
|
|
767
779
|
function posixDirname(p) {
|
|
768
780
|
const idx = p.lastIndexOf('/');
|
|
769
781
|
if (idx < 0)
|
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' | 'duplicate-diagram-view' | 'duplicate-diagram-view-category' | 'unresolved-diagram-view-name' | 'ambiguous-diagram-view-name' | 'diagram-view-wrong-category' | 'unknown-diagram-view-setting';
|
|
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
|
@@ -239,6 +239,28 @@ export function resolveNames(doc) {
|
|
|
239
239
|
diagnostics.push(...checkViews(flat));
|
|
240
240
|
// Pass 7: diagram views (spec §18.6).
|
|
241
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
|
+
}
|
|
242
264
|
return { diagnostics, symbols };
|
|
243
265
|
}
|
|
244
266
|
/* -------------------------------------------------------------------------
|
|
@@ -569,11 +591,24 @@ function resolveTypeExpression(expr, symbols, diagnostics) {
|
|
|
569
591
|
case 'SetType':
|
|
570
592
|
resolveTypeExpression(expr.elementType, symbols, diagnostics);
|
|
571
593
|
return;
|
|
572
|
-
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
|
+
}
|
|
573
607
|
for (const elem of expr.elements) {
|
|
574
608
|
resolveTypeExpression(elem.type, symbols, diagnostics);
|
|
575
609
|
}
|
|
576
610
|
return;
|
|
611
|
+
}
|
|
577
612
|
case 'JsonType':
|
|
578
613
|
// No nested type expressions.
|
|
579
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
|
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);
|
|
@@ -134,6 +134,8 @@ export class Parser {
|
|
|
134
134
|
* 1-arg form).
|
|
135
135
|
*/
|
|
136
136
|
options;
|
|
137
|
+
/** The version the document declares, once read; undefined without a declaration. */
|
|
138
|
+
declaredVersion;
|
|
137
139
|
/**
|
|
138
140
|
* The set of file paths currently being parsed in the resolution chain.
|
|
139
141
|
* Used for cycle detection: when resolving a directive whose `from` path
|
|
@@ -274,6 +276,7 @@ export class Parser {
|
|
|
274
276
|
this.advance(); // .
|
|
275
277
|
version += `.${this.advance().text}`;
|
|
276
278
|
}
|
|
279
|
+
this.declaredVersion = version;
|
|
277
280
|
if (compareVersions(version, SUPPORTED_XDBML_VERSION) > 0) {
|
|
278
281
|
throw new ParseError(`This document declares 'xdbml: ${version}', which is newer than the ` +
|
|
279
282
|
`latest version this parser supports (${SUPPORTED_XDBML_VERSION}). ` +
|
|
@@ -733,6 +736,12 @@ export class Parser {
|
|
|
733
736
|
const start = this.peek().start;
|
|
734
737
|
const modeTok = this.advance(); // 'use' or 'reuse'
|
|
735
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
|
+
}
|
|
736
745
|
// Import spec: '*' or '{ ... }'
|
|
737
746
|
let spec;
|
|
738
747
|
if (this.check(TokenKind.Star)) {
|
|
@@ -742,16 +751,15 @@ export class Parser {
|
|
|
742
751
|
else if (this.check(TokenKind.LBrace)) {
|
|
743
752
|
this.advance();
|
|
744
753
|
const items = [];
|
|
745
|
-
//
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
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))
|
|
753
761
|
break;
|
|
754
|
-
|
|
762
|
+
items.push(this.parseImportItem(context));
|
|
755
763
|
}
|
|
756
764
|
this.expect(TokenKind.RBrace, "Expected '}' closing import item list");
|
|
757
765
|
if (items.length === 0) {
|
|
@@ -2524,7 +2532,7 @@ export function parse(source, options = {}) {
|
|
|
2524
2532
|
// the outer level too). If no filePath is provided, the stack is empty.
|
|
2525
2533
|
const initialStack = new Set();
|
|
2526
2534
|
if (options.filePath)
|
|
2527
|
-
initialStack.add(options.filePath);
|
|
2535
|
+
initialStack.add(toForwardSlashes(options.filePath));
|
|
2528
2536
|
const doc = new Parser(tokens, options, initialStack, 0).parseDocument();
|
|
2529
2537
|
applyEntityAliases(doc);
|
|
2530
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",
|