@stxt-lang/core 0.15.0 → 0.16.0

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.
Files changed (38) hide show
  1. package/README.md +2 -2
  2. package/out/core/InlineNode.js +1 -0
  3. package/out/core/NameNamespace.d.ts +5 -5
  4. package/out/core/NameNamespace.js +5 -5
  5. package/out/core/NamespaceValidator.d.ts +7 -7
  6. package/out/core/NamespaceValidator.js +7 -7
  7. package/out/core/Parser.d.ts +5 -0
  8. package/out/core/Parser.js +15 -17
  9. package/out/core/StringUtils.d.ts +0 -7
  10. package/out/core/StringUtils.js +0 -10
  11. package/out/discovery/DiscoveryResolver.d.ts +1 -1
  12. package/out/discovery/DiscoveryResolver.js +36 -17
  13. package/out/discovery/DiscoveryResult.js +6 -4
  14. package/out/exceptions/ParseException.d.ts +8 -1
  15. package/out/exceptions/ParseException.js +7 -0
  16. package/out/runtime/UnifiedSchemaProvider.d.ts +1 -3
  17. package/out/runtime/UnifiedSchemaProvider.js +14 -32
  18. package/out/schema/DefinitionCompiler.d.ts +27 -0
  19. package/out/schema/DefinitionCompiler.js +53 -0
  20. package/out/schema/NodeDefinition.js +6 -3
  21. package/out/schema/Schema.d.ts +2 -0
  22. package/out/schema/Schema.js +7 -4
  23. package/out/schema/SchemaParser.js +13 -12
  24. package/out/schema/SchemaProviderMemory.js +3 -19
  25. package/out/schema/SchemaProviderMeta.d.ts +7 -1
  26. package/out/schema/SchemaProviderMeta.js +10 -9
  27. package/out/schema/SchemaValidator.js +5 -3
  28. package/out/schema/type/BASE64.d.ts +4 -2
  29. package/out/schema/type/BASE64.js +34 -10
  30. package/out/schema/type/MARKDOWN.d.ts +2 -1
  31. package/out/schema/type/MARKDOWN.js +4 -8
  32. package/out/template/ChildLineParser.js +12 -9
  33. package/out/template/MetaTemplateSchemaProvider.d.ts +3 -1
  34. package/out/template/MetaTemplateSchemaProvider.js +12 -11
  35. package/out/template/TemplateParser.d.ts +1 -2
  36. package/out/template/TemplateParser.js +116 -89
  37. package/out/template/TemplateSchemaProviderMemory.js +3 -19
  38. package/package.json +2 -2
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @stxt-lang/core
2
2
 
3
- Parser and schema validator for **STXT**, an indentation-based structured-text format.
3
+ Parser and schema validator for **STXT**, an indentation-based structured-text language.
4
4
 
5
- STXT is a plain-text format for writing structured, semantic documents: no braces, no closing tags, just indentation. It is designed to be equally readable by humans and by machines, and it comes with an optional schema layer so documents can be validated.
5
+ STXT is a plain-text language for writing structured, semantic documents: no braces, no closing tags, just indentation. It is designed to be equally readable by humans and by machines, and it comes with an optional schema layer so documents can be validated.
6
6
 
7
7
  - Website and language reference: <https://stxt.dev>
