@stxt-lang/core 0.14.1 → 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 +3 -3
- package/out/core/InlineNode.js +1 -0
- package/out/core/LineParser.js +1 -1
- 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 +22 -17
- package/out/core/StringUtils.d.ts +0 -7
- package/out/core/StringUtils.js +0 -10
- package/out/core/TextNode.d.ts +7 -0
- package/out/core/TextNode.js +11 -0
- 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/Formatter.d.ts +4 -4
- package/out/runtime/Formatter.js +15 -8
- package/out/runtime/NodeWriter.js +10 -2
- 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 -3
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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
|
-
- VSCode extension: [STXT
|
|
8
|
+
- VSCode extension: [STXT Language](https://marketplace.visualstudio.com/items?itemName=stxt-lang.stxt)
|
|
9
9
|
- Java implementation: [`dev.stxt:stxt-core`](https://central.sonatype.com/artifact/dev.stxt/stxt-core) on Maven Central
|
|
10
10
|
- Python implementation: [`stxt`](https://pypi.org/project/stxt/) on PyPI
|
|
11
11
|
|
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`);
|
package/out/core/LineParser.js
CHANGED
|
@@ -64,7 +64,7 @@ function parseLine(line, lastNodeBlock, lastLevel, numLine, validate = true) {
|
|
|
64
64
|
if (lastNodeBlock && level > lastLevel) {
|
|
65
65
|
const text = StringUtils_1.StringUtils.rightTrim(line.substring(pointer + 1));
|
|
66
66
|
// The prefix covering the block level must be homogeneous (spec 10.2, rule 2);
|
|
67
|
-
// empty lines are
|
|
67
|
+
// empty lines are never an error and are exempt from it (spec 10.3)
|
|
68
68
|
if (validate && sawSpace && sawTab && text.length > 0) {
|
|
69
69
|
throw new ParseException_1.ParseException(numLine, "INDENTATION_MIXED", `Mixed tabs and spaces in indentation`);
|
|
70
70
|
}
|
|
@@ -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,15 @@ 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();
|
|
258
|
+
// A closing block node drops its final empty lines (STXT-SPEC §10.3): they are not
|
|
259
|
+
// content, only visual separation or an editor's final line breaks. The validators
|
|
260
|
+
// and observers below already see the trimmed node; onTextLine did fire for these
|
|
261
|
+
// lines while the block was open, as process observation of the source.
|
|
262
|
+
if (completed instanceof TextNode_1.TextNode) {
|
|
263
|
+
completed.removeTrailingEmptyLines();
|
|
264
|
+
}
|
|
260
265
|
// Hand it over to the validators
|
|
261
266
|
this.validators.forEach(validator => {
|
|
262
267
|
try {
|
|
@@ -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.
|
package/out/core/TextNode.d.ts
CHANGED
|
@@ -56,6 +56,13 @@ export declare class TextNode extends Node {
|
|
|
56
56
|
addTextLine(line: string): void;
|
|
57
57
|
/** Removes every text line. */
|
|
58
58
|
clearText(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Removes the final empty lines (`""` elements at the end of the lines). The {@link Parser}
|
|
61
|
+
* calls it when the block closes (STXT-SPEC §10.3: the final empty lines of a block are not
|
|
62
|
+
* content); it is public because a programmatically built node may want the same
|
|
63
|
+
* normalization before writing.
|
|
64
|
+
*/
|
|
65
|
+
removeTrailingEmptyLines(): void;
|
|
59
66
|
getText(): string;
|
|
60
67
|
isTextNode(): boolean;
|
|
61
68
|
private static splitLines;
|
package/out/core/TextNode.js
CHANGED
|
@@ -61,6 +61,17 @@ class TextNode extends Node_1.Node {
|
|
|
61
61
|
clearText() {
|
|
62
62
|
this.lines.length = 0;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Removes the final empty lines (`""` elements at the end of the lines). The {@link Parser}
|
|
66
|
+
* calls it when the block closes (STXT-SPEC §10.3: the final empty lines of a block are not
|
|
67
|
+
* content); it is public because a programmatically built node may want the same
|
|
68
|
+
* normalization before writing.
|
|
69
|
+
*/
|
|
70
|
+
removeTrailingEmptyLines() {
|
|
71
|
+
while (this.lines.length > 0 && this.lines[this.lines.length - 1] === "") {
|
|
72
|
+
this.lines.pop();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
64
75
|
getText() {
|
|
65
76
|
return this.lines.join("\n");
|
|
66
77
|
}
|
|
@@ -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
|
|
@@ -33,10 +33,10 @@ export interface FormatResult {
|
|
|
33
33
|
* - A **text line of a block** gets the indentation of the block (its level plus one) in the
|
|
34
34
|
* requested style, followed by its content; any indentation the line had beyond the block's
|
|
35
35
|
* is content (STXT-SPEC §10.2, relative indentation is preserved) and is kept exactly. A
|
|
36
|
-
* blank line
|
|
37
|
-
* (STXT-SPEC §10.3), so it is written with the indentation of the block too: the
|
|
38
|
-
* as one piece
|
|
39
|
-
*
|
|
36
|
+
* blank line that precedes more block text is `""` in the content whatever it looks like in
|
|
37
|
+
* the source (STXT-SPEC §10.3), so it is written with the indentation of the block too: the
|
|
38
|
+
* block reads as one piece. The final blank lines of a block are not content (STXT-SPEC
|
|
39
|
+
* §10.3: the parser drops them when the block closes) and fall under the next rule.
|
|
40
40
|
* - Every **other line** — a comment, a blank line outside a block, or a line the parse tree
|
|
41
41
|
* does not describe because of a syntax error — is kept as the author wrote it, except that
|
|
42
42
|
* its trailing blanks are removed and the whole indentation units at its start are converted
|
package/out/runtime/Formatter.js
CHANGED
|
@@ -23,10 +23,10 @@ const NodeWriter_1 = require("./NodeWriter");
|
|
|
23
23
|
* - A **text line of a block** gets the indentation of the block (its level plus one) in the
|
|
24
24
|
* requested style, followed by its content; any indentation the line had beyond the block's
|
|
25
25
|
* is content (STXT-SPEC §10.2, relative indentation is preserved) and is kept exactly. A
|
|
26
|
-
* blank line
|
|
27
|
-
* (STXT-SPEC §10.3), so it is written with the indentation of the block too: the
|
|
28
|
-
* as one piece
|
|
29
|
-
*
|
|
26
|
+
* blank line that precedes more block text is `""` in the content whatever it looks like in
|
|
27
|
+
* the source (STXT-SPEC §10.3), so it is written with the indentation of the block too: the
|
|
28
|
+
* block reads as one piece. The final blank lines of a block are not content (STXT-SPEC
|
|
29
|
+
* §10.3: the parser drops them when the block closes) and fall under the next rule.
|
|
30
30
|
* - Every **other line** — a comment, a blank line outside a block, or a line the parse tree
|
|
31
31
|
* does not describe because of a syntax error — is kept as the author wrote it, except that
|
|
32
32
|
* its trailing blanks are removed and the whole indentation units at its start are converted
|
|
@@ -85,8 +85,11 @@ class Formatter {
|
|
|
85
85
|
if (node) {
|
|
86
86
|
return Formatter.renderNode(node, line, style);
|
|
87
87
|
}
|
|
88
|
+
// A final empty line of a block is not content (STXT-SPEC §10.3): the parser removed it
|
|
89
|
+
// from the node when the block closed, so its index falls beyond the logical lines. It
|
|
90
|
+
// is kept as any other line: blank, unindented.
|
|
88
91
|
const text = sourceLines.textAt(lineNumber);
|
|
89
|
-
if (text) {
|
|
92
|
+
if (text && text.index < text.node.getTextLines().length) {
|
|
90
93
|
return Formatter.indent(text.node.getLevel() + 1, style) + text.line.content;
|
|
91
94
|
}
|
|
92
95
|
return Formatter.convertUnits(StringUtils_1.StringUtils.rightTrim(line), style);
|
|
@@ -172,7 +175,10 @@ class SourceLines {
|
|
|
172
175
|
// Comment lines need no bookkeeping: every line that opens no node is treated alike.
|
|
173
176
|
}
|
|
174
177
|
onTextLine(node, lineNumber, lineString, line) {
|
|
175
|
-
|
|
178
|
+
// The line was just appended: its 0-based index in the block is the current last. After
|
|
179
|
+
// the block closes and drops its final empty lines (STXT-SPEC §10.3), an index beyond
|
|
180
|
+
// getTextLines() marks the line as a final empty line, not content.
|
|
181
|
+
this.textByLine.set(lineNumber, { node, line, index: node.getTextLines().length - 1 });
|
|
176
182
|
}
|
|
177
183
|
/**
|
|
178
184
|
* @param lineNumber line number, 1-indexed.
|
|
@@ -183,8 +189,9 @@ class SourceLines {
|
|
|
183
189
|
}
|
|
184
190
|
/**
|
|
185
191
|
* @param lineNumber line number, 1-indexed.
|
|
186
|
-
* @returns the block node this line is text of
|
|
187
|
-
*
|
|
192
|
+
* @returns the block node this line is text of, the line already split into indentation and
|
|
193
|
+
* content, and its 0-based index in the block; or undefined if the line is not text
|
|
194
|
+
* of a block.
|
|
188
195
|
*/
|
|
189
196
|
textAt(lineNumber) {
|
|
190
197
|
return this.textByLine.get(lineNumber);
|
|
@@ -59,9 +59,17 @@ class NodeWriter {
|
|
|
59
59
|
}
|
|
60
60
|
if (n instanceof TextNode_1.TextNode) {
|
|
61
61
|
out.push(" >>\n");
|
|
62
|
-
|
|
62
|
+
// Final empty lines are not emitted (STXT-TREE-SPEC 11.1 rule 6): parsing never
|
|
63
|
+
// produces them (STXT-SPEC 10.3), and on a programmatically built node they would
|
|
64
|
+
// not survive the round trip.
|
|
65
|
+
const lines = n.getTextLines();
|
|
66
|
+
let last = lines.length;
|
|
67
|
+
while (last > 0 && lines[last - 1] === "") {
|
|
68
|
+
last--;
|
|
69
|
+
}
|
|
70
|
+
for (let i = 0; i < last; i++) {
|
|
63
71
|
NodeWriter.indent(out, depth + 1, style);
|
|
64
|
-
out.push(
|
|
72
|
+
out.push(lines[i], "\n");
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
else if (n instanceof InlineNode_1.InlineNode) {
|
|
@@ -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() {
|