Skip to content

Commit f625d73

Browse files
Added support for end of line setting
1 parent 5841cde commit f625d73

10 files changed

Lines changed: 111 additions & 19 deletions

src/lib/talonFormatter.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import type { Node } from "web-tree-sitter";
2+
import type { EndOfLine } from "../types.js";
3+
import { DEFAULT_LINE_WIDTH } from "../util/constants.js";
24
import { getColumnWidth } from "../util/getColumnWidth.js";
5+
import { getEndOfLine } from "../util/getEndOfLine.js";
36
import { getIndentation } from "../util/getIndentation.js";
4-
import { DEFAULT_LINE_WIDTH } from "../util/constants.js";
57

68
interface Options {
9+
readonly endOfLine?: EndOfLine;
710
readonly indentTabs?: boolean;
811
readonly indentWidth?: number;
912
readonly lineWidth?: number;
@@ -13,8 +16,10 @@ interface Options {
1316
export function talonFormatter(node: Node, options: Options = {}): string {
1417
const columnWidth = getColumnWidth(node.text) ?? options.columnWidth;
1518
const indentation = getIndentation(options.indentTabs, options.indentWidth);
19+
const eol = getEndOfLine(options.endOfLine);
1620
const formatter = new TalonFormatter(
1721
indentation,
22+
eol,
1823
options.lineWidth ?? DEFAULT_LINE_WIDTH,
1924
columnWidth,
2025
);
@@ -26,12 +31,13 @@ class TalonFormatter {
2631

2732
constructor(
2833
private indent: string,
34+
private eol: string,
2935
private lineWidth: number,
3036
private columnWidth: number | undefined,
3137
) {}
3238

3339
getText(node: Node): string {
34-
return this.getNodeText(node) + "\n";
40+
return this.getNodeText(node) + this.eol;
3541
}
3642

3743
private getLeftRightText(node: Node): string {
@@ -53,12 +59,12 @@ class TalonFormatter {
5359

5460
const right = rightNodes
5561
.map((n) => this.getNodeText(n, true))
56-
.join("\n");
57-
return `${left}:\n${right}`;
62+
.join(this.eol);
63+
return `${left}:${this.eol}${right}`;
5864
}
5965

6066
private getNodeText(node: Node, isIndented = false): string {
61-
const nl = node.startPosition.row > this.lastRow + 1 ? "\n" : "";
67+
const nl = node.startPosition.row > this.lastRow + 1 ? this.eol : "";
6268
this.lastRow = node.endPosition.row;
6369
const text = this.getNodeTextInternal(node, isIndented);
6470
this.lastRow = node.endPosition.row;
@@ -82,25 +88,29 @@ class TalonFormatter {
8288
return node.children
8389
.map((n) => this.getNodeText(n))
8490
.filter(Boolean)
85-
.join("\n");
91+
.join(this.eol);
8692

8793
case "matches": {
8894
if (node.children.length < 2) {
8995
return "";
9096
}
91-
return node.children.map((n) => this.getNodeText(n)).join("\n");
97+
return node.children
98+
.map((n) => this.getNodeText(n))
99+
.join(this.eol);
92100
}
93101

94102
case "declarations":
95-
return node.children.map((n) => this.getNodeText(n)).join("\n");
103+
return node.children
104+
.map((n) => this.getNodeText(n))
105+
.join(this.eol);
96106

97107
case "match":
98108
return node.children.map((n) => this.getNodeText(n)).join("");
99109

100110
case "block":
101111
return node.children
102112
.map((n) => this.getNodeText(n, isIndented))
103-
.join("\n");
113+
.join(this.eol);
104114

105115
case "command_declaration":
106116
case "key_binding_declaration":

src/lib/talonListFormatter.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import type { EndOfLine } from "../types.js";
12
import { getColumnWidth } from "../util/getColumnWidth.js";
3+
import { getEndOfLine } from "../util/getEndOfLine.js";
24
import { parseTalonList } from "./parseTalonList.js";
35

46
interface Options {
7+
readonly endOfLine?: EndOfLine;
58
readonly columnWidth?: number;
69
}
710

@@ -10,6 +13,7 @@ export function talonListFormatter(
1013
options: Options = {},
1114
): string {
1215
const columnWidth = getColumnWidth(text) ?? options.columnWidth;
16+
const eol = getEndOfLine(options.endOfLine);
1317
const talonList = parseTalonList(text);
1418
talonList.headers.sort((a, _b) =>
1519
a.type === "header" && a.key === "list" ? -1 : 0,
@@ -48,5 +52,5 @@ export function talonListFormatter(
4852

4953
result.push("");
5054

51-
return result.join("\n");
55+
return result.join(eol);
5256
}

src/lib/treeSitterFormatter.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,35 @@
11
import type { Node } from "web-tree-sitter";
2+
import type { EndOfLine } from "../types.js";
3+
import { getEndOfLine } from "../util/getEndOfLine.js";
24
import { getIndentation } from "../util/getIndentation.js";
35

46
interface Options {
7+
readonly endOfLine?: EndOfLine;
58
readonly indentTabs?: boolean;
69
readonly indentWidth?: number;
710
}
811

912
export function treeSitterFormatter(node: Node, options: Options = {}): string {
1013
const indentation = getIndentation(options.indentTabs, options.indentWidth);
11-
const formatter = new TreeSitterFormatter(indentation);
14+
const eol = getEndOfLine(options.endOfLine);
15+
const formatter = new TreeSitterFormatter(indentation, eol);
1216
return formatter.getText(node);
1317
}
1418

1519
export class TreeSitterFormatter {
1620
private lastRow = 0;
1721

18-
constructor(private indentation: string) {}
22+
constructor(
23+
private indentation: string,
24+
private eol: string,
25+
) {}
1926

2027
getText(node: Node): string {
21-
return this.getNodeText(node, 0) + "\n";
28+
return this.getNodeText(node, 0) + this.eol;
2229
}
2330

2431
private getNodeText(node: Node, numIndents: number): string {
25-
const nl = node.startPosition.row > this.lastRow + 1 ? "\n" : "";
32+
const nl = node.startPosition.row > this.lastRow + 1 ? this.eol : "";
2633
this.lastRow = node.endPosition.row;
2734
const text = this.getNodeTextInternal(node, numIndents);
2835
this.lastRow = node.endPosition.row;
@@ -51,7 +58,7 @@ export class TreeSitterFormatter {
5158
`${this.getIndent(numIndents)}${first}`,
5259
...interior,
5360
`${this.getIndent(numIndents)}${last}`,
54-
].join("\n");
61+
].join(this.eol);
5562
}
5663

5764
private getListText(node: Node, numIndents: number): string {
@@ -68,7 +75,7 @@ export class TreeSitterFormatter {
6875
.map((n) => this.getNodeText(n, numIndents + 1)),
6976
`${this.getIndent(numIndents)}${last}`,
7077
];
71-
return parts.join("\n");
78+
return parts.join(this.eol);
7279
}
7380

7481
private getPredicateText(node: Node, numIndents: number): string {
@@ -95,13 +102,15 @@ export class TreeSitterFormatter {
95102
.slice(1)
96103
.map((s) => `${this.getIndent(numIndents + 1)}${s}`),
97104
`${this.getIndent(numIndents)}${last}`,
98-
].join("\n");
105+
].join(this.eol);
99106
}
100107

101108
private getFieldDefinitionText(node: Node, numIndents: number): string {
102109
// Field definition directly in document root
103110
if (numIndents === 0) {
104-
return ["(_", this.getFieldDefinitionText(node, 1), ")"].join("\n");
111+
return ["(_", this.getFieldDefinitionText(node, 1), ")"].join(
112+
this.eol,
113+
);
105114
}
106115
// [lhs, ":", rhs]
107116
return [
@@ -182,7 +191,7 @@ export class TreeSitterFormatter {
182191
const nodesToUse = lastIsQuantifier ? nodes.slice(0, -1) : nodes;
183192
const text = nodesToUse
184193
.map((n) => this.getNodeText(n, numIndents))
185-
.join("\n");
194+
.join(this.eol);
186195
return lastIsQuantifier
187196
? `${text}${nodes[nodes.length - 1].text}`
188197
: text;

src/test/cli.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,31 @@ suite("CLI", () => {
172172
}
173173
});
174174

175+
test("Passes end of line from .editorconfig", async () => {
176+
const fileName = await createTempFile(
177+
"talonfmt-",
178+
"example.talon",
179+
"content",
180+
);
181+
const cli = createCLI(
182+
(_text, options) => `endOfLine=${options.endOfLine ?? "unset"}`,
183+
);
184+
185+
try {
186+
await writeEditorConfig(fileName, {
187+
end_of_line: "crlf",
188+
});
189+
190+
const didChange = await formatFile(cli, false, fileName);
191+
const actual = await fs.readFile(fileName, "utf8");
192+
193+
assert.equal(didChange, true);
194+
assert.equal(actual, "endOfLine=crlf");
195+
} finally {
196+
await cleanupTempFile(fileName);
197+
}
198+
});
199+
175200
test("Wraps formatter errors", async () => {
176201
const fileName = await createTempFile(
177202
"talonfmt-",

src/test/talonFormatter.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,19 @@ suite("Talon formatter", () => {
217217

218218
assert.equal(actual, `foo:\n ${right}\n`);
219219
});
220+
221+
test("Uses CRLF when requested", async () => {
222+
const rootNode = await parseText(
223+
"foo:\n edit.left()",
224+
"tree-sitter-talon",
225+
);
226+
227+
const actual = talonFormatter(rootNode, {
228+
endOfLine: "crlf",
229+
});
230+
231+
assert.equal(actual, "foo:\r\n edit.left()\r\n");
232+
});
220233
});
221234

222235
function getContentString(content: Content): string {

src/test/talonListFormatter.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,13 @@ suite("Talon list formatter", () => {
6464
assert.equal(actual, fixture.post);
6565
});
6666
}
67+
68+
test("Uses CRLF when requested", () => {
69+
const actual = talonListFormatter("list: l\n-\na:b", {
70+
columnWidth: 10,
71+
endOfLine: "crlf",
72+
});
73+
74+
assert.equal(actual, "list: l\r\n-\r\n\r\na: b\r\n");
75+
});
6776
});

src/test/treeSitterFormatter.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,16 @@ suite("Tree-sitter formatter", () => {
117117

118118
assert.equal(actual, "(aaa\n\t(bbb)\n)\n");
119119
});
120+
121+
test("uses CRLF when requested", async () => {
122+
const rootNode = await parseText("(aaa (bbb))", "tree-sitter-query");
123+
124+
const actual = treeSitterFormatter(rootNode, {
125+
endOfLine: "crlf",
126+
});
127+
128+
assert.equal(actual, "(aaa\r\n (bbb)\r\n)\r\n");
129+
});
120130
});
121131

122132
function getContentString(content: Content): string {

src/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ export interface CLI {
1212
format(text: string, options: Options, filePath: string): Promise<string>;
1313
}
1414

15+
export type EndOfLine = "lf" | "crlf";
16+
1517
export interface Options {
1618
indentTabs?: boolean;
1719
indentSize?: number;
1820
maxLineLength?: number;
1921
columnWidth?: number;
22+
endOfLine?: EndOfLine;
2023
}
2124

2225
export interface ParsedArgs {

src/util/getEndOfLine.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { EndOfLine } from "../types.js";
2+
3+
export function getEndOfLine(eof?: EndOfLine): string {
4+
return eof === "crlf" ? "\r\n" : "\n";
5+
}

src/util/getOptionsFromConfig.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,9 @@ export async function getOptionsFromConfig(filePath: string): Promise<Options> {
2929
options.columnWidth = config.column_width;
3030
}
3131

32+
if (config.end_of_line != null && config.end_of_line !== "unset") {
33+
options.endOfLine = config.end_of_line;
34+
}
35+
3236
return options;
3337
}

0 commit comments

Comments
 (0)