@stxt-lang/core 0.6.0 → 0.6.2

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/out/all.d.ts CHANGED
@@ -17,6 +17,8 @@ export { transformNodeToSchema } from "./schema/SchemaParser";
17
17
  export { UnifiedSchemaProvider } from "./runtime/UnifiedSchemaProvider";
18
18
  export { ConditionalValidator } from "./runtime/ConditionalValidator";
19
19
  export { NodeWriter, IndentStyle } from "./runtime/NodeWriter";
20
+ export { toCanonicalTree, toCanonicalJson } from "./runtime/TreeJson";
21
+ export type { CanonicalDocument, CanonicalNode, CanonicalInlineNode, CanonicalBlockNode } from "./runtime/TreeJson";
20
22
  export { transformTemplateNodeToSchema } from "./template/TemplateParser";
21
23
  export { DiscoveryResolver, DiscoveryOptions } from "./discovery/DiscoveryResolver";
22
24
  export { DiscoveryResult, DiscoveryDefinition, DiscoveryLevel } from "./discovery/DiscoveryResult";
package/out/all.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // Anything that should be consumable by third parties (e.g. the VSCode extension)
4
4
  // has to be re-exported from here.
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DiscoveryError = exports.DiscoveryResult = exports.DiscoveryResolver = exports.transformTemplateNodeToSchema = exports.IndentStyle = exports.NodeWriter = exports.ConditionalValidator = exports.UnifiedSchemaProvider = exports.transformNodeToSchema = exports.ChildDefinition = exports.NodeDefinition = exports.SchemaValidator = exports.Schema = exports.ValidationException = exports.ParseException = exports.StringUtils = exports.parseLine = exports.Constants = exports.Line = exports.ParseResult = exports.Parser = exports.Node = void 0;
6
+ exports.DiscoveryError = exports.DiscoveryResult = exports.DiscoveryResolver = exports.transformTemplateNodeToSchema = exports.toCanonicalJson = exports.toCanonicalTree = exports.IndentStyle = exports.NodeWriter = exports.ConditionalValidator = exports.UnifiedSchemaProvider = exports.transformNodeToSchema = exports.ChildDefinition = exports.NodeDefinition = exports.SchemaValidator = exports.Schema = exports.ValidationException = exports.ParseException = exports.StringUtils = exports.parseLine = exports.Constants = exports.Line = exports.ParseResult = exports.Parser = exports.Node = void 0;
7
7
  var Node_1 = require("./core/Node");
8
8
  Object.defineProperty(exports, "Node", { enumerable: true, get: function () { return Node_1.Node; } });
9
9
  var Parser_1 = require("./core/Parser");
@@ -39,6 +39,9 @@ Object.defineProperty(exports, "ConditionalValidator", { enumerable: true, get:
39
39
  var NodeWriter_1 = require("./runtime/NodeWriter");
40
40
  Object.defineProperty(exports, "NodeWriter", { enumerable: true, get: function () { return NodeWriter_1.NodeWriter; } });
41
41
  Object.defineProperty(exports, "IndentStyle", { enumerable: true, get: function () { return NodeWriter_1.IndentStyle; } });
42
+ var TreeJson_1 = require("./runtime/TreeJson");
43
+ Object.defineProperty(exports, "toCanonicalTree", { enumerable: true, get: function () { return TreeJson_1.toCanonicalTree; } });
44
+ Object.defineProperty(exports, "toCanonicalJson", { enumerable: true, get: function () { return TreeJson_1.toCanonicalJson; } });
42
45
  var TemplateParser_1 = require("./template/TemplateParser");
43
46
  Object.defineProperty(exports, "transformTemplateNodeToSchema", { enumerable: true, get: function () { return TemplateParser_1.transformTemplateNodeToSchema; } });
44
47
  var DiscoveryResolver_1 = require("./discovery/DiscoveryResolver");
@@ -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);
@@ -0,0 +1,40 @@
1
+ import { Node } from "../core/Node";
2
+ /** Canonical JSON representation of a parsed STXT document (STXT-TREE-SPEC). */
3
+ export type CanonicalDocument = CanonicalNode[];
4
+ /** A node in the canonical JSON representation of an STXT document. */
5
+ export type CanonicalNode = CanonicalInlineNode | CanonicalBlockNode;
6
+ /** Canonical representation of an INLINE (`:`) node. */
7
+ export interface CanonicalInlineNode {
8
+ name: string;
9
+ canonicalName: string;
10
+ namespace: string;
11
+ form: "inline";
12
+ value: string;
13
+ children: CanonicalNode[];
14
+ }
15
+ /** Canonical representation of a BLOCK (`>>`) node. */
16
+ export interface CanonicalBlockNode {
17
+ name: string;
18
+ canonicalName: string;
19
+ namespace: string;
20
+ form: "block";
21
+ lines: string[];
22
+ }
23
+ /**
24
+ * Converts every root node of a parsed document to the logical tree defined by
25
+ * STXT-TREE-SPEC. The result deliberately excludes source positions, indentation
26
+ * style, comments and derived values such as a qualified name.
27
+ *
28
+ * @param nodes root nodes of an already parsed STXT document.
29
+ * @returns the canonical document tree, ready to be serialized as JSON.
30
+ */
31
+ export declare function toCanonicalTree(nodes: ReadonlyArray<Node>): CanonicalDocument;
32
+ /**
33
+ * Serializes the canonical tree of a parsed document as human-readable JSON.
34
+ * JSON whitespace is not part of STXT-TREE-SPEC; two-space indentation is this
35
+ * implementation's deterministic presentation for command-line use.
36
+ *
37
+ * @param nodes root nodes of an already parsed STXT document.
38
+ * @returns the canonical document tree encoded as JSON, without a final line break.
39
+ */
40
+ export declare function toCanonicalJson(nodes: ReadonlyArray<Node>): string;
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toCanonicalTree = toCanonicalTree;
4
+ exports.toCanonicalJson = toCanonicalJson;
5
+ /**
6
+ * Converts every root node of a parsed document to the logical tree defined by
7
+ * STXT-TREE-SPEC. The result deliberately excludes source positions, indentation
8
+ * style, comments and derived values such as a qualified name.
9
+ *
10
+ * @param nodes root nodes of an already parsed STXT document.
11
+ * @returns the canonical document tree, ready to be serialized as JSON.
12
+ */
13
+ function toCanonicalTree(nodes) {
14
+ return nodes.map(node => toCanonicalNode(node));
15
+ }
16
+ /**
17
+ * Serializes the canonical tree of a parsed document as human-readable JSON.
18
+ * JSON whitespace is not part of STXT-TREE-SPEC; two-space indentation is this
19
+ * implementation's deterministic presentation for command-line use.
20
+ *
21
+ * @param nodes root nodes of an already parsed STXT document.
22
+ * @returns the canonical document tree encoded as JSON, without a final line break.
23
+ */
24
+ function toCanonicalJson(nodes) {
25
+ return JSON.stringify(toCanonicalTree(nodes), null, 2);
26
+ }
27
+ function toCanonicalNode(node) {
28
+ if (node.isTextNode()) {
29
+ return {
30
+ name: node.getName(),
31
+ canonicalName: node.getNormalizedName(),
32
+ namespace: node.getNamespace(),
33
+ form: "block",
34
+ lines: [...node.getTextLines()],
35
+ };
36
+ }
37
+ return {
38
+ name: node.getName(),
39
+ canonicalName: node.getNormalizedName(),
40
+ namespace: node.getNamespace(),
41
+ form: "inline",
42
+ value: node.getValue(),
43
+ children: node.getChildren().map(child => toCanonicalNode(child)),
44
+ };
45
+ }
46
+ //# sourceMappingURL=TreeJson.js.map
@@ -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
  }
