@stxt-lang/core 0.6.1 → 0.6.3

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.
@@ -5,7 +5,6 @@
5
5
  * as told apart by {@link Node.isTextNode}.
6
6
  */
7
7
  export declare class Node {
8
- private static readonly VALID_NAME;
9
8
  private readonly name;
10
9
  private readonly normalizedName;
11
10
  private readonly namespace;
package/out/core/Node.js CHANGED
@@ -38,10 +38,7 @@ class Node {
38
38
  if (this.value.length > 0 && this.isTextNode()) {
39
39
  throw new RuntimeException_1.RuntimeException("INLINE_VALUE_NOT_VALID", "Not empty value with textNode");
40
40
  }
41
- if (!Node.VALID_NAME.test(name)) {
42
- throw new ParseException_1.ParseException(line, "INVALID_NODE_NAME", `Node name contains invalid characters: ${name}`);
43
- }
44
- if (this.normalizedName.length === 0) {
41
+ if (!StringUtils_1.StringUtils.isValidNodeName(this.name)) {
45
42
  throw new ParseException_1.ParseException(line, "INVALID_NODE_NAME", `Node name not valid: ${name}`);
46
43
  }
47
44
  }
@@ -166,6 +163,4 @@ class Node {
166
163
  }
167
164
  }
168
165
  exports.Node = Node;
169
- // STXT-SPEC 4.2: Unicode letters and digits (categories L and Nd) plus '-', '_' and space
170
- Node.VALID_NAME = /^[\p{L}\p{Nd}\-_ ]+$/u;
171
166
  //# sourceMappingURL=Node.js.map
@@ -1,5 +1,6 @@
1
1
  /** String normalization helpers used for names, namespaces and values. */
2
2
  export declare class StringUtils {
3
+ private static readonly NODE_NAME;
3
4
  private constructor();
4
5
  /**
5
6
  * Removes the trailing whitespace of a string.
@@ -29,6 +30,16 @@ export declare class StringUtils {
29
30
  * @returns the string with the outer spaces trimmed and the inner ones collapsed into a single one; null/undefined is treated as the empty string.
30
31
  */
31
32
  static compactSpaces(s: string | null | undefined): string;
33
+ /**
34
+ * Tells whether a value is a valid STXT node name.
35
+ *
36
+ * The test happens after NFC normalization: the source may use either the
37
+ * precomposed or decomposed Unicode spelling of a letter with a diacritic.
38
+ *
39
+ * @param input name to validate.
40
+ * @returns true if the name contains only permitted characters and has a non-empty canonical name.
41
+ */
42
+ static isValidNodeName(input: string | null | undefined): boolean;
32
43
  /**
33
44
  * Builds the canonical name of a node, as defined by STXT-SPEC 4.3.
34
45
  *
@@ -51,6 +51,19 @@ class StringUtils {
51
51
  static compactSpaces(s) {
52
52
  return (s ?? "").trim().replace(/\s+/g, " ");
53
53
  }
54
+ /**
55
+ * Tells whether a value is a valid STXT node name.
56
+ *
57
+ * The test happens after NFC normalization: the source may use either the
58
+ * precomposed or decomposed Unicode spelling of a letter with a diacritic.
59
+ *
60
+ * @param input name to validate.
61
+ * @returns true if the name contains only permitted characters and has a non-empty canonical name.
62
+ */
63
+ static isValidNodeName(input) {
64
+ const nfc = this.compactSpaces(input).normalize("NFC");
65
+ return this.NODE_NAME.test(nfc) && this.normalize(nfc).length > 0;
66
+ }
54
67
  // Used for the normalized name of the nodes (STXT-SPEC 4.3): NFC + lower case,
55
68
  // keeping diacritics and non-Latin alphabets (IDN model)
56
69
  /**
@@ -74,4 +87,7 @@ class StringUtils {
74
87
  }
75
88
  }
76
89
  exports.StringUtils = StringUtils;
90
+ // STXT-SPEC 4.2 / 4.3: validate the logical name after NFC so that a
91
+ // decomposed spelling such as "e" + combining acute is accepted as "é".
92
+ StringUtils.NODE_NAME = /^[\p{L}\p{Nd}\-_ ]+$/u;
77
93
  //# sourceMappingURL=StringUtils.js.map
@@ -127,10 +127,9 @@ class DiscoveryResolver {
127
127
  if (cached) {
128
128
  return cached;
129
129
  }
130
- const level = { dir, definitions: new Map(), errors: [] };
131
- const conflicted = new Set();
130
+ const level = { dir, definitions: new Map(), conflictedNamespaces: new Set(), errors: [] };
132
131
  for (const file of await this.collectFiles(dir)) {
133
- await this.loadFile(file, level, conflicted);
132
+ await this.loadFile(file, level);
134
133
  }
135
134
  this.levelCache.set(dir, level);
136
135
  return level;
@@ -152,7 +151,7 @@ class DiscoveryResolver {
152
151
  }
153
152
  // Loads one file of a level: parses it and registers every root as a definition,
154
153
  // reporting the errors of spec section 8.
155
- async loadFile(file, level, conflicted) {
154
+ async loadFile(file, level) {
156
155
  // Spec section 3: every file under a resolution directory must be a definition.
157
156
  if (!file.endsWith(STXT_EXTENSION)) {
158
157
  level.errors.push(new DiscoveryError_1.DiscoveryError(DiscoveryError_1.DiscoveryError.NOT_A_DEFINITION, file, `Not an STXT definition file: ${file}`));
@@ -171,12 +170,12 @@ class DiscoveryResolver {
171
170
  return;
172
171
  }
173
172
  for (const node of nodes) {
174
- this.loadRootNode(node, file, level, conflicted);
173
+ this.loadRootNode(node, file, level);
175
174
  }
176
175
  }
177
176
  // Validates one root node against its meta-schema, compiles it to a schema and
178
177
  // registers it in the level, detecting same-level duplicates.
179
- loadRootNode(node, file, level, conflicted) {
178
+ loadRootNode(node, file, level) {
180
179
  const namespace = node.getNamespace();
181
180
  let schema;
182
181
  try {
@@ -200,10 +199,10 @@ class DiscoveryResolver {
200
199
  const existing = level.definitions.get(key);
201
200
  // Spec section 8: on a same-level duplicate, never silently pick one of the
202
201
  // definitions — the namespace has no active definition while the conflict exists.
203
- if (conflicted.has(key) || existing) {
202
+ if (level.conflictedNamespaces.has(key) || existing) {
204
203
  if (existing) {
205
204
  level.definitions.delete(key);
206
- conflicted.add(key);
205
+ level.conflictedNamespaces.add(key);
207
206
  }
208
207
  const firstFile = existing ? existing.file : "another file of this level";
209
208
  level.errors.push(new DiscoveryError_1.DiscoveryError(DiscoveryError_1.DiscoveryError.DUPLICATE_NAMESPACE, file, `Duplicate definition for namespace '${schema.getNamespace()}' at level ${level.dir}: ` +
@@ -24,6 +24,8 @@ export interface DiscoveryLevel {
24
24
  dir: string;
25
25
  /** Definitions of the level by lowercased target namespace, conflicts excluded. */
26
26
  definitions: Map<string, DiscoveryDefinition>;
27
+ /** Namespaces with a same-level conflict; they block fallback to farther levels. */
28
+ conflictedNamespaces: Set<string>;
27
29
  /** Resolution errors found while loading this level. */
28
30
  errors: DiscoveryError[];
29
31
  }
@@ -52,6 +52,11 @@ class DiscoveryResult {
52
52
  getDefinition(namespace) {
53
53
  const key = StringUtils_1.StringUtils.lowerCase(namespace);
54
54
  for (const level of this.levels) {
55
+ // STXT-DISCOVERY-SPEC section 8: a closer conflict leaves the namespace
56
+ // without an active definition instead of falling back to a farther level.
57
+ if (level.conflictedNamespaces.has(key)) {
58
+ return undefined;
59
+ }
55
60
  const definition = level.definitions.get(key);
56
61
  if (definition) {
57
62
  return definition;
@@ -69,6 +74,11 @@ class DiscoveryResult {
69
74
  const seen = new Set();
70
75
  const result = [];
71
76
  for (const level of this.levels) {
77
+ // Mark conflicts as seen so getActiveDefinitions() has the same semantics
78
+ // as getDefinition(): they block definitions in farther levels.
79
+ for (const key of level.conflictedNamespaces) {
80
+ seen.add(key);
81
+ }
72
82
  for (const [key, definition] of level.definitions) {
73
83
  if (!seen.has(key)) {
74
84
  seen.add(key);
@@ -23,7 +23,7 @@ class ChildDefinition {
23
23
  this.min = min;
24
24
  this.max = max;
25
25
  NamespaceValidator_1.NamespaceValidator.validateNamespaceFormat(this.namespace, numLine);
26
- if (this.normalizedName.length === 0) {
26
+ if (!StringUtils_1.StringUtils.isValidNodeName(this.name)) {
27
27
  throw new ValidationException_1.ValidationException(numLine, "INVALID_NODE_NAME", `Node name not valid: ${name}`);
28
28
  }
29
29
  }
@@ -24,7 +24,7 @@ class NodeDefinition {
24
24
  this.normalizedName = StringUtils_1.StringUtils.normalize(name);
25
25
  this.type = type;
26
26
  this.description = description;
27
- if (this.normalizedName.length === 0) {
27
+ if (!StringUtils_1.StringUtils.isValidNodeName(this.name)) {
28
28
  throw new ValidationException_1.ValidationException(line, "INVALID_NODE_NAME", `Node name not valid: ${name}`);
29
29
  }
30
30
  }
@@ -16,9 +16,13 @@ export declare class SchemaProviderMeta implements SchemaProvider {
16
16
  /**
17
17
  * Serves the meta-schema of the schema language.
18
18
  *
19
+ * Follows the {@link SchemaProvider} contract: providers never throw "not found". Any
20
+ * namespace other than `@stxt.schema` yields `null`, so that this provider can sit at
21
+ * the end of a fallback chain (it is the default parent of {@link SchemaProviderMemory})
22
+ * and the {@link SchemaValidator} is the only one reporting `SCHEMA_NOT_FOUND`.
23
+ *
19
24
  * @param namespace namespace whose schema is wanted; only `@stxt.schema` is served.
20
- * @returns the meta-schema of the schema language.
21
- * @throws RuntimeException with code `RESOURCE_NOT_FOUND` if any other namespace is asked for.
25
+ * @returns the meta-schema of the schema language, or `null` for any other namespace.
22
26
  */
23
- getSchema(namespace: string): Schema;
27
+ getSchema(namespace: string): Schema | null;
24
28
  }
@@ -4,7 +4,6 @@ exports.SchemaProviderMeta = void 0;
4
4
  const Schema_1 = require("./Schema");
5
5
  const Parser_1 = require("../core/Parser");
6
6
  const ValidationException_1 = require("../exceptions/ValidationException");
7
- const RuntimeException_1 = require("../exceptions/RuntimeException");
8
7
  const SchemaParser_1 = require("./SchemaParser");
9
8
  /**
10
9
  * {@link SchemaProvider} that defines in code the meta-schema of the schema language itself
@@ -27,13 +26,17 @@ class SchemaProviderMeta {
27
26
  /**
28
27
  * Serves the meta-schema of the schema language.
29
28
  *
29
+ * Follows the {@link SchemaProvider} contract: providers never throw "not found". Any
30
+ * namespace other than `@stxt.schema` yields `null`, so that this provider can sit at
31
+ * the end of a fallback chain (it is the default parent of {@link SchemaProviderMemory})
32
+ * and the {@link SchemaValidator} is the only one reporting `SCHEMA_NOT_FOUND`.
33
+ *
30
34
  * @param namespace namespace whose schema is wanted; only `@stxt.schema` is served.
31
- * @returns the meta-schema of the schema language.
32
- * @throws RuntimeException with code `RESOURCE_NOT_FOUND` if any other namespace is asked for.
35
+ * @returns the meta-schema of the schema language, or `null` for any other namespace.
33
36
  */
34
37
  getSchema(namespace) {
35
38
  if (namespace !== Schema_1.Schema.SCHEMA_NAMESPACE) {
36
- throw new RuntimeException_1.RuntimeException("RESOURCE_NOT_FOUND", `Not found '${namespace}' in namespace: ${Schema_1.Schema.SCHEMA_NAMESPACE}`);
39
+ return null;
37
40
  }
38
41
  if (!this.meta) {
39
42
  throw new ValidationException_1.ValidationException(0, "META_SCHEMA_NOT_AVAILABLE", "Meta schema not available");
@@ -16,9 +16,12 @@ export declare class MetaTemplateSchemaProvider implements SchemaProvider {
16
16
  /**
17
17
  * Serves the meta-schema of the template language.
18
18
  *
19
+ * Follows the {@link SchemaProvider} contract: providers never throw "not found". Any
20
+ * namespace other than `@stxt.template` yields `null`; only the `SchemaValidator`
21
+ * reports `SCHEMA_NOT_FOUND`.
22
+ *
19
23
  * @param namespace namespace whose schema is wanted; only `@stxt.template` is served.
20
- * @returns the meta-schema of the template language.
21
- * @throws RuntimeException with code `RESOURCE_NOT_FOUND` if any other namespace is asked for.
24
+ * @returns the meta-schema of the template language, or `null` for any other namespace.
22
25
  */
23
- getSchema(namespace: string): Schema;
26
+ getSchema(namespace: string): Schema | null;
24
27
  }
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MetaTemplateSchemaProvider = void 0;
4
4
  const Parser_1 = require("../core/Parser");
5
5
  const ValidationException_1 = require("../exceptions/ValidationException");
6
- const RuntimeException_1 = require("../exceptions/RuntimeException");
7
6
  const TemplateParser_1 = require("./TemplateParser");
8
7
  /**
9
8
  * {@link SchemaProvider} that defines in code the meta-schema of the template language itself
@@ -26,13 +25,16 @@ class MetaTemplateSchemaProvider {
26
25
  /**
27
26
  * Serves the meta-schema of the template language.
28
27
  *
28
+ * Follows the {@link SchemaProvider} contract: providers never throw "not found". Any
29
+ * namespace other than `@stxt.template` yields `null`; only the `SchemaValidator`
30
+ * reports `SCHEMA_NOT_FOUND`.
31
+ *
29
32
  * @param namespace namespace whose schema is wanted; only `@stxt.template` is served.
30
- * @returns the meta-schema of the template language.
31
- * @throws RuntimeException with code `RESOURCE_NOT_FOUND` if any other namespace is asked for.
33
+ * @returns the meta-schema of the template language, or `null` for any other namespace.
32
34
  */
33
35
  getSchema(namespace) {
34
36
  if (namespace !== "@stxt.template") {
35
- throw new RuntimeException_1.RuntimeException("RESOURCE_NOT_FOUND", `Not found '${namespace}' in namespace: @stxt.template`);
37
+ return null;
36
38
  }
37
39
  // meta always exists once the constructor finished, but this mirrors the Java version
38
40
  if (!this.meta) {
@@ -72,6 +72,11 @@ function transformTemplateNodeToSchema(node) {
72
72
  }
73
73
  /** Adds to the schema the definition a node of the structure declares, along with its children. */
74
74
  function addToSchema(schema, node) {
75
+ // A Structure line must use the template grammar's ':' form. The core parser
76
+ // also accepts BLOCK nodes here, so reject them explicitly (STXT-TEMPLATE-SPEC 6.3).
77
+ if (node.isTextNode()) {
78
+ throw new ValidationException_1.ValidationException(node.getLine(), "INVALID_CHILD_LINE", "Template Structure lines must use ':'");
79
+ }
75
80
  // Get the qualified name
76
81
  let namespace = node.getNamespace();
77
82
  const name = node.getName();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stxt-lang/core",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Parser and schema validator for STXT, an indentation-based structured-text format.",
5
5
  "main": "out/all.js",
6
6
  "types": "out/all.d.ts",