@stxt-lang/core 0.5.2 → 0.6.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 +116 -1
- package/out/all.d.ts +5 -0
- package/out/all.js +10 -4
- package/out/core/Constants.d.ts +8 -0
- package/out/core/Constants.js +8 -0
- package/out/core/Line.d.ts +20 -0
- package/out/core/Line.js +15 -0
- package/out/core/LineParser.d.ts +17 -0
- package/out/core/LineParser.js +27 -10
- package/out/core/NameNamespace.d.ts +9 -0
- package/out/core/NameNamespace.js +9 -0
- package/out/core/NameNamespaceParser.d.ts +11 -0
- package/out/core/NameNamespaceParser.js +16 -5
- package/out/core/NamespaceValidator.d.ts +18 -0
- package/out/core/NamespaceValidator.js +18 -0
- package/out/core/Node.d.ts +55 -0
- package/out/core/Node.js +56 -1
- package/out/core/NodeCreator.d.ts +11 -0
- package/out/core/NodeCreator.js +14 -3
- package/out/core/ParseResult.d.ts +26 -0
- package/out/core/ParseResult.js +26 -0
- package/out/core/Parser.d.ts +31 -0
- package/out/core/Parser.js +50 -19
- package/out/core/StringUtils.d.ts +31 -0
- package/out/core/StringUtils.js +40 -9
- package/out/discovery/DiscoveryEnvironment.d.ts +35 -0
- package/out/discovery/DiscoveryEnvironment.js +3 -0
- package/out/discovery/DiscoveryError.d.ts +30 -0
- package/out/discovery/DiscoveryError.js +36 -0
- package/out/discovery/DiscoveryFileSystem.d.ts +57 -0
- package/out/discovery/DiscoveryFileSystem.js +3 -0
- package/out/discovery/DiscoveryResolver.d.ts +74 -0
- package/out/discovery/DiscoveryResolver.js +232 -0
- package/out/discovery/DiscoveryResult.d.ts +94 -0
- package/out/discovery/DiscoveryResult.js +111 -0
- package/out/exceptions/ParseException.d.ts +14 -0
- package/out/exceptions/ParseException.js +12 -0
- package/out/exceptions/RuntimeException.d.ts +14 -0
- package/out/exceptions/RuntimeException.js +13 -0
- package/out/exceptions/ValidationException.d.ts +8 -0
- package/out/exceptions/ValidationException.js +8 -0
- package/out/processors/Observer.d.ts +29 -0
- package/out/processors/Validator.d.ts +9 -0
- package/out/runtime/ConditionalValidator.d.ts +15 -0
- package/out/runtime/ConditionalValidator.js +16 -2
- package/out/runtime/NodeWriter.d.ts +18 -0
- package/out/runtime/NodeWriter.js +18 -0
- package/out/runtime/UnifiedSchemaProvider.d.ts +23 -5
- package/out/runtime/UnifiedSchemaProvider.js +28 -10
- package/out/schema/ChildDefinition.d.ts +18 -0
- package/out/schema/ChildDefinition.js +18 -0
- package/out/schema/NodeDefinition.d.ts +44 -0
- package/out/schema/NodeDefinition.js +47 -3
- package/out/schema/Schema.d.ts +26 -0
- package/out/schema/Schema.js +26 -1
- package/out/schema/SchemaParser.d.ts +7 -0
- package/out/schema/SchemaParser.js +26 -16
- package/out/schema/SchemaProvider.d.ts +7 -0
- package/out/schema/SchemaProviderMemory.d.ts +27 -0
- package/out/schema/SchemaProviderMemory.js +27 -0
- package/out/schema/SchemaProviderMeta.d.ts +16 -0
- package/out/schema/SchemaProviderMeta.js +16 -0
- package/out/schema/SchemaValidator.d.ts +20 -0
- package/out/schema/SchemaValidator.js +27 -7
- package/out/schema/Type.d.ts +9 -0
- package/out/schema/TypeRegistry.d.ts +13 -0
- package/out/schema/TypeRegistry.js +18 -5
- package/out/schema/type/BASE64.d.ts +1 -0
- package/out/schema/type/BASE64.js +5 -4
- package/out/schema/type/BINARY.d.ts +1 -0
- package/out/schema/type/BINARY.js +2 -1
- package/out/schema/type/BLOCK.d.ts +1 -0
- package/out/schema/type/BLOCK.js +2 -1
- package/out/schema/type/BOOLEAN.d.ts +1 -0
- package/out/schema/type/BOOLEAN.js +1 -0
- package/out/schema/type/DATE.d.ts +1 -0
- package/out/schema/type/DATE.js +1 -0
- package/out/schema/type/EMAIL.d.ts +1 -0
- package/out/schema/type/EMAIL.js +1 -0
- package/out/schema/type/ENUM.d.ts +1 -0
- package/out/schema/type/ENUM.js +2 -1
- package/out/schema/type/GROUP.d.ts +1 -0
- package/out/schema/type/GROUP.js +2 -1
- package/out/schema/type/HEXADECIMAL.d.ts +1 -0
- package/out/schema/type/HEXADECIMAL.js +2 -1
- package/out/schema/type/INLINE.d.ts +1 -0
- package/out/schema/type/INLINE.js +2 -1
- package/out/schema/type/INTEGER.d.ts +1 -0
- package/out/schema/type/INTEGER.js +1 -0
- package/out/schema/type/MARKDOWN.d.ts +4 -0
- package/out/schema/type/MARKDOWN.js +4 -2
- package/out/schema/type/NATURAL.d.ts +1 -0
- package/out/schema/type/NATURAL.js +1 -0
- package/out/schema/type/NUMBER.d.ts +1 -0
- package/out/schema/type/NUMBER.js +1 -0
- package/out/schema/type/TEXT.d.ts +1 -0
- package/out/schema/type/TEXT.js +1 -0
- package/out/schema/type/TIME.d.ts +1 -0
- package/out/schema/type/TIME.js +1 -0
- package/out/schema/type/TIMESTAMP.d.ts +1 -0
- package/out/schema/type/TIMESTAMP.js +1 -0
- package/out/schema/type/URL.d.ts +1 -0
- package/out/schema/type/URL.js +2 -1
- package/out/schema/type/UUID.d.ts +1 -0
- package/out/schema/type/UUID.js +1 -0
- package/out/schema/type/binaryValue.d.ts +9 -0
- package/out/schema/type/binaryValue.js +9 -3
- package/out/schema/type/regexType.d.ts +8 -0
- package/out/schema/type/regexType.js +9 -1
- package/out/template/ChildLine.d.ts +14 -0
- package/out/template/ChildLine.js +14 -0
- package/out/template/ChildLineParser.d.ts +10 -0
- package/out/template/ChildLineParser.js +17 -8
- package/out/template/MetaTemplateSchemaProvider.d.ts +16 -0
- package/out/template/MetaTemplateSchemaProvider.js +17 -1
- package/out/template/TemplateParser.d.ts +8 -0
- package/out/template/TemplateParser.js +49 -39
- package/out/template/TemplateSchemaProviderMemory.d.ts +18 -0
- package/out/template/TemplateSchemaProviderMemory.js +21 -3
- package/package.json +2 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A resolution error (STXT-DISCOVERY-SPEC section 8).
|
|
3
|
+
*
|
|
4
|
+
* Resolution errors are collected, not thrown: the spec mandates reporting them while
|
|
5
|
+
* allowing the tool to keep loading the remaining definitions, so a resolve pass returns
|
|
6
|
+
* every error it found instead of aborting at the first one.
|
|
7
|
+
*/
|
|
8
|
+
export declare class DiscoveryError {
|
|
9
|
+
readonly code: string;
|
|
10
|
+
readonly file: string;
|
|
11
|
+
readonly message: string;
|
|
12
|
+
readonly namespace?: string | undefined;
|
|
13
|
+
/** Two definitions for the same target namespace at the same level (spec 8.1). */
|
|
14
|
+
static readonly DUPLICATE_NAMESPACE = "DISCOVERY_DUPLICATE_NAMESPACE";
|
|
15
|
+
/** A file under a resolution directory that does not parse as STXT (spec 8.2). */
|
|
16
|
+
static readonly NOT_PARSEABLE = "DISCOVERY_NOT_PARSEABLE";
|
|
17
|
+
/** A file whose root node belongs neither to @stxt.schema nor to @stxt.template (spec 8.3). */
|
|
18
|
+
static readonly NOT_A_DEFINITION = "DISCOVERY_NOT_A_DEFINITION";
|
|
19
|
+
/** A definition that does not validate against its meta-schema (spec 8.4). */
|
|
20
|
+
static readonly INVALID_DEFINITION = "DISCOVERY_INVALID_DEFINITION";
|
|
21
|
+
/**
|
|
22
|
+
* Creates a resolution error.
|
|
23
|
+
*
|
|
24
|
+
* @param code one of the `DISCOVERY_*` constants of this class.
|
|
25
|
+
* @param file full path of the offending file.
|
|
26
|
+
* @param message human-readable description of the error.
|
|
27
|
+
* @param namespace target namespace involved, when the error is about a namespace.
|
|
28
|
+
*/
|
|
29
|
+
constructor(code: string, file: string, message: string, namespace?: string | undefined);
|
|
30
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DiscoveryError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A resolution error (STXT-DISCOVERY-SPEC section 8).
|
|
6
|
+
*
|
|
7
|
+
* Resolution errors are collected, not thrown: the spec mandates reporting them while
|
|
8
|
+
* allowing the tool to keep loading the remaining definitions, so a resolve pass returns
|
|
9
|
+
* every error it found instead of aborting at the first one.
|
|
10
|
+
*/
|
|
11
|
+
class DiscoveryError {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a resolution error.
|
|
14
|
+
*
|
|
15
|
+
* @param code one of the `DISCOVERY_*` constants of this class.
|
|
16
|
+
* @param file full path of the offending file.
|
|
17
|
+
* @param message human-readable description of the error.
|
|
18
|
+
* @param namespace target namespace involved, when the error is about a namespace.
|
|
19
|
+
*/
|
|
20
|
+
constructor(code, file, message, namespace) {
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.file = file;
|
|
23
|
+
this.message = message;
|
|
24
|
+
this.namespace = namespace;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.DiscoveryError = DiscoveryError;
|
|
28
|
+
/** Two definitions for the same target namespace at the same level (spec 8.1). */
|
|
29
|
+
DiscoveryError.DUPLICATE_NAMESPACE = "DISCOVERY_DUPLICATE_NAMESPACE";
|
|
30
|
+
/** A file under a resolution directory that does not parse as STXT (spec 8.2). */
|
|
31
|
+
DiscoveryError.NOT_PARSEABLE = "DISCOVERY_NOT_PARSEABLE";
|
|
32
|
+
/** A file whose root node belongs neither to @stxt.schema nor to @stxt.template (spec 8.3). */
|
|
33
|
+
DiscoveryError.NOT_A_DEFINITION = "DISCOVERY_NOT_A_DEFINITION";
|
|
34
|
+
/** A definition that does not validate against its meta-schema (spec 8.4). */
|
|
35
|
+
DiscoveryError.INVALID_DEFINITION = "DISCOVERY_INVALID_DEFINITION";
|
|
36
|
+
//# sourceMappingURL=DiscoveryError.js.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An entry of a directory listing, as returned by {@link DiscoveryFileSystem.listDirectory}.
|
|
3
|
+
*/
|
|
4
|
+
export interface DiscoveryEntry {
|
|
5
|
+
/** Full path of the entry, in the same form the file system uses for every other path. */
|
|
6
|
+
path: string;
|
|
7
|
+
/** Base name of the entry (last path segment). */
|
|
8
|
+
name: string;
|
|
9
|
+
/** True if the entry is a directory. */
|
|
10
|
+
isDirectory: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Minimal file-system abstraction used by {@link DiscoveryResolver} (STXT-DISCOVERY-SPEC).
|
|
14
|
+
*
|
|
15
|
+
* The resolver treats paths as opaque strings: it never parses or concatenates them itself,
|
|
16
|
+
* so an implementation may back them with plain OS paths (Node `fs`), editor URIs
|
|
17
|
+
* (`vscode.workspace.fs`) or an in-memory tree for tests. All paths returned by an
|
|
18
|
+
* implementation must be canonical enough that string equality means "same directory".
|
|
19
|
+
*/
|
|
20
|
+
export interface DiscoveryFileSystem {
|
|
21
|
+
/**
|
|
22
|
+
* Whether a path exists and is a directory.
|
|
23
|
+
*
|
|
24
|
+
* @param path path to check.
|
|
25
|
+
* @returns true if the path is an existing directory; false otherwise (including I/O errors).
|
|
26
|
+
*/
|
|
27
|
+
isDirectory(path: string): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Lists the immediate entries of a directory.
|
|
30
|
+
*
|
|
31
|
+
* @param path directory to list.
|
|
32
|
+
* @returns the entries of the directory, in any order.
|
|
33
|
+
*/
|
|
34
|
+
listDirectory(path: string): Promise<DiscoveryEntry[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Reads a file as UTF-8 text.
|
|
37
|
+
*
|
|
38
|
+
* @param path file to read.
|
|
39
|
+
* @returns the text content of the file.
|
|
40
|
+
*/
|
|
41
|
+
readFile(path: string): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Returns the parent directory of a path, or null when the path is the file-system root.
|
|
44
|
+
*
|
|
45
|
+
* @param path path whose parent is wanted.
|
|
46
|
+
* @returns the parent path, or null at the root.
|
|
47
|
+
*/
|
|
48
|
+
parentOf(path: string): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Joins a directory path and a child name.
|
|
51
|
+
*
|
|
52
|
+
* @param path base directory.
|
|
53
|
+
* @param name child segment to append.
|
|
54
|
+
* @returns the joined path.
|
|
55
|
+
*/
|
|
56
|
+
join(path: string, name: string): string;
|
|
57
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { DiscoveryEnvironment } from "./DiscoveryEnvironment";
|
|
2
|
+
import { DiscoveryFileSystem } from "./DiscoveryFileSystem";
|
|
3
|
+
import { DiscoveryResult } from "./DiscoveryResult";
|
|
4
|
+
/** Options of a {@link DiscoveryResolver}. */
|
|
5
|
+
export interface DiscoveryOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Maximum number of ancestor directories examined during the project-level ascent.
|
|
8
|
+
* A safeguard against pathological paths (circular links, virtual file systems), as
|
|
9
|
+
* allowed by STXT-DISCOVERY-SPEC section 4.1. Defaults to 32.
|
|
10
|
+
*/
|
|
11
|
+
maxAscent?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Reference implementation of STXT-DISCOVERY-SPEC: builds the resolution chain of a
|
|
15
|
+
* document (project ascent, user level, system level, or the `STXT_PATH` override), loads
|
|
16
|
+
* every definition of every level and applies the per-namespace precedence.
|
|
17
|
+
*
|
|
18
|
+
* The resolver is host-agnostic: all file-system and environment access goes through the
|
|
19
|
+
* injected {@link DiscoveryFileSystem} and {@link DiscoveryEnvironment}, so the same logic
|
|
20
|
+
* serves a command line (Node `fs`), an editor (`vscode.workspace.fs`) or a test (an
|
|
21
|
+
* in-memory tree).
|
|
22
|
+
*
|
|
23
|
+
* Loaded levels are cached by directory: resolving many documents that share levels loads
|
|
24
|
+
* each directory once, which is the sharing that STXT-DISCOVERY-SPEC section 7 allows —
|
|
25
|
+
* a level's content does not depend on which document is being resolved. Call
|
|
26
|
+
* {@link clearCache} when the underlying files may have changed.
|
|
27
|
+
*/
|
|
28
|
+
export declare class DiscoveryResolver {
|
|
29
|
+
private readonly fs;
|
|
30
|
+
private readonly env;
|
|
31
|
+
private readonly maxAscent;
|
|
32
|
+
private readonly schemaMeta;
|
|
33
|
+
private readonly templateMeta;
|
|
34
|
+
private readonly levelCache;
|
|
35
|
+
/**
|
|
36
|
+
* Creates a resolver.
|
|
37
|
+
*
|
|
38
|
+
* @param fs file-system access.
|
|
39
|
+
* @param env environment access (`STXT_PATH`, user and system directories).
|
|
40
|
+
* @param options optional settings.
|
|
41
|
+
*/
|
|
42
|
+
constructor(fs: DiscoveryFileSystem, env: DiscoveryEnvironment, options?: DiscoveryOptions);
|
|
43
|
+
/**
|
|
44
|
+
* Builds the resolution chain of a document (STXT-DISCOVERY-SPEC sections 4 and 6)
|
|
45
|
+
* without loading any definition.
|
|
46
|
+
*
|
|
47
|
+
* @param documentDir directory containing the document, or null for a document with no
|
|
48
|
+
* file-system location (standard input, an unsaved buffer), whose chain starts
|
|
49
|
+
* at the user level.
|
|
50
|
+
* @returns the existing resolution directories, highest precedence first.
|
|
51
|
+
*/
|
|
52
|
+
resolveChain(documentDir: string | null): Promise<string[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the definitions applicable to a document: builds its chain, loads every
|
|
55
|
+
* level (from the cache when already loaded) and returns the result with the
|
|
56
|
+
* per-namespace precedence applied.
|
|
57
|
+
*
|
|
58
|
+
* @param documentDir directory containing the document, or null for a document with no
|
|
59
|
+
* file-system location.
|
|
60
|
+
* @returns the resolution result, usable directly as a `SchemaProvider`.
|
|
61
|
+
*/
|
|
62
|
+
resolve(documentDir: string | null): Promise<DiscoveryResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Empties the level cache, so that the next resolve re-reads every directory. Call it
|
|
65
|
+
* when the definition files may have changed (e.g. from a file watcher).
|
|
66
|
+
*/
|
|
67
|
+
clearCache(): void;
|
|
68
|
+
private existingUnique;
|
|
69
|
+
private loadLevel;
|
|
70
|
+
private collectFiles;
|
|
71
|
+
private loadFile;
|
|
72
|
+
private loadRootNode;
|
|
73
|
+
private compile;
|
|
74
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DiscoveryResolver = void 0;
|
|
4
|
+
const Parser_1 = require("../core/Parser");
|
|
5
|
+
const StringUtils_1 = require("../core/StringUtils");
|
|
6
|
+
const ParseException_1 = require("../exceptions/ParseException");
|
|
7
|
+
const SchemaProviderMeta_1 = require("../schema/SchemaProviderMeta");
|
|
8
|
+
const SchemaValidator_1 = require("../schema/SchemaValidator");
|
|
9
|
+
const SchemaParser_1 = require("../schema/SchemaParser");
|
|
10
|
+
const MetaTemplateSchemaProvider_1 = require("../template/MetaTemplateSchemaProvider");
|
|
11
|
+
const TemplateParser_1 = require("../template/TemplateParser");
|
|
12
|
+
const DiscoveryError_1 = require("./DiscoveryError");
|
|
13
|
+
const DiscoveryResult_1 = require("./DiscoveryResult");
|
|
14
|
+
/** Name of the resolution directories (STXT-DISCOVERY-SPEC section 3). */
|
|
15
|
+
const STXT_DIR = ".stxt";
|
|
16
|
+
/** File extension of STXT documents. */
|
|
17
|
+
const STXT_EXTENSION = ".stxt";
|
|
18
|
+
/** Default value of {@link DiscoveryOptions.maxAscent}. */
|
|
19
|
+
const DEFAULT_MAX_ASCENT = 32;
|
|
20
|
+
/**
|
|
21
|
+
* Reference implementation of STXT-DISCOVERY-SPEC: builds the resolution chain of a
|
|
22
|
+
* document (project ascent, user level, system level, or the `STXT_PATH` override), loads
|
|
23
|
+
* every definition of every level and applies the per-namespace precedence.
|
|
24
|
+
*
|
|
25
|
+
* The resolver is host-agnostic: all file-system and environment access goes through the
|
|
26
|
+
* injected {@link DiscoveryFileSystem} and {@link DiscoveryEnvironment}, so the same logic
|
|
27
|
+
* serves a command line (Node `fs`), an editor (`vscode.workspace.fs`) or a test (an
|
|
28
|
+
* in-memory tree).
|
|
29
|
+
*
|
|
30
|
+
* Loaded levels are cached by directory: resolving many documents that share levels loads
|
|
31
|
+
* each directory once, which is the sharing that STXT-DISCOVERY-SPEC section 7 allows —
|
|
32
|
+
* a level's content does not depend on which document is being resolved. Call
|
|
33
|
+
* {@link clearCache} when the underlying files may have changed.
|
|
34
|
+
*/
|
|
35
|
+
class DiscoveryResolver {
|
|
36
|
+
/**
|
|
37
|
+
* Creates a resolver.
|
|
38
|
+
*
|
|
39
|
+
* @param fs file-system access.
|
|
40
|
+
* @param env environment access (`STXT_PATH`, user and system directories).
|
|
41
|
+
* @param options optional settings.
|
|
42
|
+
*/
|
|
43
|
+
constructor(fs, env, options) {
|
|
44
|
+
this.fs = fs;
|
|
45
|
+
this.env = env;
|
|
46
|
+
this.schemaMeta = new SchemaProviderMeta_1.SchemaProviderMeta();
|
|
47
|
+
this.templateMeta = new MetaTemplateSchemaProvider_1.MetaTemplateSchemaProvider();
|
|
48
|
+
this.levelCache = new Map();
|
|
49
|
+
this.maxAscent = options?.maxAscent ?? DEFAULT_MAX_ASCENT;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Builds the resolution chain of a document (STXT-DISCOVERY-SPEC sections 4 and 6)
|
|
53
|
+
* without loading any definition.
|
|
54
|
+
*
|
|
55
|
+
* @param documentDir directory containing the document, or null for a document with no
|
|
56
|
+
* file-system location (standard input, an unsaved buffer), whose chain starts
|
|
57
|
+
* at the user level.
|
|
58
|
+
* @returns the existing resolution directories, highest precedence first.
|
|
59
|
+
*/
|
|
60
|
+
async resolveChain(documentDir) {
|
|
61
|
+
// STXT_PATH, when defined, completely replaces the chain (spec section 6).
|
|
62
|
+
const stxtPath = this.env.getStxtPath();
|
|
63
|
+
if (stxtPath !== null) {
|
|
64
|
+
return this.existingUnique(stxtPath);
|
|
65
|
+
}
|
|
66
|
+
const chain = [];
|
|
67
|
+
// Project level: every .stxt directory from the document's directory upward.
|
|
68
|
+
if (documentDir !== null) {
|
|
69
|
+
let dir = documentDir;
|
|
70
|
+
for (let level = 0; level < this.maxAscent && dir !== null; level++) {
|
|
71
|
+
const candidate = this.fs.join(dir, STXT_DIR);
|
|
72
|
+
if (await this.fs.isDirectory(candidate)) {
|
|
73
|
+
chain.push(candidate);
|
|
74
|
+
}
|
|
75
|
+
dir = this.fs.parentOf(dir);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// User and system levels. The ascent may have reached them already (a document
|
|
79
|
+
// under the user's home finds $HOME/.stxt as a project candidate): deduplicate.
|
|
80
|
+
const userDir = this.env.getUserLevelDir();
|
|
81
|
+
const systemDir = this.env.getSystemLevelDir();
|
|
82
|
+
for (const dir of [userDir, systemDir]) {
|
|
83
|
+
if (dir !== null && !chain.includes(dir) && await this.fs.isDirectory(dir)) {
|
|
84
|
+
chain.push(dir);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return chain;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Resolves the definitions applicable to a document: builds its chain, loads every
|
|
91
|
+
* level (from the cache when already loaded) and returns the result with the
|
|
92
|
+
* per-namespace precedence applied.
|
|
93
|
+
*
|
|
94
|
+
* @param documentDir directory containing the document, or null for a document with no
|
|
95
|
+
* file-system location.
|
|
96
|
+
* @returns the resolution result, usable directly as a `SchemaProvider`.
|
|
97
|
+
*/
|
|
98
|
+
async resolve(documentDir) {
|
|
99
|
+
const chain = await this.resolveChain(documentDir);
|
|
100
|
+
const levels = [];
|
|
101
|
+
for (const dir of chain) {
|
|
102
|
+
levels.push(await this.loadLevel(dir));
|
|
103
|
+
}
|
|
104
|
+
return new DiscoveryResult_1.DiscoveryResult(levels, this.schemaMeta, this.templateMeta);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Empties the level cache, so that the next resolve re-reads every directory. Call it
|
|
108
|
+
* when the definition files may have changed (e.g. from a file watcher).
|
|
109
|
+
*/
|
|
110
|
+
clearCache() {
|
|
111
|
+
this.levelCache.clear();
|
|
112
|
+
}
|
|
113
|
+
// Filters a list of directories down to the existing ones, removing duplicates.
|
|
114
|
+
async existingUnique(dirs) {
|
|
115
|
+
const result = [];
|
|
116
|
+
for (const dir of dirs) {
|
|
117
|
+
if (!result.includes(dir) && await this.fs.isDirectory(dir)) {
|
|
118
|
+
result.push(dir);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
// Loads a resolution directory (or returns it from the cache): every file under it,
|
|
124
|
+
// recursively, with the level-local duplicate detection of spec section 5.
|
|
125
|
+
async loadLevel(dir) {
|
|
126
|
+
const cached = this.levelCache.get(dir);
|
|
127
|
+
if (cached) {
|
|
128
|
+
return cached;
|
|
129
|
+
}
|
|
130
|
+
const level = { dir, definitions: new Map(), errors: [] };
|
|
131
|
+
const conflicted = new Set();
|
|
132
|
+
for (const file of await this.collectFiles(dir)) {
|
|
133
|
+
await this.loadFile(file, level, conflicted);
|
|
134
|
+
}
|
|
135
|
+
this.levelCache.set(dir, level);
|
|
136
|
+
return level;
|
|
137
|
+
}
|
|
138
|
+
// Collects every file under a directory, recursively, sorted by path so that results
|
|
139
|
+
// and error messages do not depend on the listing order of the file system.
|
|
140
|
+
async collectFiles(dir) {
|
|
141
|
+
const files = [];
|
|
142
|
+
const entries = [...await this.fs.listDirectory(dir)].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
143
|
+
for (const entry of entries) {
|
|
144
|
+
if (entry.isDirectory) {
|
|
145
|
+
files.push(...await this.collectFiles(entry.path));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
files.push(entry.path);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return files;
|
|
152
|
+
}
|
|
153
|
+
// Loads one file of a level: parses it and registers every root as a definition,
|
|
154
|
+
// reporting the errors of spec section 8.
|
|
155
|
+
async loadFile(file, level, conflicted) {
|
|
156
|
+
// Spec section 3: every file under a resolution directory must be a definition.
|
|
157
|
+
if (!file.endsWith(STXT_EXTENSION)) {
|
|
158
|
+
level.errors.push(new DiscoveryError_1.DiscoveryError(DiscoveryError_1.DiscoveryError.NOT_A_DEFINITION, file, `Not an STXT definition file: ${file}`));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
let nodes;
|
|
162
|
+
try {
|
|
163
|
+
nodes = new Parser_1.Parser().parse(await this.fs.readFile(file));
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
level.errors.push(new DiscoveryError_1.DiscoveryError(DiscoveryError_1.DiscoveryError.NOT_PARSEABLE, file, `Cannot parse ${file}: ${e instanceof Error ? e.message : String(e)}`));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (nodes.length === 0) {
|
|
170
|
+
level.errors.push(new DiscoveryError_1.DiscoveryError(DiscoveryError_1.DiscoveryError.NOT_A_DEFINITION, file, `Empty document, not a definition: ${file}`));
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
for (const node of nodes) {
|
|
174
|
+
this.loadRootNode(node, file, level, conflicted);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Validates one root node against its meta-schema, compiles it to a schema and
|
|
178
|
+
// registers it in the level, detecting same-level duplicates.
|
|
179
|
+
loadRootNode(node, file, level, conflicted) {
|
|
180
|
+
const namespace = node.getNamespace();
|
|
181
|
+
let schema;
|
|
182
|
+
try {
|
|
183
|
+
if (namespace === "@stxt.template") {
|
|
184
|
+
schema = this.compile(node, this.templateMeta, TemplateParser_1.transformTemplateNodeToSchema);
|
|
185
|
+
}
|
|
186
|
+
else if (namespace === "@stxt.schema") {
|
|
187
|
+
schema = this.compile(node, this.schemaMeta, SchemaParser_1.transformNodeToSchema);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
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}`));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
const message = e instanceof ParseException_1.ParseException ? `[${e.code}] ${e.message}` : String(e);
|
|
196
|
+
level.errors.push(new DiscoveryError_1.DiscoveryError(DiscoveryError_1.DiscoveryError.INVALID_DEFINITION, file, `Invalid definition in ${file}: ${message}`));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const key = StringUtils_1.StringUtils.lowerCase(schema.getNamespace());
|
|
200
|
+
const existing = level.definitions.get(key);
|
|
201
|
+
// Spec section 8: on a same-level duplicate, never silently pick one of the
|
|
202
|
+
// definitions — the namespace has no active definition while the conflict exists.
|
|
203
|
+
if (conflicted.has(key) || existing) {
|
|
204
|
+
if (existing) {
|
|
205
|
+
level.definitions.delete(key);
|
|
206
|
+
conflicted.add(key);
|
|
207
|
+
}
|
|
208
|
+
const firstFile = existing ? existing.file : "another file of this level";
|
|
209
|
+
level.errors.push(new DiscoveryError_1.DiscoveryError(DiscoveryError_1.DiscoveryError.DUPLICATE_NAMESPACE, file, `Duplicate definition for namespace '${schema.getNamespace()}' at level ${level.dir}: ` +
|
|
210
|
+
`already defined in ${firstFile}`, schema.getNamespace()));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const definition = {
|
|
214
|
+
namespace: schema.getNamespace(),
|
|
215
|
+
schema,
|
|
216
|
+
file,
|
|
217
|
+
levelDir: level.dir,
|
|
218
|
+
};
|
|
219
|
+
level.definitions.set(key, definition);
|
|
220
|
+
}
|
|
221
|
+
// Validates a root node against a meta-schema and transforms it into a Schema,
|
|
222
|
+
// throwing the first validation error (same policy as UnifiedSchemaProvider).
|
|
223
|
+
compile(node, meta, transform) {
|
|
224
|
+
const errors = new SchemaValidator_1.SchemaValidator(meta, true).validate(node);
|
|
225
|
+
if (errors.length > 0) {
|
|
226
|
+
throw errors[0];
|
|
227
|
+
}
|
|
228
|
+
return transform(node);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
exports.DiscoveryResolver = DiscoveryResolver;
|
|
232
|
+
//# sourceMappingURL=DiscoveryResolver.js.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Schema } from "../schema/Schema";
|
|
2
|
+
import { SchemaProvider } from "../schema/SchemaProvider";
|
|
3
|
+
import { DiscoveryError } from "./DiscoveryError";
|
|
4
|
+
/**
|
|
5
|
+
* An active definition: a schema or template that won the per-namespace precedence for a
|
|
6
|
+
* document's resolution chain, together with where it came from.
|
|
7
|
+
*/
|
|
8
|
+
export interface DiscoveryDefinition {
|
|
9
|
+
/** Target namespace of the definition, as written in the definition document. */
|
|
10
|
+
namespace: string;
|
|
11
|
+
/** The compiled schema (templates are compiled to schemas at load time). */
|
|
12
|
+
schema: Schema;
|
|
13
|
+
/** Full path of the file the definition was read from. */
|
|
14
|
+
file: string;
|
|
15
|
+
/** Resolution directory (level) the file belongs to. */
|
|
16
|
+
levelDir: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A loaded resolution directory: its definitions indexed by lowercased target namespace.
|
|
20
|
+
* Namespaces in conflict inside the level (spec 8.1) are excluded from the map.
|
|
21
|
+
*/
|
|
22
|
+
export interface DiscoveryLevel {
|
|
23
|
+
/** Full path of the resolution directory. */
|
|
24
|
+
dir: string;
|
|
25
|
+
/** Definitions of the level by lowercased target namespace, conflicts excluded. */
|
|
26
|
+
definitions: Map<string, DiscoveryDefinition>;
|
|
27
|
+
/** Resolution errors found while loading this level. */
|
|
28
|
+
errors: DiscoveryError[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The outcome of resolving a document's definitions (STXT-DISCOVERY-SPEC): the chain of
|
|
32
|
+
* levels, the active definition per namespace (nearest level wins) and every resolution
|
|
33
|
+
* error found along the way.
|
|
34
|
+
*
|
|
35
|
+
* It implements {@link SchemaProvider}, so it can be handed directly to a
|
|
36
|
+
* `SchemaValidator`/`ConditionalValidator` to validate the document it was resolved for.
|
|
37
|
+
* Like `UnifiedSchemaProvider`, it serves the meta-schemas of the two reserved namespaces
|
|
38
|
+
* itself, so schema and template documents also validate against it.
|
|
39
|
+
*/
|
|
40
|
+
export declare class DiscoveryResult implements SchemaProvider {
|
|
41
|
+
private readonly levels;
|
|
42
|
+
private readonly schemaMeta;
|
|
43
|
+
private readonly templateMeta;
|
|
44
|
+
/**
|
|
45
|
+
* Creates a result. Built by {@link DiscoveryResolver}; not meant to be constructed
|
|
46
|
+
* directly.
|
|
47
|
+
*
|
|
48
|
+
* @param levels loaded levels of the chain, highest precedence first.
|
|
49
|
+
* @param schemaMeta provider of the @stxt.schema meta-schema.
|
|
50
|
+
* @param templateMeta provider of the @stxt.template meta-schema.
|
|
51
|
+
*/
|
|
52
|
+
constructor(levels: ReadonlyArray<DiscoveryLevel>, schemaMeta: SchemaProvider, templateMeta: SchemaProvider);
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the schema that applies to a namespace: the meta-schemas for the two
|
|
55
|
+
* reserved namespaces, and otherwise the active definition of the nearest level.
|
|
56
|
+
*
|
|
57
|
+
* @param namespace namespace whose schema is wanted.
|
|
58
|
+
* @returns the schema of the namespace, or null if the chain has no definition for it.
|
|
59
|
+
*/
|
|
60
|
+
getSchema(namespace: string): Schema | null | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* The active definition of a namespace: the one from the nearest level that defines it
|
|
63
|
+
* (STXT-DISCOVERY-SPEC section 5), with its provenance.
|
|
64
|
+
*
|
|
65
|
+
* @param namespace namespace whose definition is wanted.
|
|
66
|
+
* @returns the active definition, or undefined if the chain has none for the namespace.
|
|
67
|
+
*/
|
|
68
|
+
getDefinition(namespace: string): DiscoveryDefinition | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Every active definition of the chain, with per-namespace precedence already applied:
|
|
71
|
+
* one entry per namespace, from its nearest defining level.
|
|
72
|
+
*
|
|
73
|
+
* @returns the active definitions, ordered by level (nearest level's definitions first).
|
|
74
|
+
*/
|
|
75
|
+
getActiveDefinitions(): ReadonlyArray<DiscoveryDefinition>;
|
|
76
|
+
/**
|
|
77
|
+
* Every active schema of the chain (the schemas of {@link getActiveDefinitions}).
|
|
78
|
+
*
|
|
79
|
+
* @returns the active schemas, ordered by level (nearest level's schemas first).
|
|
80
|
+
*/
|
|
81
|
+
getAllSchemas(): ReadonlyArray<Schema>;
|
|
82
|
+
/**
|
|
83
|
+
* The resolution chain: the loaded level directories, highest precedence first.
|
|
84
|
+
*
|
|
85
|
+
* @returns the directories of the chain, in precedence order.
|
|
86
|
+
*/
|
|
87
|
+
getChain(): ReadonlyArray<string>;
|
|
88
|
+
/**
|
|
89
|
+
* Every resolution error found while loading the chain (STXT-DISCOVERY-SPEC section 8).
|
|
90
|
+
*
|
|
91
|
+
* @returns the errors, ordered by level and then by file.
|
|
92
|
+
*/
|
|
93
|
+
getErrors(): ReadonlyArray<DiscoveryError>;
|
|
94
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DiscoveryResult = void 0;
|
|
4
|
+
const StringUtils_1 = require("../core/StringUtils");
|
|
5
|
+
/**
|
|
6
|
+
* The outcome of resolving a document's definitions (STXT-DISCOVERY-SPEC): the chain of
|
|
7
|
+
* levels, the active definition per namespace (nearest level wins) and every resolution
|
|
8
|
+
* error found along the way.
|
|
9
|
+
*
|
|
10
|
+
* It implements {@link SchemaProvider}, so it can be handed directly to a
|
|
11
|
+
* `SchemaValidator`/`ConditionalValidator` to validate the document it was resolved for.
|
|
12
|
+
* Like `UnifiedSchemaProvider`, it serves the meta-schemas of the two reserved namespaces
|
|
13
|
+
* itself, so schema and template documents also validate against it.
|
|
14
|
+
*/
|
|
15
|
+
class DiscoveryResult {
|
|
16
|
+
/**
|
|
17
|
+
* Creates a result. Built by {@link DiscoveryResolver}; not meant to be constructed
|
|
18
|
+
* directly.
|
|
19
|
+
*
|
|
20
|
+
* @param levels loaded levels of the chain, highest precedence first.
|
|
21
|
+
* @param schemaMeta provider of the @stxt.schema meta-schema.
|
|
22
|
+
* @param templateMeta provider of the @stxt.template meta-schema.
|
|
23
|
+
*/
|
|
24
|
+
constructor(levels, schemaMeta, templateMeta) {
|
|
25
|
+
this.levels = levels;
|
|
26
|
+
this.schemaMeta = schemaMeta;
|
|
27
|
+
this.templateMeta = templateMeta;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolves the schema that applies to a namespace: the meta-schemas for the two
|
|
31
|
+
* reserved namespaces, and otherwise the active definition of the nearest level.
|
|
32
|
+
*
|
|
33
|
+
* @param namespace namespace whose schema is wanted.
|
|
34
|
+
* @returns the schema of the namespace, or null if the chain has no definition for it.
|
|
35
|
+
*/
|
|
36
|
+
getSchema(namespace) {
|
|
37
|
+
if (namespace === "@stxt.template") {
|
|
38
|
+
return this.templateMeta.getSchema(namespace);
|
|
39
|
+
}
|
|
40
|
+
else if (namespace === "@stxt.schema") {
|
|
41
|
+
return this.schemaMeta.getSchema(namespace);
|
|
42
|
+
}
|
|
43
|
+
return this.getDefinition(namespace)?.schema ?? null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The active definition of a namespace: the one from the nearest level that defines it
|
|
47
|
+
* (STXT-DISCOVERY-SPEC section 5), with its provenance.
|
|
48
|
+
*
|
|
49
|
+
* @param namespace namespace whose definition is wanted.
|
|
50
|
+
* @returns the active definition, or undefined if the chain has none for the namespace.
|
|
51
|
+
*/
|
|
52
|
+
getDefinition(namespace) {
|
|
53
|
+
const key = StringUtils_1.StringUtils.lowerCase(namespace);
|
|
54
|
+
for (const level of this.levels) {
|
|
55
|
+
const definition = level.definitions.get(key);
|
|
56
|
+
if (definition) {
|
|
57
|
+
return definition;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Every active definition of the chain, with per-namespace precedence already applied:
|
|
64
|
+
* one entry per namespace, from its nearest defining level.
|
|
65
|
+
*
|
|
66
|
+
* @returns the active definitions, ordered by level (nearest level's definitions first).
|
|
67
|
+
*/
|
|
68
|
+
getActiveDefinitions() {
|
|
69
|
+
const seen = new Set();
|
|
70
|
+
const result = [];
|
|
71
|
+
for (const level of this.levels) {
|
|
72
|
+
for (const [key, definition] of level.definitions) {
|
|
73
|
+
if (!seen.has(key)) {
|
|
74
|
+
seen.add(key);
|
|
75
|
+
result.push(definition);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Every active schema of the chain (the schemas of {@link getActiveDefinitions}).
|
|
83
|
+
*
|
|
84
|
+
* @returns the active schemas, ordered by level (nearest level's schemas first).
|
|
85
|
+
*/
|
|
86
|
+
getAllSchemas() {
|
|
87
|
+
return this.getActiveDefinitions().map(definition => definition.schema);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The resolution chain: the loaded level directories, highest precedence first.
|
|
91
|
+
*
|
|
92
|
+
* @returns the directories of the chain, in precedence order.
|
|
93
|
+
*/
|
|
94
|
+
getChain() {
|
|
95
|
+
return this.levels.map(level => level.dir);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Every resolution error found while loading the chain (STXT-DISCOVERY-SPEC section 8).
|
|
99
|
+
*
|
|
100
|
+
* @returns the errors, ordered by level and then by file.
|
|
101
|
+
*/
|
|
102
|
+
getErrors() {
|
|
103
|
+
const result = [];
|
|
104
|
+
for (const level of this.levels) {
|
|
105
|
+
result.push(...level.errors);
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.DiscoveryResult = DiscoveryResult;
|
|
111
|
+
//# sourceMappingURL=DiscoveryResult.js.map
|
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syntax error detected while parsing (lexical/structural phase, not schema). Every exception
|
|
3
|
+
* carries an UPPERCASE code and the line of the document where it was detected.
|
|
4
|
+
*/
|
|
1
5
|
export declare class ParseException extends Error {
|
|
6
|
+
/** Line number of the document where the error was detected. */
|
|
2
7
|
readonly line: number;
|
|
8
|
+
/** Error code in UPPERCASE (e.g. `INVALID_LINE`). */
|
|
3
9
|
readonly code: string;
|
|
10
|
+
/**
|
|
11
|
+
* Creates a syntax error located at a line of the document.
|
|
12
|
+
*
|
|
13
|
+
* @param line line number where the error was detected.
|
|
14
|
+
* @param code error code in UPPERCASE.
|
|
15
|
+
* @param message descriptive message.
|
|
16
|
+
*/
|
|
4
17
|
constructor(line: number, code: string, message: string);
|
|
18
|
+
/** @returns a readable representation of the error, with its line and its code. */
|
|
5
19
|
toString(): string;
|
|
6
20
|
}
|