@stxt-lang/core 0.11.0 → 0.11.1

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 CHANGED
@@ -291,6 +291,22 @@ const text = NodeWriter.toSTXT(node, IndentStyle.TABS);
291
291
  const doc = NodeWriter.toSTXTDocs(result.getNodes(), IndentStyle.SPACES_4);
292
292
  ```
293
293
 
294
+ `NodeWriter` re-serializes the tree, so comments and blank lines are gone. To reformat a document
295
+ **keeping everything the author wrote**, use `Formatter`: it rewrites the original text line by
296
+ line — node lines in canonical form, block lines re-indented to their block, comments and blank
297
+ lines kept with their indentation units converted — and reports the syntax errors it met, so the
298
+ caller decides what to do with a document that does not parse. It is the formatter behind
299
+ `stxt format`, the VS Code extension and the playground.
300
+
301
+ ```ts
302
+ import { Formatter, IndentStyle } from '@stxt-lang/core';
303
+
304
+ const { text, errors } = Formatter.format(source, IndentStyle.TABS);
305
+ if (errors.length === 0) {
306
+ fs.writeFileSync(file, text);
307
+ }
308
+ ```
309
+
294
310
  ## API surface
295
311
 
296
312
  Everything importable from the package:
@@ -300,7 +316,7 @@ Everything importable from the package:
300
316
  - **Extension points** — `Observer`, `Validator`
301
317
  - **Schemas** — `Schema`, `SchemaValidator`, `SchemaProvider`, `SchemaProviderMemory`, `SchemaProviderMeta`, `NodeDefinition`, `ChildDefinition`, `TypeRegistry`, `Type`, `transformNodeToSchema`
302
318
  - **Templates** — `transformTemplateNodeToSchema`, `TEMPLATE_NAMESPACE`, `TemplateSchemaProviderMemory`, `MetaTemplateSchemaProvider`
303
- - **Runtime** — `UnifiedSchemaProvider`, `NodeWriter`, `IndentStyle`
319
+ - **Runtime** — `UnifiedSchemaProvider`, `NodeWriter`, `IndentStyle`, `Formatter`, `FormatResult`, `toCanonicalTree`, `toCanonicalJson`
304
320
  - **Discovery** — `DiscoveryResolver`, `DiscoveryOptions`, `DiscoveryResult`, `DiscoveryDefinition`, `DiscoveryLevel`, `DiscoveryError`, `DiscoveryFileSystem`, `DiscoveryEntry`, `DiscoveryEnvironment`
305
321
 
306
322
  ## License
package/out/all.d.ts CHANGED
@@ -30,6 +30,8 @@ export { ChildDefinition } from "./schema/ChildDefinition";
30
30
  export { transformNodeToSchema } from "./schema/SchemaParser";
31
31
  export { UnifiedSchemaProvider } from "./runtime/UnifiedSchemaProvider";
32
32
  export { NodeWriter, IndentStyle } from "./runtime/NodeWriter";
33
+ export { Formatter } from "./runtime/Formatter";
34
+ export type { FormatResult } from "./runtime/Formatter";
33
35
  export { toCanonicalTree, toCanonicalJson } from "./runtime/TreeJson";
34
36
  export type { CanonicalDocument, CanonicalNode, CanonicalInlineNode, CanonicalBlockNode } from "./runtime/TreeJson";
35
37
  export { transformTemplateNodeToSchema, TEMPLATE_NAMESPACE } from "./template/TemplateParser";
package/out/all.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // Anything that should be consumable by third parties (e.g. the VSCode extension)
4
4
  // has to be re-exported from here.
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DiscoveryError = exports.DiscoveryResult = exports.DiscoveryResolver = exports.MetaTemplateSchemaProvider = exports.TemplateSchemaProviderMemory = exports.TEMPLATE_NAMESPACE = exports.transformTemplateNodeToSchema = exports.toCanonicalJson = exports.toCanonicalTree = exports.IndentStyle = exports.NodeWriter = exports.UnifiedSchemaProvider = exports.transformNodeToSchema = exports.ChildDefinition = exports.NodeDefinition = exports.TypeRegistry = exports.SchemaProviderMeta = exports.SchemaProviderMemory = exports.SchemaValidator = exports.Schema = exports.RuntimeException = exports.ValidationException = exports.ParseException = exports.StringUtils = exports.parseLine = exports.SPEC_VERSION = exports.Constants = exports.Line = exports.ParseResult = exports.Parser = exports.TextNode = exports.InlineNode = exports.Node = void 0;
6
+ exports.DiscoveryError = exports.DiscoveryResult = exports.DiscoveryResolver = exports.MetaTemplateSchemaProvider = exports.TemplateSchemaProviderMemory = exports.TEMPLATE_NAMESPACE = exports.transformTemplateNodeToSchema = exports.toCanonicalJson = exports.toCanonicalTree = exports.Formatter = exports.IndentStyle = exports.NodeWriter = exports.UnifiedSchemaProvider = exports.transformNodeToSchema = exports.ChildDefinition = exports.NodeDefinition = exports.TypeRegistry = exports.SchemaProviderMeta = exports.SchemaProviderMemory = exports.SchemaValidator = exports.Schema = exports.RuntimeException = exports.ValidationException = exports.ParseException = exports.StringUtils = exports.parseLine = exports.SPEC_VERSION = exports.Constants = exports.Line = exports.ParseResult = exports.Parser = exports.TextNode = exports.InlineNode = exports.Node = void 0;
7
7
  var Node_1 = require("./core/Node");
8
8
  Object.defineProperty(exports, "Node", { enumerable: true, get: function () { return Node_1.Node; } });
9
9
  var InlineNode_1 = require("./core/InlineNode");
@@ -56,6 +56,8 @@ Object.defineProperty(exports, "UnifiedSchemaProvider", { enumerable: true, get:
56
56
  var NodeWriter_1 = require("./runtime/NodeWriter");
57
57
  Object.defineProperty(exports, "NodeWriter", { enumerable: true, get: function () { return NodeWriter_1.NodeWriter; } });
58
58
  Object.defineProperty(exports, "IndentStyle", { enumerable: true, get: function () { return NodeWriter_1.IndentStyle; } });
59
+ var Formatter_1 = require("./runtime/Formatter");
60
+ Object.defineProperty(exports, "Formatter", { enumerable: true, get: function () { return Formatter_1.Formatter; } });
59
61
  var TreeJson_1 = require("./runtime/TreeJson");
60
62
  Object.defineProperty(exports, "toCanonicalTree", { enumerable: true, get: function () { return TreeJson_1.toCanonicalTree; } });
61
63
  Object.defineProperty(exports, "toCanonicalJson", { enumerable: true, get: function () { return TreeJson_1.toCanonicalJson; } });
@@ -0,0 +1,104 @@
1
+ import { ParseException } from "../exceptions/ParseException";
2
+ import { IndentStyle } from "./NodeWriter";
3
+ /** The outcome of {@link Formatter.format}. */
4
+ export interface FormatResult {
5
+ /**
6
+ * The formatted document: the same lines as the source, in the same order, with the same
7
+ * line ending (CRLF is kept) and with a final newline only where the source had one.
8
+ */
9
+ text: string;
10
+ /**
11
+ * Syntax errors found while parsing, in line order; empty when the document parses. A line
12
+ * the parse tree does not describe because of an error is only converted by indentation
13
+ * units and right-trimmed (see {@link Formatter}): formatting never repairs a document, and
14
+ * whether a document with errors should be reformatted at all is the caller's decision.
15
+ */
16
+ errors: ParseException[];
17
+ }
18
+ /**
19
+ * Reformats an STXT document **line by line, over the original text**, so that nothing the
20
+ * parse tree does not hold — comments, blank lines, the exact content of text blocks — is lost.
21
+ * This is what distinguishes it from {@link NodeWriter}, which re-serializes the tree and
22
+ * therefore drops comments and blank lines.
23
+ *
24
+ * The rules, the same for every tool of the ecosystem (the CLI's `stxt format`, the VS Code
25
+ * extension's formatter and the playground's re-indentation all delegate here):
26
+ *
27
+ * - A line that **opens a node** is rendered in canonical form: the indentation of its level in
28
+ * the requested style, the name as parsed, the namespace only where the source wrote it (a
29
+ * child repeating its parent's namespace is redundant but legal, and dropping it would be an
30
+ * edit, not a reformat), `: value` with exactly one space — or a bare `:` when there is no
31
+ * value, so container nodes do not end in a stray space — or ` >>` for a block.
32
+ * - A **text line of a block** gets the indentation of the block (its level plus one) in the
33
+ * requested style, followed by its content; any indentation the line had beyond the block's
34
+ * is content (STXT-SPEC §10.2, relative indentation is preserved) and is kept exactly. A
35
+ * blank line of the block is `""` in the content whatever it looks like in the source
36
+ * (STXT-SPEC §10.3), so it is written with the indentation of the block too: the block reads
37
+ * as one piece and, at the end of the file, the line is not lost — an empty last line would
38
+ * be indistinguishable from the final line ending.
39
+ * - Every **other line** — a comment, a blank line outside a block, or a line the parse tree
40
+ * does not describe because of a syntax error — is kept as the author wrote it, except that
41
+ * its trailing blanks are removed and the whole indentation units at its start are converted
42
+ * one for one to the requested style (a tab or four spaces in either style count as a unit;
43
+ * whatever follows the last whole unit, including a remainder that is not a whole unit, is
44
+ * kept as it is). STXT-SPEC §9 validates the indentation of a comment like a node's, so in a
45
+ * document that parses every comment has a whole number of units and comes out fully in the
46
+ * new style; the remainder only survives in documents with errors, which this conversion
47
+ * neither repairs nor hides.
48
+ *
49
+ * The result is idempotent, round-trips between the two styles, and produces the same canonical
50
+ * tree (STXT-TREE-SPEC) as the source. The document is parsed without any schema: formatting
51
+ * has nothing to do with validation.
52
+ */
53
+ export declare class Formatter {
54
+ private constructor();
55
+ /**
56
+ * Formats a document.
57
+ *
58
+ * @param text the document.
59
+ * @param style indentation style to format with; tabs by default.
60
+ * @returns the formatted text and the syntax errors found; see {@link FormatResult}.
61
+ */
62
+ static format(text: string, style?: IndentStyle): FormatResult;
63
+ /**
64
+ * Formats one source line.
65
+ *
66
+ * @param line the line, without its line ending.
67
+ * @param lineNumber its line number, 1-indexed as the parser counts them.
68
+ * @param style indentation style to format with.
69
+ * @param sourceLines the parse of the document seen as source lines.
70
+ * @returns the formatted line.
71
+ */
72
+ private static formatLine;
73
+ /**
74
+ * Renders the line that opens a node in its canonical form.
75
+ *
76
+ * @param node the node the line opens.
77
+ * @param line the source line, used only to tell whether it spelled the namespace out.
78
+ * @param style indentation style to format with.
79
+ * @returns the formatted line.
80
+ */
81
+ private static renderNode;
82
+ /**
83
+ * Converts the whole indentation units at the start of a line to the requested style and
84
+ * keeps the rest of the line, remainder included.
85
+ *
86
+ * @param line the line, without trailing blanks.
87
+ * @param style indentation style to convert to.
88
+ * @returns the line with its indentation units converted.
89
+ */
90
+ private static convertUnits;
91
+ /**
92
+ * @param line a line.
93
+ * @param position a position in it.
94
+ * @returns the length of the whole indentation unit — a tab or four spaces — that starts at
95
+ * `position`, or 0 if none does.
96
+ */
97
+ private static unitAt;
98
+ /**
99
+ * @param level indentation level to produce.
100
+ * @param style indentation style to produce it in.
101
+ * @returns the indentation of that level.
102
+ */
103
+ private static indent;
104
+ }
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Formatter = void 0;
4
+ const InlineNode_1 = require("../core/InlineNode");
5
+ const Parser_1 = require("../core/Parser");
6
+ const StringUtils_1 = require("../core/StringUtils");
7
+ const TextNode_1 = require("../core/TextNode");
8
+ const NodeWriter_1 = require("./NodeWriter");
9
+ /**
10
+ * Reformats an STXT document **line by line, over the original text**, so that nothing the
11
+ * parse tree does not hold — comments, blank lines, the exact content of text blocks — is lost.
12
+ * This is what distinguishes it from {@link NodeWriter}, which re-serializes the tree and
13
+ * therefore drops comments and blank lines.
14
+ *
15
+ * The rules, the same for every tool of the ecosystem (the CLI's `stxt format`, the VS Code
16
+ * extension's formatter and the playground's re-indentation all delegate here):
17
+ *
18
+ * - A line that **opens a node** is rendered in canonical form: the indentation of its level in
19
+ * the requested style, the name as parsed, the namespace only where the source wrote it (a
20
+ * child repeating its parent's namespace is redundant but legal, and dropping it would be an
21
+ * edit, not a reformat), `: value` with exactly one space — or a bare `:` when there is no
22
+ * value, so container nodes do not end in a stray space — or ` >>` for a block.
23
+ * - A **text line of a block** gets the indentation of the block (its level plus one) in the
24
+ * requested style, followed by its content; any indentation the line had beyond the block's
25
+ * is content (STXT-SPEC §10.2, relative indentation is preserved) and is kept exactly. A
26
+ * blank line of the block is `""` in the content whatever it looks like in the source
27
+ * (STXT-SPEC §10.3), so it is written with the indentation of the block too: the block reads
28
+ * as one piece and, at the end of the file, the line is not lost — an empty last line would
29
+ * be indistinguishable from the final line ending.
30
+ * - Every **other line** — a comment, a blank line outside a block, or a line the parse tree
31
+ * does not describe because of a syntax error — is kept as the author wrote it, except that
32
+ * its trailing blanks are removed and the whole indentation units at its start are converted
33
+ * one for one to the requested style (a tab or four spaces in either style count as a unit;
34
+ * whatever follows the last whole unit, including a remainder that is not a whole unit, is
35
+ * kept as it is). STXT-SPEC §9 validates the indentation of a comment like a node's, so in a
36
+ * document that parses every comment has a whole number of units and comes out fully in the
37
+ * new style; the remainder only survives in documents with errors, which this conversion
38
+ * neither repairs nor hides.
39
+ *
40
+ * The result is idempotent, round-trips between the two styles, and produces the same canonical
41
+ * tree (STXT-TREE-SPEC) as the source. The document is parsed without any schema: formatting
42
+ * has nothing to do with validation.
43
+ */
44
+ class Formatter {
45
+ constructor() { }
46
+ /**
47
+ * Formats a document.
48
+ *
49
+ * @param text the document.
50
+ * @param style indentation style to format with; tabs by default.
51
+ * @returns the formatted text and the syntax errors found; see {@link FormatResult}.
52
+ */
53
+ static format(text, style = NodeWriter_1.IndentStyle.TABS) {
54
+ const sourceLines = new SourceLines();
55
+ const parser = new Parser_1.Parser();
56
+ parser.registerObserver(sourceLines);
57
+ const result = parser.parseResult(text);
58
+ const eol = text.includes("\r\n") ? "\r\n" : "\n";
59
+ const formatted = text
60
+ .split(/\r?\n/)
61
+ .map((line, index) => Formatter.formatLine(line, index + 1, style, sourceLines))
62
+ .join(eol);
63
+ return { text: formatted, errors: result.getErrors() };
64
+ }
65
+ /**
66
+ * Formats one source line.
67
+ *
68
+ * @param line the line, without its line ending.
69
+ * @param lineNumber its line number, 1-indexed as the parser counts them.
70
+ * @param style indentation style to format with.
71
+ * @param sourceLines the parse of the document seen as source lines.
72
+ * @returns the formatted line.
73
+ */
74
+ static formatLine(line, lineNumber, style, sourceLines) {
75
+ const node = sourceLines.nodeAt(lineNumber);
76
+ if (node) {
77
+ return Formatter.renderNode(node, line, style);
78
+ }
79
+ const text = sourceLines.textAt(lineNumber);
80
+ if (text) {
81
+ return Formatter.indent(text.node.getLevel() + 1, style) + text.line.content;
82
+ }
83
+ return Formatter.convertUnits(StringUtils_1.StringUtils.rightTrim(line), style);
84
+ }
85
+ /**
86
+ * Renders the line that opens a node in its canonical form.
87
+ *
88
+ * @param node the node the line opens.
89
+ * @param line the source line, used only to tell whether it spelled the namespace out.
90
+ * @param style indentation style to format with.
91
+ * @returns the formatted line.
92
+ */
93
+ static renderNode(node, line, style) {
94
+ const indent = Formatter.indent(node.getLevel(), style);
95
+ const head = node instanceof InlineNode_1.InlineNode ? line.substring(0, line.indexOf(":")) : line;
96
+ const name = head.includes("(")
97
+ ? `${node.getName()} (${node.getNamespace()})`
98
+ : node.getName();
99
+ if (node instanceof TextNode_1.TextNode) {
100
+ return `${indent}${name} >>`;
101
+ }
102
+ const value = node.getValue();
103
+ return value.length > 0 ? `${indent}${name}: ${value}` : `${indent}${name}:`;
104
+ }
105
+ /**
106
+ * Converts the whole indentation units at the start of a line to the requested style and
107
+ * keeps the rest of the line, remainder included.
108
+ *
109
+ * @param line the line, without trailing blanks.
110
+ * @param style indentation style to convert to.
111
+ * @returns the line with its indentation units converted.
112
+ */
113
+ static convertUnits(line, style) {
114
+ let consumed = 0;
115
+ let units = 0;
116
+ let unit = Formatter.unitAt(line, consumed);
117
+ while (unit > 0) {
118
+ consumed += unit;
119
+ units++;
120
+ unit = Formatter.unitAt(line, consumed);
121
+ }
122
+ return units === 0 ? line : Formatter.indent(units, style) + line.substring(consumed);
123
+ }
124
+ /**
125
+ * @param line a line.
126
+ * @param position a position in it.
127
+ * @returns the length of the whole indentation unit — a tab or four spaces — that starts at
128
+ * `position`, or 0 if none does.
129
+ */
130
+ static unitAt(line, position) {
131
+ if (line.startsWith("\t", position)) {
132
+ return 1;
133
+ }
134
+ return line.startsWith(" ", position) ? 4 : 0;
135
+ }
136
+ /**
137
+ * @param level indentation level to produce.
138
+ * @param style indentation style to produce it in.
139
+ * @returns the indentation of that level.
140
+ */
141
+ static indent(level, style) {
142
+ return (style === NodeWriter_1.IndentStyle.SPACES_4 ? " " : "\t").repeat(level);
143
+ }
144
+ }
145
+ exports.Formatter = Formatter;
146
+ /**
147
+ * The parse of a document seen as source lines: which line opened which node, and which line is
148
+ * a text line of which block. It is what lets the formatter rewrite the lines the parse tree
149
+ * describes and leave every other line as the author wrote it.
150
+ */
151
+ class SourceLines {
152
+ constructor() {
153
+ this.nodeByLine = new Map();
154
+ this.textByLine = new Map();
155
+ }
156
+ onCreate(node) {
157
+ this.nodeByLine.set(node.getLine(), node);
158
+ }
159
+ onFinish() {
160
+ // Formatting only needs to know where each node started.
161
+ }
162
+ onComment() {
163
+ // Comment lines need no bookkeeping: every line that opens no node is treated alike.
164
+ }
165
+ onTextLine(node, lineNumber, lineString, line) {
166
+ this.textByLine.set(lineNumber, { node, line });
167
+ }
168
+ /**
169
+ * @param lineNumber line number, 1-indexed.
170
+ * @returns the node this line opened, or undefined if it opened none.
171
+ */
172
+ nodeAt(lineNumber) {
173
+ return this.nodeByLine.get(lineNumber);
174
+ }
175
+ /**
176
+ * @param lineNumber line number, 1-indexed.
177
+ * @returns the block node this line is text of and the line already split into indentation
178
+ * and content, or undefined if the line is not text of a block.
179
+ */
180
+ textAt(lineNumber) {
181
+ return this.textByLine.get(lineNumber);
182
+ }
183
+ }
184
+ //# sourceMappingURL=Formatter.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stxt-lang/core",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Parser and schema validator for STXT, an indentation-based structured-text format.",
5
5
  "main": "out/all.js",
6
6
  "types": "out/all.d.ts",