@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.
- package/README.md +2 -2
- package/out/core/InlineNode.js +1 -0
- package/out/core/NameNamespace.d.ts +5 -5
- package/out/core/NameNamespace.js +5 -5
- package/out/core/NamespaceValidator.d.ts +7 -7
- package/out/core/NamespaceValidator.js +7 -7
- package/out/core/Parser.d.ts +5 -0
- package/out/core/Parser.js +15 -17
- package/out/core/StringUtils.d.ts +0 -7
- package/out/core/StringUtils.js +0 -10
- package/out/discovery/DiscoveryResolver.d.ts +1 -1
- package/out/discovery/DiscoveryResolver.js +36 -17
- package/out/discovery/DiscoveryResult.js +6 -4
- package/out/exceptions/ParseException.d.ts +8 -1
- package/out/exceptions/ParseException.js +7 -0
- package/out/runtime/UnifiedSchemaProvider.d.ts +1 -3
- package/out/runtime/UnifiedSchemaProvider.js +14 -32
- package/out/schema/DefinitionCompiler.d.ts +27 -0
- package/out/schema/DefinitionCompiler.js +53 -0
- package/out/schema/NodeDefinition.js +6 -3
- package/out/schema/Schema.d.ts +2 -0
- package/out/schema/Schema.js +7 -4
- package/out/schema/SchemaParser.js +13 -12
- package/out/schema/SchemaProviderMemory.js +3 -19
- package/out/schema/SchemaProviderMeta.d.ts +7 -1
- package/out/schema/SchemaProviderMeta.js +10 -9
- package/out/schema/SchemaValidator.js +5 -3
- package/out/schema/type/BASE64.d.ts +4 -2
- package/out/schema/type/BASE64.js +34 -10
- package/out/schema/type/MARKDOWN.d.ts +2 -1
- package/out/schema/type/MARKDOWN.js +4 -8
- package/out/template/ChildLineParser.js +12 -9
- package/out/template/MetaTemplateSchemaProvider.d.ts +3 -1
- package/out/template/MetaTemplateSchemaProvider.js +12 -11
- package/out/template/TemplateParser.d.ts +1 -2
- package/out/template/TemplateParser.js +116 -89
- package/out/template/TemplateSchemaProviderMemory.js +3 -19
- 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
|
|
3
|
+
Parser and schema validator for **STXT**, an indentation-based structured-text language.
|
|
4
4
|
|
|
5
|
-
STXT is a plain-text
|
|
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)
|
package/out/core/InlineNode.js
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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;
|
package/out/core/Parser.d.ts
CHANGED
|
@@ -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
|
package/out/core/Parser.js
CHANGED
|
@@ -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
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
*
|
package/out/core/StringUtils.js
CHANGED
|
@@ -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.
|
|
@@ -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
|
-
|
|
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.
|
|
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 ===
|
|
183
|
-
schema =
|
|
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 ===
|
|
186
|
-
schema =
|
|
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
|
-
|
|
38
|
-
|
|
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 (
|
|
41
|
-
return this.schemaMeta.getSchema(
|
|
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
|
-
/**
|
|
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
|
|
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 (
|
|
35
|
+
if (key === Schema_1.Schema.TEMPLATE_NAMESPACE) {
|
|
35
36
|
return this.templateMeta.getSchema(key);
|
|
36
37
|
}
|
|
37
|
-
else if (
|
|
38
|
+
else if (key === Schema_1.Schema.SCHEMA_NAMESPACE) {
|
|
38
39
|
return this.schemaMeta.getSchema(key);
|
|
39
40
|
}
|
|
40
|
-
|
|
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 ===
|
|
57
|
-
this.
|
|
56
|
+
if (namespace === Schema_1.Schema.TEMPLATE_NAMESPACE) {
|
|
57
|
+
this.addNode(node, this.templateMeta, TemplateParser_1.transformTemplateNodeToSchema);
|
|
58
58
|
}
|
|
59
|
-
else if (namespace ===
|
|
60
|
-
this.
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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(
|
|
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
|
-
|
|
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 ??
|
|
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
|
}
|
package/out/schema/Schema.d.ts
CHANGED
|
@@ -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;
|
package/out/schema/Schema.js
CHANGED
|
@@ -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
|
|
48
|
-
if (this.nodes.has(
|
|
49
|
-
throw new ValidationException_1.ValidationException(
|
|
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(
|
|
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
|