@vertekum/schema-builder 0.3.0 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @vertekum/schema-builder
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`7f9db48`](https://github.com/moros-oxus/vertekum/commit/7f9db48344c672b90a97f3f4608bf7de893799bf) Thanks [@tschemmer](https://github.com/tschemmer)! - `<@name>` resolution gains key precedence: an import keyed `name` wins outright — its root, else its own `name` production — so sibling imports' public productions can no longer shadow or collide with a keyed module. The cross-import production search remains as the fallback when no key matches.
8
+ - Updated dependencies []:
9
+ - @vertekum/core@0.3.1
10
+
3
11
  ## 0.3.0
4
12
 
5
13
  ### Minor Changes
package/docs/modules.md CHANGED
@@ -38,8 +38,8 @@ use "./system/color.dfn" as system-color
38
38
 
39
39
  | Reference | Means |
40
40
  | -------------------- | ---------------------------------------------------------------------------- |
41
- | `<@key>` | the imported module's **root** — the whole subtree it grants. |
42
- | `<@name>` | the production `name`, searched across every import; unambiguous or an error. |
41
+ | `<@key>` | an import KEYED `key` **wins outright**: its root — else its own `key` production (the fragment idiom) — sibling imports' productions can never shadow or collide with it. |
42
+ | `<@name>` | no key matches: the production `name`, searched across every import; unambiguous or an error. |
43
43
  | `<@key/name>` | qualified: that import's production alone — the collision resolver. |
44
44
 
45
45
  When two imports both declare `<accent>`, the unqualified `<@accent>` refuses with the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertekum/schema-builder",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Declare token vocabularies as grammar; build them into JSON Schema files",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -37,13 +37,13 @@
37
37
  "./cli": "./src/cli.ts"
38
38
  },
39
39
  "peerDependencies": {
40
- "@vertekum/core": "0.3.0"
40
+ "@vertekum/core": "0.3.1"
41
41
  },
42
42
  "dependencies": {
43
43
  "zod": "^3.25.76"
44
44
  },
45
45
  "devDependencies": {
46
- "@vertekum/core": "0.3.0",
46
+ "@vertekum/core": "0.3.1",
47
47
  "@vertekum/schema-vocabulary": "0.3.0"
48
48
  }
49
49
  }
package/src/build.ts CHANGED
@@ -125,41 +125,51 @@ function target(
125
125
  };
126
126
  }
127
127
 
128
+ // 1. A module KEY match wins outright — a keyed module cannot be shadowed by (or
129
+ // collide with) sibling imports' productions. Its root; else its OWN production of
130
+ // the same name (the fragment-declares-its-own-name idiom); else the fragment hint.
131
+ const keyed = scope.module.imports.get(ref.name);
132
+ if (keyed) {
133
+ const keyedScope: Scope = {
134
+ module: keyed,
135
+ expanding: new Set(),
136
+ warn: scope.warn,
137
+ };
138
+ if (keyed.module.root) {
139
+ return {
140
+ node: keyed.module.root,
141
+ scope: keyedScope,
142
+ importedRoot: keyed,
143
+ };
144
+ }
145
+ const own = keyed.module.productions.get(ref.name);
146
+ if (own && !keyed.module.private.has(ref.name)) {
147
+ return { node: own, scope: keyedScope };
148
+ }
149
+ throw new DfnError(
150
+ `'${ref.name}' is imported, but it declares no root (a fragment) — reference one of its productions: ${productionsOf(ref.name, keyed)}`,
151
+ ref.line,
152
+ ref.column,
153
+ file,
154
+ );
155
+ }
156
+
157
+ // 2. No key match: the production searched across every import — unambiguous or say so.
128
158
  const hits: Array<{
129
159
  node: Node;
130
160
  scope: Scope;
131
161
  importedRoot?: ResolvedModule;
132
162
  }> = [];
133
- for (const [key, imported] of scope.module.imports) {
134
- const importedScope: Scope = {
135
- module: imported,
136
- expanding: new Set(),
137
- warn: scope.warn,
138
- };
163
+ for (const imported of scope.module.imports.values()) {
139
164
  const production = imported.module.productions.get(ref.name);
140
165
  if (production && !imported.module.private.has(ref.name)) {
141
- hits.push({ node: production, scope: importedScope });
142
- }
143
- if (key === ref.name && imported.module.root) {
144
166
  hits.push({
145
- node: imported.module.root,
146
- scope: importedScope,
147
- importedRoot: imported,
167
+ node: production,
168
+ scope: { module: imported, expanding: new Set(), warn: scope.warn },
148
169
  });
149
170
  }
150
171
  }
151
172
  if (hits.length === 0) {
152
- // The near-miss that reads as a missing `use`: the import EXISTS under this key, but
153
- // `<@key>` means its root and a fragment has none. Say so, and name the real forms.
154
- const fragment = scope.module.imports.get(ref.name);
155
- if (fragment) {
156
- throw new DfnError(
157
- `'${ref.name}' is imported, but it declares no root (a fragment) — reference one of its productions: ${productionsOf(ref.name, fragment)}`,
158
- ref.line,
159
- ref.column,
160
- file,
161
- );
162
- }
163
173
  throw new DfnError(
164
174
  `no import provides '<@${ref.name}>'`,
165
175
  ref.line,