@@ -52,9 +52,14 @@ class SchemaProviderMemory {
52
52
  addSchema(txt) {
53
53
  const parser = new Parser_1.Parser();
54
54
  const node = parser.parse(txt)[0];
55
- const schema = (0, SchemaParser_1.transformNodeToSchema)(node);
55
+ // A schema that does not validate against its meta-schema must not be
56
+ // registered (same policy as UnifiedSchemaProvider/DiscoveryResolver)
56
57
  const schemaValidator = new SchemaValidator_1.SchemaValidator(new SchemaProviderMeta_1.SchemaProviderMeta(), true);
57
- schemaValidator.validate(node);
58
+ const errors = schemaValidator.validate(node);
59
+ if (errors.length > 0) {
60
+ throw errors[0];
61
+ }
62
+ const schema = (0, SchemaParser_1.transformNodeToSchema)(node);
58
63
  const key = schema.getNamespace();
59
64
  this.schemas.set(key, schema);
60
65
  }
@@ -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();
@@ -18,7 +18,8 @@ export declare class TemplateSchemaProviderMemory extends SchemaProviderMemory {
18
18
  *
19
19
  * @param template text of the `@stxt.template` document.
20
20
  * @throws ValidationException with code `INVALID_SCHEMA` if the document does not hold exactly
21
- * one template, or if the resulting schema has no namespace.
21
+ * one template or the resulting schema has no namespace, or the first validation error
22
+ * if the template does not validate against the template meta-schema.
22
23
  */
23
24
  addTemplate(template: string): void;
24
25
  }
@@ -31,7 +31,8 @@ class TemplateSchemaProviderMemory extends SchemaProviderMemory_1.SchemaProvider
31
31
  *
32
32
  * @param template text of the `@stxt.template` document.
33
33
  * @throws ValidationException with code `INVALID_SCHEMA` if the document does not hold exactly
34
- * one template, or if the resulting schema has no namespace.
34
+ * one template or the resulting schema has no namespace, or the first validation error
35
+ * if the template does not validate against the template meta-schema.
35
36
  */
36
37
  addTemplate(template) {
37
38
  const parser = new Parser_1.Parser();
@@ -39,9 +40,13 @@ class TemplateSchemaProviderMemory extends SchemaProviderMemory_1.SchemaProvider
39
40
  if (nodes.length !== 1) {
40
41
  throw new ValidationException_1.ValidationException(0, "INVALID_SCHEMA", `There are ${nodes.length}, and expected is 1`);
41
42
  }
42
- // Validate the template against the template meta-schema
43
+ // A template that does not validate against the template meta-schema must not
44
+ // be registered (same policy as UnifiedSchemaProvider/DiscoveryResolver)
43
45
  const schemaValidator = new SchemaValidator_1.SchemaValidator(new MetaTemplateSchemaProvider_1.MetaTemplateSchemaProvider(), true);
44
- schemaValidator.validate(nodes[0]);
46
+ const errors = schemaValidator.validate(nodes[0]);
47
+ if (errors.length > 0) {
48
+ throw errors[0];
49
+ }
45
50
  // Build the schema out of the template
46
51
  const sch = (0, TemplateParser_1.transformTemplateNodeToSchema)(nodes[0]);
47
52
  // Minimum safety check (Java checked the expected namespace here too)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stxt-lang/core",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
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",