8
8
  - VSCode extension: [STXT Language](https://marketplace.visualstudio.com/items?itemName=stxt-lang.stxt)
@@ -66,6 +66,7 @@ class InlineNode extends Node_1.Node {
66
66
  if (child.getParent() !== null) {
67
67
  throw new RuntimeException_1.RuntimeException("NODE_ALREADY_ATTACHED", `Node '${child.getName()}' already has a parent: detach it first`);
68
68
  }
69
+ // eslint-disable-next-line @typescript-eslint/no-this-alias -- cursor of the ancestor walk, not an alias kept around
69
70
  for (let p = this; p !== null; p = p.getParent()) {
70
71
  if (p === child) {
71
72
  throw new RuntimeException_1.RuntimeException("NODE_CYCLE", `Node '${child.getName()}' cannot be a child of itself or of one of its descendants`);
@@ -3,11 +3,11 @@ export declare class NameNamespace {
3
3
  private readonly name;
4
4
  private readonly namespace;
5
5
  /**
6
- * Creates a resolved name and namespace pair.
7
- *
8
- * @param name name of the node without the namespace part.
9
- * @param namespace resolved namespace (its own or inherited).
10
- */
6
+ * Creates a resolved name and namespace pair.
7
+ *
8
+ * @param name name of the node without the namespace part.
9
+ * @param namespace resolved namespace (its own or inherited).
10
+ */
11
11
  constructor(name: string, namespace: string);
12
12
  /** @returns the name of the node, without the namespace part. */
13
13
  getName(): string;
@@ -4,11 +4,11 @@ exports.NameNamespace = void 0;
4
4
  /** Result of splitting a raw node name into its resolved name and namespace. */
5
5
  class NameNamespace {
6
6
  /**
7
- * Creates a resolved name and namespace pair.
8
- *
9
- * @param name name of the node without the namespace part.
10
- * @param namespace resolved namespace (its own or inherited).
11
- */
7
+ * Creates a resolved name and namespace pair.
8
+ *
9
+ * @param name name of the node without the namespace part.
10
+ * @param namespace resolved namespace (its own or inherited).
11
+ */
12
12
  constructor(name, namespace) {
13
13
  this.name = name;
14
14
  this.namespace = namespace;
@@ -11,13 +11,6 @@ export declare class NamespaceValidator {
11
11
  * valid examples: "xxx", "xxx.ddd", "zzz.ttt.ooo", "@xxx", "@xxx.ddd".
12
12
  */
13
13
  private static readonly NAMESPACE_FORMAT;
14
- /**
15
- * Validates the format of a namespace.
16
- *
17
- * @param namespace already normalized namespace to validate; ignored when null or empty.
18
- * @param lineNumber line number, for the error message.
19
- * @throws ParseException with code `INVALID_NAMESPACE` if it does not match the format.
20
- */
21
14
  /**
22
15
  * Tells whether a namespace matches the format, without throwing.
23
16
  *
@@ -25,5 +18,12 @@ export declare class NamespaceValidator {
25
18
  * @returns true if it matches the format; false when it is null, empty or malformed.
26
19
  */
27
20
  static isValid(namespace: string | null | undefined): boolean;
21
+ /**
22
+ * Validates the format of a namespace.
23
+ *
24
+ * @param namespace already normalized namespace to validate; ignored when null or empty.
25
+ * @param lineNumber line number, for the error message.
26
+ * @throws ParseException with code `INVALID_NAMESPACE` if it does not match the format.
27
+ */
28
28
  static validateNamespaceFormat(namespace: string | null | undefined, lineNumber: number): void;
29
29
  }
@@ -4,13 +4,6 @@ exports.NamespaceValidator = void 0;
4
4
  const ParseException_1 = require("../exceptions/ParseException");
5
5
  /** Validates the format of STXT `(a.b.c)` namespaces. */
6
6
  class NamespaceValidator {
7
- /**
8
- * Validates the format of a namespace.
9
- *
10
- * @param namespace already normalized namespace to validate; ignored when null or empty.
11
- * @param lineNumber line number, for the error message.
12
- * @throws ParseException with code `INVALID_NAMESPACE` if it does not match the format.
13
- */
14
7
  /**
15
8
  * Tells whether a namespace matches the format, without throwing.
16
9
  *
@@ -20,6 +13,13 @@ class NamespaceValidator {
20
13
  static isValid(namespace) {
21
14
  return !!namespace && NamespaceValidator.NAMESPACE_FORMAT.test(namespace);
22
15
  }
16
+ /**
17
+ * Validates the format of a namespace.
18
+ *
19
+ * @param namespace already normalized namespace to validate; ignored when null or empty.
20
+ * @param lineNumber line number, for the error message.
21
+ * @throws ParseException with code `INVALID_NAMESPACE` if it does not match the format.
22
+ */
23
23
  static validateNamespaceFormat(namespace, lineNumber) {
24
24
  if (!namespace) {
25
25
  return;
@@ -100,6 +100,11 @@ export declare class Parser {
100
100
  * registered callback fires the same.
101
101
  */
102
102
  private parseLines;
103
+ /**
104
+ * Processes one source line. Errors of this line are collected into the result and the
105
+ * traversal continues with the next line: returns true to keep going, false when a limit
106
+ * aborted the parse (its error is already emitted) — parseLines stops on it.
107
+ */
103
108
  private processLine;
104
109
  /**
105
110
  * Records an error raised while parsing or validating a line. Typed exceptions travel as they
@@ -139,20 +139,18 @@ class Parser {
139
139
  this.emitError(new LimitException_1.LimitException(lineNumber, "LIMIT_INPUT_SIZE_EXCEEDED", `Input larger than ${this.maxInputSize} characters`), result);
140
140
  return;
141
141
  }
142
- try {
143
- this.processLine(line, lineNumber, stack, result);
144
- }
145
- catch (e) {
146
- if (e instanceof LimitException_1.LimitException) {
147
- this.emitError(e, result);
148
- return;
149
- }
150
- throw e;
142
+ if (!this.processLine(line, lineNumber, stack, result)) {
143
+ return; // a limit aborted the parse; its error is already emitted
151
144
  }
152
145
  }
153
146
  // Close every node still open at EOF
154
147
  this.closeToLevel(stack, 0, result);
155
148
  }
149
+ /**
150
+ * Processes one source line. Errors of this line are collected into the result and the
151
+ * traversal continues with the next line: returns true to keep going, false when a limit
152
+ * aborted the parse (its error is already emitted) — parseLines stops on it.
153
+ */
156
154
  processLine(lineString, lineNumber, stack, result) {
157
155
  try {
158
156
  const lastNode = stack.length === 0 ? null : stack[stack.length - 1];
@@ -176,7 +174,7 @@ class Parser {
176
174
  this.observers.forEach(observer => {
177
175
  observer.onComment(lineNumber, lineString);
178
176
  });
179
- return;
177
+ return true;
180
178
  }
181
179
  const currentLevel = line.level;
182
180
  // When we are inside a text node and the level says it is still text,
@@ -188,17 +186,18 @@ class Parser {
188
186
  this.observers.forEach(observer => {
189
187
  observer.onTextLine(textNode, lineNumber, lineString, line);
190
188
  });
191
- return;
189
+ return true;
192
190
  }
193
191
  // Empty lines are ignored
194
192
  if (line.isEmpty()) {
195
- return;
193
+ return true;
196
194
  }
197
195
  // Nesting limit (spec 11.2): only a node line can open a new level. Comment and
198
196
  // block text lines returned above; with the consecutive-level rule this triggers
199
197
  // exactly when the first node at level maxNesting opens.
200
198
  if (this.maxNesting !== -1 && currentLevel >= this.maxNesting) {
201
- throw new LimitException_1.LimitException(lineNumber, "LIMIT_NESTING_EXCEEDED", `Nesting deeper than ${this.maxNesting} levels`);
199
+ this.emitError(new LimitException_1.LimitException(lineNumber, "LIMIT_NESTING_EXCEEDED", `Nesting deeper than ${this.maxNesting} levels`), result);
200
+ return false;
202
201
  }
203
202
  // Close the nodes down to the current level (this "finishes" them: validators and observers run)
204
203
  this.closeToLevel(stack, currentLevel, result);
@@ -220,11 +219,9 @@ class Parser {
220
219
  stack.push(node);
221
220
  }
222
221
  catch (e) {
223
- if (e instanceof LimitException_1.LimitException) {
224
- throw e;
225
- }
226
222
  this.handleError(e, lineNumber, result);
227
223
  }
224
+ return true;
228
225
  }
229
226
  /**
230
227
  * Records an error raised while parsing or validating a line. Typed exceptions travel as they
@@ -256,7 +253,8 @@ class Parser {
256
253
  }
257
254
  closeToLevel(stack, targetLevel, result) {
258
255
  while (stack.length > targetLevel) {
259
- const completed = stack.pop();
256
+ const completed = stack[stack.length - 1];
257
+ stack.pop();
260
258
  // A closing block node drops its final empty lines (STXT-SPEC §10.3): they are not
261
259
  // content, only visual separation or an editor's final line breaks. The validators
262
260
  // and observers below already see the trimmed node; onTextLine did fire for these
@@ -27,13 +27,6 @@ export declare class StringUtils {
27
27
  * @returns the string without trailing blanks; null/undefined is treated as the empty string.
28
28
  */
29
29
  static rightTrim(s: string | null | undefined): string;
30
- /**
31
- * Removes every whitespace character of a string.
32
- *
33
- * @param input string to remove the spaces from.
34
- * @returns the string without any whitespace at all.
35
- */
36
- static cleanSpaces(input: string): string;
37
30
  /**
38
31
  * Lower-cases a string.
39
32
  *
@@ -33,16 +33,6 @@ class StringUtils {
33
33
  static rightTrim(s) {
34
34
  return (s ?? "").replace(this.TRAILING_BLANKS, "");
35
35
  }
36
- // Used for BASE64 and HEXADECIMAL nodes
37
- /**
38
- * Removes every whitespace character of a string.
39
- *
40
- * @param input string to remove the spaces from.
41
- * @returns the string without any whitespace at all.
42
- */
43
- static cleanSpaces(input) {
44
- return input.replace(/\s+/g, "");
45
- }
46
36
  // Used to normalize namespaces
47
37
  /**
48
38
  * Lower-cases a string.
@@ -68,7 +68,7 @@ export declare class DiscoveryResolver {
68
68
  private existingUnique;
69
69
  private loadLevel;
70
70
  private collectFiles;
71
+ private collectFilesAt;
71
72
  private loadFile;
72
73
  private loadRootNode;
73
- private compile;
74
74
  }
@@ -4,8 +4,9 @@ exports.DiscoveryResolver = void 0;
4
4
  const Parser_1 = require("../core/Parser");
5
5
  const StringUtils_1 = require("../core/StringUtils");
6
6
  const ParseException_1 = require("../exceptions/ParseException");
7
+ const Schema_1 = require("../schema/Schema");
8
+ const DefinitionCompiler_1 = require("../schema/DefinitionCompiler");
7
9
  const SchemaProviderMeta_1 = require("../schema/SchemaProviderMeta");
8
- const SchemaValidator_1 = require("../schema/SchemaValidator");
9
10
  const SchemaParser_1 = require("../schema/SchemaParser");
10
11
  const MetaTemplateSchemaProvider_1 = require("../template/MetaTemplateSchemaProvider");
11
12
  const TemplateParser_1 = require("../template/TemplateParser");
@@ -17,6 +18,12 @@ const STXT_DIR = ".stxt";
17
18
  const STXT_EXTENSION = ".stxt";
18
19
  /** Default value of {@link DiscoveryOptions.maxAscent}. */
19
20
  const DEFAULT_MAX_ASCENT = 32;
21
+ /**
22
+ * Maximum depth of the recursive descent through a resolution directory's subtree. An
23
+ * internal safeguard (STXT-DISCOVERY-SPEC sections 3 and 10) against symlink loops and
24
+ * pathological trees; not part of the public API.
25
+ */
26
+ const DEFAULT_MAX_DESCENT = 32;
20
27
  /**
21
28
  * Reference implementation of STXT-DISCOVERY-SPEC: builds the resolution chain of a
22
29
  * document (project ascent, user level, system level, or the `STXT_PATH` override), loads
@@ -135,13 +142,34 @@ class DiscoveryResolver {
135
142
  return level;
136
143
  }
137
144
  // Collects every file under a directory, recursively, sorted by path so that results
138
- // and error messages do not depend on the listing order of the file system.
145
+ // and error messages do not depend on the listing order of the file system. The descent
146
+ // is bounded by DEFAULT_MAX_DESCENT and tolerant of listing failures (STXT-DISCOVERY-SPEC
147
+ // sections 3 and 10): a subdirectory that reaches the depth limit or cannot be listed
148
+ // contributes no files, never an exception. Together with adapters that do not follow
149
+ // directory symlinks, this stops symlink loops and pathological trees from turning
150
+ // resolution into unbounded recursion or an escaping error.
139
151
  async collectFiles(dir) {
152
+ return this.collectFilesAt(dir, 0);
153
+ }
154
+ async collectFilesAt(dir, depth) {
140
155
  const files = [];
141
- const entries = [...await this.fs.listDirectory(dir)].sort((a, b) => a.path < b.path ? -1 : 1);
156
+ // Safeguard against symlink loops and pathological trees (spec section 10): stop
157
+ // descending once the depth limit is reached.
158
+ if (depth >= DEFAULT_MAX_DESCENT) {
159
+ return files;
160
+ }
161
+ let entries;
162
+ try {
163
+ entries = [...await this.fs.listDirectory(dir)].sort((a, b) => a.path < b.path ? -1 : 1);
164
+ }
165
+ catch {
166
+ // A directory that cannot be listed contributes no files (spec section 3); it
167
+ // does not stop the resolution of the rest of the level.
168
+ return files;
169
+ }
142
170
  for (const entry of entries) {
143
171
  if (entry.isDirectory) {
144
- files.push(...await this.collectFiles(entry.path));
172
+ files.push(...await this.collectFilesAt(entry.path, depth + 1));
145
173
  }
146
174
  else {
147
175
  files.push(entry.path);
@@ -179,11 +207,11 @@ class DiscoveryResolver {
179
207
  const namespace = node.getNamespace();
180
208
  let schema;
181
209
  try {
182
- if (namespace === "@stxt.template") {
183
- schema = this.compile(node, this.templateMeta, TemplateParser_1.transformTemplateNodeToSchema);
210
+ if (namespace === Schema_1.Schema.TEMPLATE_NAMESPACE) {
211
+ schema = (0, DefinitionCompiler_1.compileDefinitionNode)(node, this.templateMeta, TemplateParser_1.transformTemplateNodeToSchema);
184
212
  }
185
- else if (namespace === "@stxt.schema") {
186
- schema = this.compile(node, this.schemaMeta, SchemaParser_1.transformNodeToSchema);
213
+ else if (namespace === Schema_1.Schema.SCHEMA_NAMESPACE) {
214
+ schema = (0, DefinitionCompiler_1.compileDefinitionNode)(node, this.schemaMeta, SchemaParser_1.transformNodeToSchema);
187
215
  }
188
216
  else {
189
217
  level.errors.push(new DiscoveryError_1.DiscoveryError(DiscoveryError_1.DiscoveryError.NOT_A_DEFINITION, file, `Root node belongs to '${namespace ?? ""}', not to @stxt.schema or @stxt.template: ${file}`));
@@ -217,15 +245,6 @@ class DiscoveryResolver {
217
245
  };
218
246
  level.definitions.set(key, definition);
219
247
  }
220
- // Validates a root node against a meta-schema and transforms it into a Schema,
221
- // throwing the first validation error (same policy as UnifiedSchemaProvider).
222
- compile(node, meta, transform) {
223
- const errors = new SchemaValidator_1.SchemaValidator(meta, true).validate(node);
224
- if (errors.length > 0) {
225
- throw errors[0];
226
- }
227
- return transform(node);
228
- }
229
248
  }
230
249
  exports.DiscoveryResolver = DiscoveryResolver;
231
250
  //# sourceMappingURL=DiscoveryResolver.js.map
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DiscoveryResult = void 0;
4
4
  const StringUtils_1 = require("../core/StringUtils");
5
+ const Schema_1 = require("../schema/Schema");
5
6
  /**
6
7
  * The outcome of resolving a document's definitions (STXT-DISCOVERY-SPEC): the chain of
7
8
  * levels, the active definition per namespace (nearest level wins) and every resolution
@@ -34,11 +35,12 @@ class DiscoveryResult {
34
35
  * @returns the schema of the namespace, or null if the chain has no definition for it.
35
36
  */
36
37
  getSchema(namespace) {
37
- if (namespace === "@stxt.template") {
38
- return this.templateMeta.getSchema(namespace);
38
+ const key = StringUtils_1.StringUtils.lowerCase(namespace);
39
+ if (key === Schema_1.Schema.TEMPLATE_NAMESPACE) {
40
+ return this.templateMeta.getSchema(key);
39
41
  }
40
- else if (namespace === "@stxt.schema") {
41
- return this.schemaMeta.getSchema(namespace);
42
+ else if (key === Schema_1.Schema.SCHEMA_NAMESPACE) {
43
+ return this.schemaMeta.getSchema(key);
42
44
  }
43
45
  return this.getDefinition(namespace)?.schema ?? null;
44
46
  }
@@ -3,7 +3,14 @@
3
3
  * carries an UPPERCASE code and the line of the document where it was detected.
4
4
  */
5
5
  export declare class ParseException extends Error {
6
- /** Line number of the document where the error was detected. */
6
+ /**
7
+ * Line of an error that has no single source line: it concerns the document as a whole
8
+ * (`SCHEMA_MULTIPLE_ROOTS`) or a condition with no one line to point at (`NODE_DUPLICATED`,
9
+ * `CHILD_NOT_DEFINED`). The value 0 is part of the conformance surface (the kit asserts it);
10
+ * it is not `Node.NO_LINE` (-1), which marks nodes built programmatically, never errors.
11
+ */
12
+ static readonly NO_LINE = 0;
13
+ /** Line number of the document where the error was detected, or {@link ParseException.NO_LINE}. */
7
14
  readonly line: number;
8
15
  /** Error code in UPPERCASE (e.g. `INVALID_LINE`). */
9
16
  readonly code: string;
@@ -32,4 +32,11 @@ class ParseException extends Error {
32
32
  }
33
33
  }
34
34
  exports.ParseException = ParseException;
35
+ /**
36
+ * Line of an error that has no single source line: it concerns the document as a whole
37
+ * (`SCHEMA_MULTIPLE_ROOTS`) or a condition with no one line to point at (`NODE_DUPLICATED`,
38
+ * `CHILD_NOT_DEFINED`). The value 0 is part of the conformance surface (the kit asserts it);
39
+ * it is not `Node.NO_LINE` (-1), which marks nodes built programmatically, never errors.
40
+ */
41
+ ParseException.NO_LINE = 0;
35
42
  //# sourceMappingURL=ParseException.js.map
@@ -30,9 +30,7 @@ export declare class UnifiedSchemaProvider implements SchemaProvider {
30
30
  * a schema or a template does not validate against its meta-schema.
31
31
  */
32
32
  addFile(text: string): void;
33
- private addTemplateNode;
34
- private addSchemaNode;
35
- private static throwIfInvalid;
33
+ private addNode;
36
34
  /** Removes every schema and template registered in this provider. */
37
35
  clear(): void;
38
36
  /** @returns every schema registered in this provider, in registration order. */
@@ -3,9 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.UnifiedSchemaProvider = void 0;
4
4
  const Parser_1 = require("../core/Parser");
5
5
  const StringUtils_1 = require("../core/StringUtils");
6
+ const Schema_1 = require("../schema/Schema");
7
+ const DefinitionCompiler_1 = require("../schema/DefinitionCompiler");
6
8
  const SchemaProviderMeta_1 = require("../schema/SchemaProviderMeta");
7
9
  const SchemaParser_1 = require("../schema/SchemaParser");
8
- const SchemaValidator_1 = require("../schema/SchemaValidator");
9
10
  const MetaTemplateSchemaProvider_1 = require("../template/MetaTemplateSchemaProvider");
10
11
  const TemplateParser_1 = require("../template/TemplateParser");
11
12
  /**
@@ -31,14 +32,13 @@ class UnifiedSchemaProvider {
31
32
  */
32
33
  getSchema(namespace) {
33
34
  const key = StringUtils_1.StringUtils.lowerCase(namespace);
34
- if (namespace === "@stxt.template") {
35
+ if (key === Schema_1.Schema.TEMPLATE_NAMESPACE) {
35
36
  return this.templateMeta.getSchema(key);
36
37
  }
37
- else if (namespace === "@stxt.schema") {
38
+ else if (key === Schema_1.Schema.SCHEMA_NAMESPACE) {
38
39
  return this.schemaMeta.getSchema(key);
39
40
  }
40
- let result = this.schemas.get(key);
41
- return result;
41
+ return this.schemas.get(key);
42
42
  }
43
43
  /**
44
44
  * Parses a document and registers every schema or template it defines, each one under its own
@@ -53,37 +53,19 @@ class UnifiedSchemaProvider {
53
53
  const nodes = parser.parse(text);
54
54
  for (const node of nodes) {
55
55
  const namespace = node.getNamespace();
56
- if (namespace === "@stxt.template") {
57
- this.addTemplateNode(node);
56
+ if (namespace === Schema_1.Schema.TEMPLATE_NAMESPACE) {
57
+ this.addNode(node, this.templateMeta, TemplateParser_1.transformTemplateNodeToSchema);
58
58
  }
59
- else if (namespace === "@stxt.schema") {
60
- this.addSchemaNode(node);
59
+ else if (namespace === Schema_1.Schema.SCHEMA_NAMESPACE) {
60
+ this.addNode(node, this.schemaMeta, SchemaParser_1.transformNodeToSchema);
61
61
  }
62
62
  }
63
63
  }
64
- addTemplateNode(node) {
65
- // Validate against the meta-schema of templates
66
- const schemaValidator = new SchemaValidator_1.SchemaValidator(this.templateMeta, true);
67
- UnifiedSchemaProvider.throwIfInvalid(schemaValidator.validate(node));
68
- // Transform the template into a schema
69
- const schema = (0, TemplateParser_1.transformTemplateNodeToSchema)(node);
70
- const key = StringUtils_1.StringUtils.lowerCase(schema.getNamespace());
71
- this.schemas.set(key, schema);
72
- }
73
- addSchemaNode(node) {
74
- // Validate against the meta-schema of schemas
75
- const schemaValidator = new SchemaValidator_1.SchemaValidator(this.schemaMeta, true);
76
- UnifiedSchemaProvider.throwIfInvalid(schemaValidator.validate(node));
77
- // Transform the node into a schema
78
- const schema = (0, SchemaParser_1.transformNodeToSchema)(node);
79
- const key = StringUtils_1.StringUtils.lowerCase(schema.getNamespace());
80
- this.schemas.set(key, schema);
81
- }
82
- // A schema/template that does not validate against its meta-schema must not be loaded
83
- static throwIfInvalid(errors) {
84
- if (errors.length > 0) {
85
- throw errors[0];
86
- }
64
+ // Compiles a definition root through the shared pipeline (see DefinitionCompiler)
65
+ // and registers it; a definition that does not validate is never registered.
66
+ addNode(node, meta, transform) {
67
+ const schema = (0, DefinitionCompiler_1.compileDefinitionNode)(node, meta, transform);
68
+ this.schemas.set(StringUtils_1.StringUtils.lowerCase(schema.getNamespace()), schema);
87
69
  }
88
70
  /** Removes every schema and template registered in this provider. */
89
71
  clear() {
@@ -0,0 +1,27 @@
1
+ import { Node } from "../core/Node";
2
+ import { Schema } from "./Schema";
3
+ import { SchemaProvider } from "./SchemaProvider";
4
+ /**
5
+ * Validates one root node against the meta provider of its kind and compiles it into a
6
+ * {@link Schema}.
7
+ *
8
+ * @param node root node of the definition (`Schema (@stxt.schema)` or `Template (@stxt.template)`).
9
+ * @param meta provider of the meta-schema of the kind.
10
+ * @param transform function that turns the validated node into a Schema.
11
+ * @returns the compiled schema.
12
+ * @throws ValidationException the first validation finding, if the node does not validate.
13
+ */
14
+ export declare function compileDefinitionNode(node: Node, meta: SchemaProvider, transform: (node: Node) => Schema): Schema;
15
+ /**
16
+ * Parses a whole document that must hold exactly one definition, and compiles it.
17
+ *
18
+ * @param text text of the definition document.
19
+ * @param meta provider of the meta-schema of the kind.
20
+ * @param transform function that turns the validated root into a Schema.
21
+ * @param multipleRootsCode error code when the document does not hold exactly one root
22
+ * (`SCHEMA_MULTIPLE_ROOTS` for schemas, `TEMPLATE_MULTIPLE_ROOTS` for templates).
23
+ * @param kind word naming the kind in the error message (`schema` or `template`).
24
+ * @returns the compiled schema.
25
+ * @throws ParseException or ValidationException if the document is not a valid definition.
26
+ */
27
+ export declare function compileDefinitionDocument(text: string, meta: SchemaProvider, transform: (node: Node) => Schema, multipleRootsCode: string, kind: string): Schema;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileDefinitionNode = compileDefinitionNode;
4
+ exports.compileDefinitionDocument = compileDefinitionDocument;
5
+ const Parser_1 = require("../core/Parser");
6
+ const ParseException_1 = require("../exceptions/ParseException");
7
+ const ValidationException_1 = require("../exceptions/ValidationException");
8
+ const SchemaValidator_1 = require("./SchemaValidator");
9
+ /*
10
+ * The one pipeline every definition loader shares, whatever the store: the in-memory
11
+ * providers (a document each), UnifiedSchemaProvider (several roots per file) and
12
+ * discovery. A definition node is validated against the meta-schema of its kind and,
13
+ * only when valid, transformed into a Schema; a definition that does not validate is
14
+ * never registered anywhere — the first validation finding is thrown instead.
15
+ * Mirrors stxt-impl/schema/definition_compiler.txt.
16
+ */
17
+ /**
18
+ * Validates one root node against the meta provider of its kind and compiles it into a
19
+ * {@link Schema}.
20
+ *
21
+ * @param node root node of the definition (`Schema (@stxt.schema)` or `Template (@stxt.template)`).
22
+ * @param meta provider of the meta-schema of the kind.
23
+ * @param transform function that turns the validated node into a Schema.
24
+ * @returns the compiled schema.
25
+ * @throws ValidationException the first validation finding, if the node does not validate.
26
+ */
27
+ function compileDefinitionNode(node, meta, transform) {
28
+ const errors = new SchemaValidator_1.SchemaValidator(meta, true).validate(node);
29
+ if (errors.length > 0) {
30
+ throw errors[0];
31
+ }
32
+ return transform(node);
33
+ }
34
+ /**
35
+ * Parses a whole document that must hold exactly one definition, and compiles it.
36
+ *
37
+ * @param text text of the definition document.
38
+ * @param meta provider of the meta-schema of the kind.
39
+ * @param transform function that turns the validated root into a Schema.
40
+ * @param multipleRootsCode error code when the document does not hold exactly one root
41
+ * (`SCHEMA_MULTIPLE_ROOTS` for schemas, `TEMPLATE_MULTIPLE_ROOTS` for templates).
42
+ * @param kind word naming the kind in the error message (`schema` or `template`).
43
+ * @returns the compiled schema.
44
+ * @throws ParseException or ValidationException if the document is not a valid definition.
45
+ */
46
+ function compileDefinitionDocument(text, meta, transform, multipleRootsCode, kind) {
47
+ const nodes = new Parser_1.Parser().parse(text);
48
+ if (nodes.length !== 1) {
49
+ throw new ValidationException_1.ValidationException(ParseException_1.ParseException.NO_LINE, multipleRootsCode, `A ${kind} document must hold exactly 1 root node, got ${nodes.length}`);
50
+ }
51
+ return compileDefinitionNode(nodes[0], meta, transform);
52
+ }
53
+ //# sourceMappingURL=DefinitionCompiler.js.map
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NodeDefinition = void 0;
4
+ const ParseException_1 = require("../exceptions/ParseException");
4
5
  const ValidationException_1 = require("../exceptions/ValidationException");
5
6
  const StringUtils_1 = require("../core/StringUtils");
6
7
  /**
@@ -65,7 +66,7 @@ class NodeDefinition {
65
66
  addChildDefinition(childDefinition) {
66
67
  const qname = childDefinition.getQualifiedName();
67
68
  if (this.children.has(qname)) {
68
- throw new ValidationException_1.ValidationException(0, "CHILD_DUPLICATED", `Exists a previous node definition with: ${qname}`);
69
+ throw new ValidationException_1.ValidationException(ParseException_1.ParseException.NO_LINE, "CHILD_DUPLICATED", `A child declaration with the same name already exists: ${qname}`);
69
70
  }
70
71
  this.children.set(qname, childDefinition);
71
72
  }
@@ -80,9 +81,11 @@ class NodeDefinition {
80
81
  * @throws ValidationException with code `VALUE_DUPLICATED` if the value (once trimmed) had already been added.
81
82
  */
82
83
  addValue(value, line) {
83
- const trimmed = value?.trim() ?? "";
84
+ // Language blanks only (U+0020/U+0009): any other whitespace (NBSP...) is part of
85
+ // the value, so `x` and `x<NBSP>` are two different ENUM values, as in every port.
86
+ const trimmed = StringUtils_1.StringUtils.trim(value ?? "");
84
87
  if (this.values.has(trimmed)) {
85
- throw new ValidationException_1.ValidationException(line ?? 0, "VALUE_DUPLICATED", `The values ${trimmed} is duplicated`);
88
+ throw new ValidationException_1.ValidationException(line ?? ParseException_1.ParseException.NO_LINE, "VALUE_DUPLICATED", `The value ${trimmed} is duplicated`);
86
89
  }
87
90
  this.values.add(trimmed);
88
91
  }
@@ -3,6 +3,8 @@ import { NodeDefinition } from "./NodeDefinition";
3
3
  export declare class Schema {
4
4
  /** Namespace of the schema language itself, `@stxt.schema`. */
5
5
  static readonly SCHEMA_NAMESPACE = "@stxt.schema";
6
+ /** Namespace of the template language, `@stxt.template`. */
7
+ static readonly TEMPLATE_NAMESPACE = "@stxt.template";
6
8
  private readonly nodes;
7
9
  private readonly namespace;
8
10
  private readonly description;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Schema = void 0;
4
4
  const NamespaceValidator_1 = require("../core/NamespaceValidator");
5
5
  const StringUtils_1 = require("../core/StringUtils");
6
+ const ParseException_1 = require("../exceptions/ParseException");
6
7
  const ValidationException_1 = require("../exceptions/ValidationException");
7
8
  /** Schema of a namespace: the set of {@link NodeDefinition} valid for the nodes of that namespace. */
8
9
  class Schema {
@@ -44,11 +45,11 @@ class Schema {
44
45
  * @throws ValidationException with code `NODE_DUPLICATED` if there already was a node definition with the same name.
45
46
  */
46
47
  addNodeDefinition(nodeDefinition) {
47
- const qname = nodeDefinition.getCanonicalName();
48
- if (this.nodes.has(qname)) {
49
- throw new ValidationException_1.ValidationException(0, "NODE_DUPLICATED", `Exists a previous node definition with: ${qname}`);
48
+ const canonicalName = nodeDefinition.getCanonicalName();
49
+ if (this.nodes.has(canonicalName)) {
50
+ throw new ValidationException_1.ValidationException(ParseException_1.ParseException.NO_LINE, "NODE_DUPLICATED", `A node definition with the same name already exists: ${canonicalName}`);
50
51
  }
51
- this.nodes.set(qname, nodeDefinition);
52
+ this.nodes.set(canonicalName, nodeDefinition);
52
53
  }
53
54
  /** @returns the namespace this schema applies to. */
54
55
  getNamespace() {
@@ -69,4 +70,6 @@ class Schema {
69
70
  exports.Schema = Schema;
70
71
  /** Namespace of the schema language itself, `@stxt.schema`. */
71
72
  Schema.SCHEMA_NAMESPACE = "@stxt.schema";
73
+ /** Namespace of the template language, `@stxt.template`. */
74
+ Schema.TEMPLATE_NAMESPACE = "@stxt.template";
72
75
  //# sourceMappingURL=Schema.js.map