@rejot-dev/thalo-prettier 0.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 - present ReJot Nederland B.V., and individual contributors.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/mod.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { SyntaxNode } from "tree-sitter";
2
+ import { Plugin } from "prettier";
3
+
4
+ //#region src/mod.d.ts
5
+ declare const languages: Plugin<SyntaxNode>["languages"];
6
+ declare const parsers: Plugin<SyntaxNode>["parsers"];
7
+ declare const printers: Plugin<SyntaxNode>["printers"];
8
+ //#endregion
9
+ export { languages, parsers, printers };
10
+ //# sourceMappingURL=mod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.d.ts","names":[],"sources":["../src/mod.ts"],"sourcesContent":[],"mappings":";;;;cAKa,WAAW,OAAO;cAQlB,SAAS,OAAO;AARhB,cAYA,QAZkB,EAYR,MAZQ,CAYD,UAZA,CAAA,CAAA,UAAA,CAAA"}
package/dist/mod.js ADDED
@@ -0,0 +1,15 @@
1
+ import { parser } from "./parser.js";
2
+ import { printer } from "./printer.js";
3
+
4
+ //#region src/mod.ts
5
+ const languages = [{
6
+ name: "thalo",
7
+ parsers: ["thalo"],
8
+ extensions: [".thalo"]
9
+ }];
10
+ const parsers = { thalo: parser };
11
+ const printers = { "thalo-ast": printer };
12
+
13
+ //#endregion
14
+ export { languages, parsers, printers };
15
+ //# sourceMappingURL=mod.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod.js","names":["languages: Plugin<SyntaxNode>[\"languages\"]","parsers: Plugin<SyntaxNode>[\"parsers\"]","printers: Plugin<SyntaxNode>[\"printers\"]"],"sources":["../src/mod.ts"],"sourcesContent":["import type { Plugin } from \"prettier\";\nimport type { SyntaxNode } from \"tree-sitter\";\nimport { parser } from \"./parser\";\nimport { printer } from \"./printer\";\n\nexport const languages: Plugin<SyntaxNode>[\"languages\"] = [\n {\n name: \"thalo\",\n parsers: [\"thalo\"],\n extensions: [\".thalo\"],\n },\n];\n\nexport const parsers: Plugin<SyntaxNode>[\"parsers\"] = {\n thalo: parser,\n};\n\nexport const printers: Plugin<SyntaxNode>[\"printers\"] = {\n \"thalo-ast\": printer,\n};\n"],"mappings":";;;;AAKA,MAAaA,YAA6C,CACxD;CACE,MAAM;CACN,SAAS,CAAC,QAAQ;CAClB,YAAY,CAAC,SAAS;CACvB,CACF;AAED,MAAaC,UAAyC,EACpD,OAAO,QACR;AAED,MAAaC,WAA2C,EACtD,aAAa,SACd"}
package/dist/parser.js ADDED
@@ -0,0 +1,33 @@
1
+ import Parser from "tree-sitter";
2
+ import thalo from "@rejot-dev/tree-sitter-thalo";
3
+
4
+ //#region src/parser.ts
5
+ let parserInstance;
6
+ const getParser = () => {
7
+ if (!parserInstance) {
8
+ thalo.nodeTypeInfo ??= [];
9
+ parserInstance = new Parser();
10
+ parserInstance.setLanguage(thalo);
11
+ }
12
+ return parserInstance;
13
+ };
14
+ const parseThalo = (source) => {
15
+ return getParser().parse(source);
16
+ };
17
+ const parser = {
18
+ parse: (text, options) => {
19
+ const tree = parseThalo(text);
20
+ const rootNode = tree.rootNode;
21
+ rootNode._thaloSource = text;
22
+ rootNode._thaloFilepath = options?.filepath;
23
+ rootNode._thaloHasErrors = tree.rootNode.hasError;
24
+ return rootNode;
25
+ },
26
+ astFormat: "thalo-ast",
27
+ locStart: (node) => node.startIndex,
28
+ locEnd: (node) => node.endIndex
29
+ };
30
+
31
+ //#endregion
32
+ export { parser };
33
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","names":["parserInstance: Parser | undefined"],"sources":["../src/parser.ts"],"sourcesContent":["import Parser, { type Language, type SyntaxNode } from \"tree-sitter\";\nimport thalo from \"@rejot-dev/tree-sitter-thalo\";\nimport {\n findErrorNodes,\n formatParseErrors,\n type ErrorLocation,\n extractErrorLocations,\n} from \"./parse-errors\";\n\nlet parserInstance: Parser | undefined;\n\nconst getParser = (): Parser => {\n if (!parserInstance) {\n // Ensure nodeTypeInfo is an array (may be undefined if JSON import fails in some environments)\n thalo.nodeTypeInfo ??= [];\n parserInstance = new Parser();\n parserInstance.setLanguage(thalo as unknown as Language);\n }\n return parserInstance;\n};\n\nexport const parseThalo = (source: string): Parser.Tree => {\n return getParser().parse(source);\n};\n\n// Re-export for external use\nexport { findErrorNodes, formatParseErrors, type ErrorLocation, extractErrorLocations };\n\n// Extended root node type that includes parse metadata\nexport interface ThaloRootNode extends SyntaxNode {\n _thaloSource?: string;\n _thaloFilepath?: string;\n _thaloHasErrors?: boolean;\n}\n\ninterface ParserOptions {\n filepath?: string;\n}\n\nexport const parser = {\n parse: (text: string, options?: ParserOptions): ThaloRootNode => {\n const tree = parseThalo(text);\n const rootNode = tree.rootNode as ThaloRootNode;\n\n // Attach metadata to root node for printer to access\n rootNode._thaloSource = text;\n rootNode._thaloFilepath = options?.filepath;\n rootNode._thaloHasErrors = tree.rootNode.hasError;\n\n // Don't throw - let printer handle ERROR nodes gracefully\n return rootNode;\n },\n astFormat: \"thalo-ast\",\n locStart: (node: SyntaxNode) => node.startIndex,\n locEnd: (node: SyntaxNode) => node.endIndex,\n};\n"],"mappings":";;;;AASA,IAAIA;AAEJ,MAAM,kBAA0B;AAC9B,KAAI,CAAC,gBAAgB;AAEnB,QAAM,iBAAiB,EAAE;AACzB,mBAAiB,IAAI,QAAQ;AAC7B,iBAAe,YAAY,MAA6B;;AAE1D,QAAO;;AAGT,MAAa,cAAc,WAAgC;AACzD,QAAO,WAAW,CAAC,MAAM,OAAO;;AAiBlC,MAAa,SAAS;CACpB,QAAQ,MAAc,YAA2C;EAC/D,MAAM,OAAO,WAAW,KAAK;EAC7B,MAAM,WAAW,KAAK;AAGtB,WAAS,eAAe;AACxB,WAAS,iBAAiB,SAAS;AACnC,WAAS,kBAAkB,KAAK,SAAS;AAGzC,SAAO;;CAET,WAAW;CACX,WAAW,SAAqB,KAAK;CACrC,SAAS,SAAqB,KAAK;CACpC"}
@@ -0,0 +1,398 @@
1
+ import { doc } from "prettier";
2
+
3
+ //#region src/printer.ts
4
+ const { align, fill, hardline, join, line } = doc.builders;
5
+ const getIndent = (options) => {
6
+ const tabWidth = typeof options.tabWidth === "number" ? options.tabWidth : 2;
7
+ return options.useTabs ? " " : " ".repeat(tabWidth);
8
+ };
9
+ /**
10
+ * Detect markdown list items: "- item", "* item", "+ item", "1. item", etc.
11
+ */
12
+ const isListItem = (line$1) => {
13
+ return /^[-*+]\s|^\d+\.\s/.test(line$1);
14
+ };
15
+ /**
16
+ * Check if a word starts with a character that cannot begin a content line.
17
+ * In thalo syntax, content lines cannot start with:
18
+ * - # (would be parsed as markdown header attempt)
19
+ * - // (would be parsed as a comment)
20
+ */
21
+ const startsWithProblematicChar = (word) => {
22
+ return word.startsWith("#") || word.startsWith("//");
23
+ };
24
+ const getContentLineText = (node) => {
25
+ const contentText = node.children.find((c) => c.type === "content_text");
26
+ if (contentText) return contentText.text.trim();
27
+ return node.text.replace(/^[\r\n\s]+/, "").replace(/[\r\n]+$/, "");
28
+ };
29
+ const printContentLine = (node, _options, indent) => {
30
+ return [indent, getContentLineText(node)];
31
+ };
32
+ const formatParagraph = (lines, options, indent) => {
33
+ if (lines.length === 0) return "";
34
+ const proseWrap = options.proseWrap ?? "preserve";
35
+ if (lines.some(isListItem)) {
36
+ const listItems = [];
37
+ for (const rawLine of lines) {
38
+ const trimmedLine = rawLine.trim();
39
+ if (isListItem(trimmedLine)) listItems.push([trimmedLine]);
40
+ else if (listItems.length > 0) listItems[listItems.length - 1].push(trimmedLine);
41
+ else listItems.push([trimmedLine]);
42
+ }
43
+ const docs = [];
44
+ for (const [itemIndex, itemLines] of listItems.entries()) {
45
+ if (itemIndex > 0) docs.push(hardline);
46
+ const firstLine = itemLines[0];
47
+ if (isListItem(firstLine)) {
48
+ const match = firstLine.match(/^([-*+]\s|\d+\.\s)(.*)$/);
49
+ if (match) {
50
+ const [, marker, firstContent] = match;
51
+ const allContent = [firstContent, ...itemLines.slice(1)].join(" ").replace(/\s+/g, " ").trim();
52
+ const words = allContent.length > 0 ? allContent.split(/\s+/) : [];
53
+ if (words.length === 0) docs.push(indent, marker.trimEnd());
54
+ else if (proseWrap === "always") {
55
+ const wordDocs = [];
56
+ for (const [wordIndex, word] of words.entries()) if (wordIndex === 0) wordDocs.push(word);
57
+ else if (startsWithProblematicChar(word)) wordDocs.push(" ", word);
58
+ else wordDocs.push(line, word);
59
+ const markerIndent = indent + " ".repeat(marker.length);
60
+ docs.push(indent, marker, align(markerIndent, fill(wordDocs)));
61
+ } else docs.push(indent, marker, allContent);
62
+ } else docs.push(indent, firstLine);
63
+ } else docs.push(indent, itemLines.join(" ").replace(/\s+/g, " ").trim());
64
+ }
65
+ return docs;
66
+ }
67
+ const text = lines.join(" ").replace(/\s+/g, " ").trim();
68
+ if (proseWrap === "never") return [indent, text];
69
+ if (proseWrap === "always") {
70
+ const words = text.length > 0 ? text.split(/\s+/) : [];
71
+ if (words.length === 0) return indent;
72
+ const wordDocs = [];
73
+ for (const [index, word] of words.entries()) if (index === 0) wordDocs.push(word);
74
+ else if (startsWithProblematicChar(word)) wordDocs.push(" ", word);
75
+ else wordDocs.push(line, word);
76
+ return [indent, align(indent, fill(wordDocs))];
77
+ }
78
+ const [first, ...rest] = lines.map((l) => l.trim());
79
+ if (rest.length === 0) return [indent, first];
80
+ return [indent, join([hardline, indent], [first, ...rest])];
81
+ };
82
+ /**
83
+ * Print header fields that are inline in data_entry or schema_entry nodes.
84
+ * The new grammar has header fields directly on the entry node rather than
85
+ * in a separate header child node.
86
+ */
87
+ const printEntryHeaderFields = (node, directiveType) => {
88
+ const parts = [];
89
+ for (const child of node.children) if (child.type === "timestamp") parts.push(child.text);
90
+ else if (child.type === directiveType) parts.push(" ", child.text);
91
+ else if (child.type === "identifier") parts.push(" ", child.text);
92
+ else if (child.type === "title") parts.push(" ", child.text);
93
+ else if (child.type === "link") parts.push(" ", child.text);
94
+ else if (child.type === "tag") parts.push(" ", child.text);
95
+ return parts;
96
+ };
97
+ const printMetadata = (node, indent) => {
98
+ const key = node.childForFieldName("key");
99
+ const value = node.childForFieldName("value");
100
+ if (!key || !value) return "";
101
+ return [
102
+ indent,
103
+ key.text,
104
+ ": ",
105
+ value.text.trim()
106
+ ];
107
+ };
108
+ const printMarkdownHeader = (node, indent) => {
109
+ let hashes = "";
110
+ let text = "";
111
+ for (const child of node.children) if (child.type === "md_indicator") hashes = child.text;
112
+ else if (child.type === "md_heading_text") text = child.text;
113
+ if (!hashes) {
114
+ const trimmedText = node.text.replace(/^[\r\n\s]+/, "").replace(/[\r\n]+$/, "");
115
+ const match = trimmedText.match(/^(#+)\s*(.*)$/);
116
+ if (match) {
117
+ hashes = match[1];
118
+ text = " " + match[2];
119
+ } else return [indent, trimmedText.trim()];
120
+ }
121
+ text = text.replace(/ +/g, " ");
122
+ return [
123
+ indent,
124
+ hashes,
125
+ text
126
+ ];
127
+ };
128
+ const printCommentLine = (node, indent) => {
129
+ const comment = node.children.find((c) => c.type === "comment");
130
+ if (comment) return [indent, comment.text];
131
+ return [indent, node.text.replace(/^[\r\n\s]+/, "").replace(/[\r\n]+$/, "")];
132
+ };
133
+ const printContent = (node, options, indent, insertBlankLineBeforeContent) => {
134
+ const parts = [];
135
+ const contentChildren = node.children.filter((c) => c.type === "markdown_header" || c.type === "content_line" || c.type === "comment_line");
136
+ let lastRow = -1;
137
+ let paragraphLines = [];
138
+ const flushParagraph = () => {
139
+ if (paragraphLines.length === 0) return;
140
+ parts.push(hardline, formatParagraph(paragraphLines, options, indent));
141
+ paragraphLines = [];
142
+ };
143
+ for (const child of contentChildren) {
144
+ const currentRow = child.startPosition.row;
145
+ if (lastRow >= 0) {
146
+ const rowGap = currentRow - lastRow;
147
+ if (rowGap > 1) {
148
+ flushParagraph();
149
+ for (let i = 0; i < rowGap - 1; i++) parts.push(hardline);
150
+ }
151
+ }
152
+ if (child.type === "markdown_header") {
153
+ flushParagraph();
154
+ parts.push(hardline, printMarkdownHeader(child, indent));
155
+ } else if (child.type === "comment_line") {
156
+ flushParagraph();
157
+ parts.push(hardline, printCommentLine(child, indent));
158
+ } else paragraphLines.push(getContentLineText(child));
159
+ lastRow = currentRow;
160
+ }
161
+ flushParagraph();
162
+ if (parts.length === 0) return "";
163
+ return insertBlankLineBeforeContent ? [hardline, ...parts] : parts;
164
+ };
165
+ /**
166
+ * Print a data_entry node (handles create, update, define-synthesis, actualize-synthesis).
167
+ * Header fields are inline on the entry node in the new grammar.
168
+ */
169
+ const printDataEntry = (node, options, indent) => {
170
+ const parts = [];
171
+ parts.push(printEntryHeaderFields(node, "data_directive"));
172
+ const metadataAndComments = node.children.filter((c) => c.type === "metadata" || c.type === "comment_line");
173
+ const hasHeaderPreludeLines = metadataAndComments.length > 0;
174
+ for (const child of metadataAndComments) if (child.type === "metadata") parts.push(hardline, printMetadata(child, indent));
175
+ else parts.push(hardline, printCommentLine(child, indent));
176
+ const content = node.children.find((c) => c.type === "content");
177
+ if (content) parts.push(printContent(content, options, indent, hasHeaderPreludeLines));
178
+ return parts;
179
+ };
180
+ const printTypeExpression = (node) => {
181
+ const parts = [];
182
+ for (const child of node.children) if (child.type === "union_type") parts.push(printUnionType(child));
183
+ else if (child.type === "array_type") parts.push(printArrayType(child));
184
+ else if (child.type === "primitive_type") parts.push(child.text);
185
+ else if (child.type === "literal_type") parts.push(child.text);
186
+ return parts;
187
+ };
188
+ const printUnionType = (node) => {
189
+ const typeTerms = [];
190
+ for (const child of node.children) if (child.type === "array_type") typeTerms.push(printArrayType(child));
191
+ else if (child.type === "primitive_type" || child.type === "literal_type") typeTerms.push(child.text);
192
+ return join(" | ", typeTerms);
193
+ };
194
+ const printArrayType = (node) => {
195
+ for (const child of node.children) if (child.type === "primitive_type" || child.type === "literal_type") return [child.text, "[]"];
196
+ return node.text;
197
+ };
198
+ const printFieldDefinition = (node, indent) => {
199
+ const parts = [indent];
200
+ const fieldName = node.children.find((c) => c.type === "field_name");
201
+ if (fieldName) {
202
+ const nameMatch = fieldName.text.match(/[a-z][a-zA-Z0-9\-_]*/);
203
+ if (nameMatch) parts.push(nameMatch[0]);
204
+ }
205
+ if (node.children.find((c) => c.type === "optional_marker")) parts.push("?");
206
+ parts.push(": ");
207
+ const typeExpr = node.childForFieldName("type");
208
+ if (typeExpr) parts.push(printTypeExpression(typeExpr));
209
+ const defaultValue = node.childForFieldName("default");
210
+ if (defaultValue) {
211
+ parts.push(" = ");
212
+ const literal = defaultValue.children.find((c) => c.type === "literal_type");
213
+ if (literal) parts.push(literal.text.trim());
214
+ else parts.push(defaultValue.text.trim());
215
+ }
216
+ const description = node.childForFieldName("description");
217
+ if (description) parts.push(" ; ", description.text);
218
+ return parts;
219
+ };
220
+ const printSectionDefinition = (node, indent) => {
221
+ const parts = [indent];
222
+ const sectionName = node.children.find((c) => c.type === "section_name");
223
+ if (sectionName) {
224
+ const nameMatch = sectionName.text.match(/[A-Z][a-zA-Z0-9]*(?: +[a-zA-Z0-9]+)*/);
225
+ if (nameMatch) parts.push(nameMatch[0].replace(/ +/g, " "));
226
+ }
227
+ if (node.children.find((c) => c.type === "optional_marker")) parts.push("?");
228
+ const description = node.childForFieldName("description");
229
+ if (description) parts.push(" ; ", description.text);
230
+ return parts;
231
+ };
232
+ const printFieldRemoval = (node, indent) => {
233
+ const parts = [indent];
234
+ const fieldName = node.children.find((c) => c.type === "field_name");
235
+ if (fieldName) {
236
+ const nameMatch = fieldName.text.match(/[a-z][a-zA-Z0-9\-_]*/);
237
+ if (nameMatch) parts.push(nameMatch[0]);
238
+ }
239
+ const reason = node.childForFieldName("reason");
240
+ if (reason) parts.push(" ; ", reason.text);
241
+ return parts;
242
+ };
243
+ const printSectionRemoval = (node, indent) => {
244
+ const parts = [indent];
245
+ const sectionName = node.children.find((c) => c.type === "section_name");
246
+ if (sectionName) {
247
+ const nameMatch = sectionName.text.match(/[A-Z][a-zA-Z0-9]*(?: +[a-zA-Z0-9]+)*/);
248
+ if (nameMatch) parts.push(nameMatch[0].replace(/ +/g, " "));
249
+ }
250
+ const reason = node.childForFieldName("reason");
251
+ if (reason) parts.push(" ; ", reason.text);
252
+ return parts;
253
+ };
254
+ const printMetadataBlock = (node, indent) => {
255
+ const parts = [
256
+ hardline,
257
+ indent,
258
+ "# Metadata"
259
+ ];
260
+ const fieldDefs = node.children.filter((c) => c.type === "field_definition");
261
+ for (const field of fieldDefs) parts.push(hardline, printFieldDefinition(field, indent));
262
+ return parts;
263
+ };
264
+ const printSectionsBlock = (node, indent) => {
265
+ const parts = [
266
+ hardline,
267
+ indent,
268
+ "# Sections"
269
+ ];
270
+ const sectionDefs = node.children.filter((c) => c.type === "section_definition");
271
+ for (const section of sectionDefs) parts.push(hardline, printSectionDefinition(section, indent));
272
+ return parts;
273
+ };
274
+ const printRemoveMetadataBlock = (node, indent) => {
275
+ const parts = [
276
+ hardline,
277
+ indent,
278
+ "# Remove Metadata"
279
+ ];
280
+ const fieldRemovals = node.children.filter((c) => c.type === "field_removal");
281
+ for (const removal of fieldRemovals) parts.push(hardline, printFieldRemoval(removal, indent));
282
+ return parts;
283
+ };
284
+ const printRemoveSectionsBlock = (node, indent) => {
285
+ const parts = [
286
+ hardline,
287
+ indent,
288
+ "# Remove Sections"
289
+ ];
290
+ const sectionRemovals = node.children.filter((c) => c.type === "section_removal");
291
+ for (const removal of sectionRemovals) parts.push(hardline, printSectionRemoval(removal, indent));
292
+ return parts;
293
+ };
294
+ /**
295
+ * Print a schema_entry node (handles define-entity, alter-entity).
296
+ * Header fields are inline on the entry node in the new grammar.
297
+ */
298
+ const printSchemaEntry = (node, _options, indent) => {
299
+ const parts = [];
300
+ parts.push(printEntryHeaderFields(node, "schema_directive"));
301
+ let hasBlockBefore = false;
302
+ for (const child of node.children) if (child.type === "metadata_block") {
303
+ parts.push(printMetadataBlock(child, indent));
304
+ hasBlockBefore = true;
305
+ } else if (child.type === "sections_block") {
306
+ if (hasBlockBefore) parts.push(hardline);
307
+ parts.push(printSectionsBlock(child, indent));
308
+ hasBlockBefore = true;
309
+ } else if (child.type === "remove_metadata_block") {
310
+ parts.push(printRemoveMetadataBlock(child, indent));
311
+ hasBlockBefore = true;
312
+ } else if (child.type === "remove_sections_block") {
313
+ if (hasBlockBefore) parts.push(hardline);
314
+ parts.push(printRemoveSectionsBlock(child, indent));
315
+ hasBlockBefore = true;
316
+ }
317
+ return parts;
318
+ };
319
+ const printEntry = (node, options, indent) => {
320
+ const dataEntry = node.children.find((c) => c.type === "data_entry");
321
+ if (dataEntry) return printDataEntry(dataEntry, options, indent);
322
+ const schemaEntry = node.children.find((c) => c.type === "schema_entry");
323
+ if (schemaEntry) return printSchemaEntry(schemaEntry, options, indent);
324
+ return node.text;
325
+ };
326
+ const printComment = (node, indent) => {
327
+ return node.startPosition.column > 0 ? [indent, node.text] : node.text;
328
+ };
329
+ const printUnhandledNode = (node) => {
330
+ return node.text;
331
+ };
332
+ const printSourceFile = (node, options, indent) => {
333
+ const rootNode = node;
334
+ if (rootNode._thaloHasErrors) {
335
+ const source = rootNode._thaloSource ?? "";
336
+ return source.endsWith("\n") ? source : source + "\n";
337
+ }
338
+ const relevantChildren = node.children.filter((c) => c.type !== "");
339
+ if (relevantChildren.length === 0) return "";
340
+ const docs = [];
341
+ let lastWasEntry = false;
342
+ let lastWasIndentedComment = false;
343
+ for (const child of relevantChildren) if (child.type === "comment") {
344
+ if (child.startPosition.column > 0) {
345
+ docs.push(hardline, printComment(child, indent));
346
+ lastWasIndentedComment = true;
347
+ } else {
348
+ if (lastWasEntry || lastWasIndentedComment) docs.push(hardline, hardline);
349
+ else if (docs.length > 0) docs.push(hardline);
350
+ docs.push(printComment(child, indent));
351
+ lastWasIndentedComment = false;
352
+ }
353
+ lastWasEntry = false;
354
+ } else if (child.type === "entry") {
355
+ if (docs.length > 0) docs.push(hardline, hardline);
356
+ docs.push(printEntry(child, options, indent));
357
+ lastWasEntry = true;
358
+ lastWasIndentedComment = false;
359
+ } else {
360
+ if (docs.length > 0) docs.push(hardline, hardline);
361
+ docs.push(printUnhandledNode(child));
362
+ lastWasEntry = true;
363
+ lastWasIndentedComment = false;
364
+ }
365
+ return [...docs, hardline];
366
+ };
367
+ const printer = { print(path, options, _print) {
368
+ const node = path.node;
369
+ const indent = getIndent(options);
370
+ switch (node.type) {
371
+ case "source_file": return printSourceFile(node, options, indent);
372
+ case "entry": return printEntry(node, options, indent);
373
+ case "data_entry": return printDataEntry(node, options, indent);
374
+ case "schema_entry": return printSchemaEntry(node, options, indent);
375
+ case "metadata": return printMetadata(node, indent);
376
+ case "content": return printContent(node, options, indent, true);
377
+ case "markdown_header": return printMarkdownHeader(node, indent);
378
+ case "content_line": return printContentLine(node, options, indent);
379
+ case "comment_line": return printCommentLine(node, indent);
380
+ case "comment": return printComment(node, indent);
381
+ case "metadata_block": return printMetadataBlock(node, indent);
382
+ case "sections_block": return printSectionsBlock(node, indent);
383
+ case "remove_metadata_block": return printRemoveMetadataBlock(node, indent);
384
+ case "remove_sections_block": return printRemoveSectionsBlock(node, indent);
385
+ case "field_definition": return printFieldDefinition(node, indent);
386
+ case "section_definition": return printSectionDefinition(node, indent);
387
+ case "field_removal": return printFieldRemoval(node, indent);
388
+ case "section_removal": return printSectionRemoval(node, indent);
389
+ case "type_expression": return printTypeExpression(node);
390
+ case "union_type": return printUnionType(node);
391
+ case "array_type": return printArrayType(node);
392
+ default: return node.text;
393
+ }
394
+ } };
395
+
396
+ //#endregion
397
+ export { printer };
398
+ //# sourceMappingURL=printer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"printer.js","names":["line","listItems: string[][]","docs: Doc[]","wordDocs: Doc[]","parts: Doc[]","paragraphLines: string[]","typeTerms: Doc[]","printer: ThaloPrinter"],"sources":["../src/printer.ts"],"sourcesContent":["import type { AstPath, Doc, Options, Printer } from \"prettier\";\nimport type { SyntaxNode } from \"tree-sitter\";\nimport { doc } from \"prettier\";\nimport type { ThaloRootNode } from \"./parser\";\n\nconst { align, fill, hardline, join, line } = doc.builders;\n\ntype ThaloPrinter = Printer<SyntaxNode>;\n\ntype ThaloOptions = Options & {\n proseWrap?: \"always\" | \"never\" | \"preserve\";\n};\n\nconst getIndent = (options: ThaloOptions): string => {\n const tabWidth = typeof options.tabWidth === \"number\" ? options.tabWidth : 2;\n return options.useTabs ? \"\\t\" : \" \".repeat(tabWidth);\n};\n\n/**\n * Detect markdown list items: \"- item\", \"* item\", \"+ item\", \"1. item\", etc.\n */\nconst isListItem = (line: string): boolean => {\n return /^[-*+]\\s|^\\d+\\.\\s/.test(line);\n};\n\n/**\n * Check if a word starts with a character that cannot begin a content line.\n * In thalo syntax, content lines cannot start with:\n * - # (would be parsed as markdown header attempt)\n * - // (would be parsed as a comment)\n */\nconst startsWithProblematicChar = (word: string): boolean => {\n return word.startsWith(\"#\") || word.startsWith(\"//\");\n};\n\nconst getContentLineText = (node: SyntaxNode): string => {\n const contentText = node.children.find((c) => c.type === \"content_text\");\n if (contentText) {\n return contentText.text.trim();\n }\n\n // Fallback: Content line text, trimmed of leading newline/indent and trailing newline\n // (grammar includes _content_line_start which has the newline+indent)\n return node.text.replace(/^[\\r\\n\\s]+/, \"\").replace(/[\\r\\n]+$/, \"\");\n};\n\nconst printContentLine = (node: SyntaxNode, _options: ThaloOptions, indent: string): Doc => {\n return [indent, getContentLineText(node)];\n};\n\nconst formatParagraph = (lines: string[], options: ThaloOptions, indent: string): Doc => {\n if (lines.length === 0) {\n return \"\";\n }\n\n const proseWrap = options.proseWrap ?? \"preserve\";\n\n // Check if any line is a list item - if so, preserve each on its own line\n const hasListItems = lines.some(isListItem);\n if (hasListItems) {\n // Group lines into list items: a bullet line and its continuation lines form one item\n // This ensures stable formatting regardless of how lines were previously wrapped\n const listItems: string[][] = [];\n for (const rawLine of lines) {\n const trimmedLine = rawLine.trim();\n if (isListItem(trimmedLine)) {\n // Start a new list item\n listItems.push([trimmedLine]);\n } else if (listItems.length > 0) {\n // Continuation of the current list item\n listItems[listItems.length - 1].push(trimmedLine);\n } else {\n // Non-list line before any list item (prose before the list)\n listItems.push([trimmedLine]);\n }\n }\n\n // Format each list item\n const docs: Doc[] = [];\n for (const [itemIndex, itemLines] of listItems.entries()) {\n if (itemIndex > 0) {\n docs.push(hardline);\n }\n\n const firstLine = itemLines[0];\n if (isListItem(firstLine)) {\n // This is a bullet item - combine all its lines and format as one unit\n const match = firstLine.match(/^([-*+]\\s|\\d+\\.\\s)(.*)$/);\n if (match) {\n const [, marker, firstContent] = match;\n // Combine bullet content with continuation lines\n const allContent = [firstContent, ...itemLines.slice(1)]\n .join(\" \")\n .replace(/\\s+/g, \" \")\n .trim();\n const words = allContent.length > 0 ? allContent.split(/\\s+/) : [];\n\n if (words.length === 0) {\n docs.push(indent, marker.trimEnd());\n } else if (proseWrap === \"always\") {\n const wordDocs: Doc[] = [];\n for (const [wordIndex, word] of words.entries()) {\n if (wordIndex === 0) {\n wordDocs.push(word);\n } else if (startsWithProblematicChar(word)) {\n // Words starting with # or // cannot begin a content line in thalo syntax.\n // Use a non-breaking space to keep them attached to the previous word.\n wordDocs.push(\" \", word);\n } else {\n wordDocs.push(line, word);\n }\n }\n // Align continuation to after the marker\n const markerIndent = indent + \" \".repeat(marker.length);\n docs.push(indent, marker, align(markerIndent, fill(wordDocs)));\n } else {\n // For \"never\" or \"preserve\", keep content on one line after the marker\n docs.push(indent, marker, allContent);\n }\n } else {\n docs.push(indent, firstLine);\n }\n } else {\n // Non-list item (prose that appeared before the first bullet)\n docs.push(indent, itemLines.join(\" \").replace(/\\s+/g, \" \").trim());\n }\n }\n return docs;\n }\n\n const text = lines.join(\" \").replace(/\\s+/g, \" \").trim();\n\n if (proseWrap === \"never\") {\n return [indent, text];\n }\n\n if (proseWrap === \"always\") {\n const words = text.length > 0 ? text.split(/\\s+/) : [];\n if (words.length === 0) {\n return indent;\n }\n\n const wordDocs: Doc[] = [];\n for (const [index, word] of words.entries()) {\n if (index === 0) {\n wordDocs.push(word);\n } else if (startsWithProblematicChar(word)) {\n // Words starting with # or // cannot begin a content line in thalo syntax.\n // Use a non-breaking space to keep them attached to the previous word.\n wordDocs.push(\" \", word);\n } else {\n wordDocs.push(line, word);\n }\n }\n\n // Align wrapped lines to the same indent string. align() accepts a string to\n // reuse the exact indent (spaces or tabs) rather than a fixed width.\n return [indent, align(indent, fill(wordDocs))];\n }\n\n // preserve\n const trimmedLines = lines.map((l) => l.trim());\n const [first, ...rest] = trimmedLines;\n if (rest.length === 0) {\n return [indent, first];\n }\n\n return [indent, join([hardline, indent], [first, ...rest])];\n};\n\n// ===================\n// Instance Entry Printing (create/update lore, opinion, etc.)\n// ===================\n\n/**\n * Print header fields that are inline in data_entry or schema_entry nodes.\n * The new grammar has header fields directly on the entry node rather than\n * in a separate header child node.\n */\nconst printEntryHeaderFields = (\n node: SyntaxNode,\n directiveType: \"data_directive\" | \"schema_directive\",\n): Doc => {\n const parts: Doc[] = [];\n\n for (const child of node.children) {\n if (child.type === \"timestamp\") {\n parts.push(child.text);\n } else if (child.type === directiveType) {\n parts.push(\" \", child.text);\n } else if (child.type === \"identifier\") {\n parts.push(\" \", child.text);\n } else if (child.type === \"title\") {\n parts.push(\" \", child.text);\n } else if (child.type === \"link\") {\n parts.push(\" \", child.text);\n } else if (child.type === \"tag\") {\n parts.push(\" \", child.text);\n }\n }\n\n return parts;\n};\n\nconst printMetadata = (node: SyntaxNode, indent: string): Doc => {\n const key = node.childForFieldName(\"key\");\n const value = node.childForFieldName(\"value\");\n\n if (!key || !value) {\n return \"\";\n }\n\n // Trim leading/trailing whitespace from value (grammar may capture spaces)\n return [indent, key.text, \": \", value.text.trim()];\n};\n\nconst printMarkdownHeader = (node: SyntaxNode, indent: string): Doc => {\n // Extract hashes and text from the node (new grammar uses md_indicator and md_heading_text)\n let hashes = \"\";\n let text = \"\";\n\n for (const child of node.children) {\n if (child.type === \"md_indicator\") {\n hashes = child.text;\n } else if (child.type === \"md_heading_text\") {\n text = child.text;\n }\n }\n\n // If we couldn't get structured parts, fall back to reconstructing from text\n // Note: node.text includes preceding newline from grammar's _content_line_start\n if (!hashes) {\n const trimmedText = node.text.replace(/^[\\r\\n\\s]+/, \"\").replace(/[\\r\\n]+$/, \"\");\n const match = trimmedText.match(/^(#+)\\s*(.*)$/);\n if (match) {\n hashes = match[1];\n text = \" \" + match[2];\n } else {\n return [indent, trimmedText.trim()];\n }\n }\n\n // Normalize multiple spaces to single space in header text\n text = text.replace(/ +/g, \" \");\n\n return [indent, hashes, text];\n};\n\nconst printCommentLine = (node: SyntaxNode, indent: string): Doc => {\n // comment_line contains a comment child\n const comment = node.children.find((c) => c.type === \"comment\");\n if (comment) {\n return [indent, comment.text];\n }\n // Fallback: extract comment from node text\n const text = node.text.replace(/^[\\r\\n\\s]+/, \"\").replace(/[\\r\\n]+$/, \"\");\n return [indent, text];\n};\n\nconst printContent = (\n node: SyntaxNode,\n options: ThaloOptions,\n indent: string,\n insertBlankLineBeforeContent: boolean,\n): Doc => {\n const parts: Doc[] = [];\n\n // Get visible content children (markdown_header, content_line, and comment_line)\n const contentChildren = node.children.filter(\n (c) => c.type === \"markdown_header\" || c.type === \"content_line\" || c.type === \"comment_line\",\n );\n\n let lastRow = -1;\n let paragraphLines: string[] = [];\n\n const flushParagraph = () => {\n if (paragraphLines.length === 0) {\n return;\n }\n parts.push(hardline, formatParagraph(paragraphLines, options, indent));\n paragraphLines = [];\n };\n\n for (const child of contentChildren) {\n const currentRow = child.startPosition.row;\n\n if (lastRow >= 0) {\n // Check for blank lines between elements by comparing row numbers\n const rowGap = currentRow - lastRow;\n if (rowGap > 1) {\n flushParagraph();\n // Add blank lines for gaps (rowGap - 1 blank lines)\n for (let i = 0; i < rowGap - 1; i++) {\n parts.push(hardline);\n }\n }\n }\n\n if (child.type === \"markdown_header\") {\n flushParagraph();\n parts.push(hardline, printMarkdownHeader(child, indent));\n } else if (child.type === \"comment_line\") {\n flushParagraph();\n parts.push(hardline, printCommentLine(child, indent));\n } else {\n paragraphLines.push(getContentLineText(child));\n }\n\n lastRow = currentRow;\n }\n\n flushParagraph();\n\n if (parts.length === 0) {\n return \"\";\n }\n\n // `parts` always begins with a `hardline` (because each child is printed as `[hardline, ...]`).\n // If we want a *blank line* before content, prepend an extra `hardline`.\n return insertBlankLineBeforeContent ? [hardline, ...parts] : parts;\n};\n\n/**\n * Print a data_entry node (handles create, update, define-synthesis, actualize-synthesis).\n * Header fields are inline on the entry node in the new grammar.\n */\nconst printDataEntry = (node: SyntaxNode, options: ThaloOptions, indent: string): Doc => {\n const parts: Doc[] = [];\n\n // Print header fields directly from the entry node\n parts.push(printEntryHeaderFields(node, \"data_directive\"));\n\n // Handle metadata and comment_line nodes (they can be interleaved)\n const metadataAndComments = node.children.filter(\n (c) => c.type === \"metadata\" || c.type === \"comment_line\",\n );\n // If there are any lines between header and content (metadata or indented comments),\n // keep a blank line before the first section. If there are none, don't insert an\n // empty line between the header and the first section.\n const hasHeaderPreludeLines = metadataAndComments.length > 0;\n for (const child of metadataAndComments) {\n if (child.type === \"metadata\") {\n parts.push(hardline, printMetadata(child, indent));\n } else {\n parts.push(hardline, printCommentLine(child, indent));\n }\n }\n\n const content = node.children.find((c) => c.type === \"content\");\n if (content) {\n // If there is no metadata, don't insert an empty line between the header and the first section.\n parts.push(printContent(content, options, indent, hasHeaderPreludeLines));\n }\n\n return parts;\n};\n\n// ===================\n// Schema Entry Printing (define-entity/alter-entity)\n// ===================\n\nconst printTypeExpression = (node: SyntaxNode): Doc => {\n const parts: Doc[] = [];\n\n for (const child of node.children) {\n if (child.type === \"union_type\") {\n parts.push(printUnionType(child));\n } else if (child.type === \"array_type\") {\n parts.push(printArrayType(child));\n } else if (child.type === \"primitive_type\") {\n parts.push(child.text);\n } else if (child.type === \"literal_type\") {\n parts.push(child.text);\n }\n }\n\n return parts;\n};\n\nconst printUnionType = (node: SyntaxNode): Doc => {\n const typeTerms: Doc[] = [];\n\n for (const child of node.children) {\n if (child.type === \"array_type\") {\n typeTerms.push(printArrayType(child));\n } else if (child.type === \"primitive_type\" || child.type === \"literal_type\") {\n typeTerms.push(child.text);\n }\n }\n\n return join(\" | \", typeTerms);\n};\n\nconst printArrayType = (node: SyntaxNode): Doc => {\n for (const child of node.children) {\n if (child.type === \"primitive_type\" || child.type === \"literal_type\") {\n return [child.text, \"[]\"];\n }\n }\n return node.text;\n};\n\nconst printFieldDefinition = (node: SyntaxNode, indent: string): Doc => {\n const parts: Doc[] = [indent];\n\n // Field name (includes newline+indent in token, extract just the name)\n const fieldName = node.children.find((c) => c.type === \"field_name\");\n if (fieldName) {\n // Extract just the field name from the token (which includes \\n and indent)\n const nameMatch = fieldName.text.match(/[a-z][a-zA-Z0-9\\-_]*/);\n if (nameMatch) {\n parts.push(nameMatch[0]);\n }\n }\n\n // Optional marker\n const optionalMarker = node.children.find((c) => c.type === \"optional_marker\");\n if (optionalMarker) {\n parts.push(\"?\");\n }\n\n parts.push(\": \");\n\n // Type expression\n const typeExpr = node.childForFieldName(\"type\");\n if (typeExpr) {\n parts.push(printTypeExpression(typeExpr));\n }\n\n // Default value\n const defaultValue = node.childForFieldName(\"default\");\n if (defaultValue) {\n parts.push(\" = \");\n const literal = defaultValue.children.find((c) => c.type === \"literal_type\");\n if (literal) {\n parts.push(literal.text.trim());\n } else {\n parts.push(defaultValue.text.trim());\n }\n }\n\n // Description\n const description = node.childForFieldName(\"description\");\n if (description) {\n parts.push(\" ; \", description.text);\n }\n\n return parts;\n};\n\nconst printSectionDefinition = (node: SyntaxNode, indent: string): Doc => {\n const parts: Doc[] = [indent];\n\n // Section name (includes newline+indent in token, extract just the name)\n const sectionName = node.children.find((c) => c.type === \"section_name\");\n if (sectionName) {\n // Extract just the section name from the token (which includes \\n and indent)\n // Section names can have spaces: \"Key Takeaways\", \"Related Items\", etc.\n // Match allows 1+ spaces between words, then normalize to single space\n const nameMatch = sectionName.text.match(/[A-Z][a-zA-Z0-9]*(?: +[a-zA-Z0-9]+)*/);\n if (nameMatch) {\n // Normalize multiple spaces to single space\n parts.push(nameMatch[0].replace(/ +/g, \" \"));\n }\n }\n\n // Optional marker\n const optionalMarker = node.children.find((c) => c.type === \"optional_marker\");\n if (optionalMarker) {\n parts.push(\"?\");\n }\n\n // Description\n const description = node.childForFieldName(\"description\");\n if (description) {\n parts.push(\" ; \", description.text);\n }\n\n return parts;\n};\n\nconst printFieldRemoval = (node: SyntaxNode, indent: string): Doc => {\n const parts: Doc[] = [indent];\n\n // Field name\n const fieldName = node.children.find((c) => c.type === \"field_name\");\n if (fieldName) {\n const nameMatch = fieldName.text.match(/[a-z][a-zA-Z0-9\\-_]*/);\n if (nameMatch) {\n parts.push(nameMatch[0]);\n }\n }\n\n // Reason (description)\n const reason = node.childForFieldName(\"reason\");\n if (reason) {\n parts.push(\" ; \", reason.text);\n }\n\n return parts;\n};\n\nconst printSectionRemoval = (node: SyntaxNode, indent: string): Doc => {\n const parts: Doc[] = [indent];\n\n // Section name\n const sectionName = node.children.find((c) => c.type === \"section_name\");\n if (sectionName) {\n // Section names can have spaces: \"Key Takeaways\", \"Related Items\", etc.\n // Match allows 1+ spaces between words, then normalize to single space\n const nameMatch = sectionName.text.match(/[A-Z][a-zA-Z0-9]*(?: +[a-zA-Z0-9]+)*/);\n if (nameMatch) {\n // Normalize multiple spaces to single space\n parts.push(nameMatch[0].replace(/ +/g, \" \"));\n }\n }\n\n // Reason (description)\n const reason = node.childForFieldName(\"reason\");\n if (reason) {\n parts.push(\" ; \", reason.text);\n }\n\n return parts;\n};\n\nconst printMetadataBlock = (node: SyntaxNode, indent: string): Doc => {\n const parts: Doc[] = [hardline, indent, \"# Metadata\"];\n\n const fieldDefs = node.children.filter((c) => c.type === \"field_definition\");\n for (const field of fieldDefs) {\n parts.push(hardline, printFieldDefinition(field, indent));\n }\n\n return parts;\n};\n\nconst printSectionsBlock = (node: SyntaxNode, indent: string): Doc => {\n const parts: Doc[] = [hardline, indent, \"# Sections\"];\n\n const sectionDefs = node.children.filter((c) => c.type === \"section_definition\");\n for (const section of sectionDefs) {\n parts.push(hardline, printSectionDefinition(section, indent));\n }\n\n return parts;\n};\n\nconst printRemoveMetadataBlock = (node: SyntaxNode, indent: string): Doc => {\n const parts: Doc[] = [hardline, indent, \"# Remove Metadata\"];\n\n const fieldRemovals = node.children.filter((c) => c.type === \"field_removal\");\n for (const removal of fieldRemovals) {\n parts.push(hardline, printFieldRemoval(removal, indent));\n }\n\n return parts;\n};\n\nconst printRemoveSectionsBlock = (node: SyntaxNode, indent: string): Doc => {\n const parts: Doc[] = [hardline, indent, \"# Remove Sections\"];\n\n const sectionRemovals = node.children.filter((c) => c.type === \"section_removal\");\n for (const removal of sectionRemovals) {\n parts.push(hardline, printSectionRemoval(removal, indent));\n }\n\n return parts;\n};\n\n/**\n * Print a schema_entry node (handles define-entity, alter-entity).\n * Header fields are inline on the entry node in the new grammar.\n */\nconst printSchemaEntry = (node: SyntaxNode, _options: ThaloOptions, indent: string): Doc => {\n const parts: Doc[] = [];\n\n // Print header fields directly from the entry node\n parts.push(printEntryHeaderFields(node, \"schema_directive\"));\n\n // Track whether we've printed a block (for adding blank lines between blocks)\n let hasBlockBefore = false;\n\n // Print blocks in order they appear\n for (const child of node.children) {\n if (child.type === \"metadata_block\") {\n parts.push(printMetadataBlock(child, indent));\n hasBlockBefore = true;\n } else if (child.type === \"sections_block\") {\n // Add blank line before # Sections if there's a preceding block\n if (hasBlockBefore) {\n parts.push(hardline);\n }\n parts.push(printSectionsBlock(child, indent));\n hasBlockBefore = true;\n } else if (child.type === \"remove_metadata_block\") {\n parts.push(printRemoveMetadataBlock(child, indent));\n hasBlockBefore = true;\n } else if (child.type === \"remove_sections_block\") {\n // Add blank line before # Remove Sections if there's a preceding block\n if (hasBlockBefore) {\n parts.push(hardline);\n }\n parts.push(printRemoveSectionsBlock(child, indent));\n hasBlockBefore = true;\n }\n }\n\n return parts;\n};\n\n// ===================\n// Entry Printing (dispatches to instance or schema)\n// ===================\n\nconst printEntry = (node: SyntaxNode, options: ThaloOptions, indent: string): Doc => {\n const dataEntry = node.children.find((c) => c.type === \"data_entry\");\n if (dataEntry) {\n return printDataEntry(dataEntry, options, indent);\n }\n\n const schemaEntry = node.children.find((c) => c.type === \"schema_entry\");\n if (schemaEntry) {\n return printSchemaEntry(schemaEntry, options, indent);\n }\n\n // For unhandled entry types, preserve the original text\n return node.text;\n};\n\nconst printComment = (node: SyntaxNode, indent: string): Doc => {\n // Use column position to determine if comment was indented\n const isIndented = node.startPosition.column > 0;\n return isIndented ? [indent, node.text] : node.text;\n};\n\nconst printUnhandledNode = (node: SyntaxNode): Doc => {\n // Preserve unhandled nodes exactly as they appear in source\n return node.text;\n};\n\nconst printSourceFile = (node: SyntaxNode, options: ThaloOptions, indent: string): Doc => {\n // Check for errors - if present, return original source unchanged\n // Tree-sitter error recovery can produce structurally broken trees where\n // content gets detached from its parent entry, losing indentation context.\n // Safest approach: don't format files with syntax errors.\n const rootNode = node as ThaloRootNode;\n if (rootNode._thaloHasErrors) {\n // Return original source unchanged (with trailing newline for consistency)\n // The format command handles error reporting, so we don't output anything here.\n const source = rootNode._thaloSource ?? \"\";\n return source.endsWith(\"\\n\") ? source : source + \"\\n\";\n }\n\n // Get all non-whitespace children\n const relevantChildren = node.children.filter((c) => c.type !== \"\");\n\n if (relevantChildren.length === 0) {\n return \"\";\n }\n\n // Build output preserving comments and entries with proper spacing\n const docs: Doc[] = [];\n let lastWasEntry = false;\n let lastWasIndentedComment = false;\n\n for (const child of relevantChildren) {\n if (child.type === \"comment\") {\n const isIndented = child.startPosition.column > 0;\n\n if (isIndented) {\n // Indented comment - belongs to preceding entry, no blank line\n docs.push(hardline, printComment(child, indent));\n lastWasIndentedComment = true;\n } else {\n // Top-level comment - add blank line after entry\n if (lastWasEntry || lastWasIndentedComment) {\n docs.push(hardline, hardline);\n } else if (docs.length > 0) {\n docs.push(hardline);\n }\n docs.push(printComment(child, indent));\n lastWasIndentedComment = false;\n }\n lastWasEntry = false;\n } else if (child.type === \"entry\") {\n // Entry - add blank line between entries/after comments\n if (docs.length > 0) {\n docs.push(hardline, hardline);\n }\n docs.push(printEntry(child, options, indent));\n lastWasEntry = true;\n lastWasIndentedComment = false;\n } else {\n // Unhandled node type - preserve as-is with proper spacing\n if (docs.length > 0) {\n docs.push(hardline, hardline);\n }\n docs.push(printUnhandledNode(child));\n lastWasEntry = true; // Treat as entry for spacing purposes\n lastWasIndentedComment = false;\n }\n }\n\n return [...docs, hardline];\n};\n\nexport const printer: ThaloPrinter = {\n print(path: AstPath<SyntaxNode>, options: ThaloOptions, _print): Doc {\n const node = path.node;\n const indent = getIndent(options);\n\n switch (node.type) {\n case \"source_file\":\n return printSourceFile(node, options, indent);\n case \"entry\":\n return printEntry(node, options, indent);\n case \"data_entry\":\n return printDataEntry(node, options, indent);\n case \"schema_entry\":\n return printSchemaEntry(node, options, indent);\n case \"metadata\":\n return printMetadata(node, indent);\n case \"content\":\n return printContent(node, options, indent, true);\n case \"markdown_header\":\n return printMarkdownHeader(node, indent);\n case \"content_line\":\n return printContentLine(node, options, indent);\n case \"comment_line\":\n return printCommentLine(node, indent);\n case \"comment\":\n return printComment(node, indent);\n case \"metadata_block\":\n return printMetadataBlock(node, indent);\n case \"sections_block\":\n return printSectionsBlock(node, indent);\n case \"remove_metadata_block\":\n return printRemoveMetadataBlock(node, indent);\n case \"remove_sections_block\":\n return printRemoveSectionsBlock(node, indent);\n case \"field_definition\":\n return printFieldDefinition(node, indent);\n case \"section_definition\":\n return printSectionDefinition(node, indent);\n case \"field_removal\":\n return printFieldRemoval(node, indent);\n case \"section_removal\":\n return printSectionRemoval(node, indent);\n case \"type_expression\":\n return printTypeExpression(node);\n case \"union_type\":\n return printUnionType(node);\n case \"array_type\":\n return printArrayType(node);\n default:\n // For any unhandled node, just return its text\n return node.text;\n }\n },\n};\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@rejot-dev/thalo-prettier",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/rejot-dev/thalo.git",
8
+ "directory": "packages/thalo-prettier"
9
+ },
10
+ "license": "MIT",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "exports": {
15
+ ".": {
16
+ "development": "./src/mod.ts",
17
+ "types": "./dist/mod.d.ts",
18
+ "default": "./dist/mod.js"
19
+ }
20
+ },
21
+ "main": "./dist/mod.js",
22
+ "types": "./dist/mod.d.ts",
23
+ "dependencies": {
24
+ "tree-sitter": "^0.25.0",
25
+ "@rejot-dev/tree-sitter-thalo": "0.0.0"
26
+ },
27
+ "peerDependencies": {
28
+ "prettier": "^3.0.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^24",
32
+ "prettier": "^3.5.3",
33
+ "tsdown": "^0.15.12",
34
+ "typescript": "^5.7.3",
35
+ "vitest": "^3.2.4",
36
+ "@rejot-private/typescript-config": "0.0.1"
37
+ },
38
+ "scripts": {
39
+ "dev": "tsx src/index.ts",
40
+ "build": "tsdown",
41
+ "build:watch": "tsdown --watch",
42
+ "types:check": "tsc --noEmit",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest --watch"
45
+ }
46
+ }