# Formatting

> Format templates from the command line or JavaScript.

Kiln has a formatter. It indents templates to match their structure and tidies the spacing inside tags, so every template in a project looks the same. It only changes whitespace that HTML ignores, so pages look the same before and after.

```kiln
{# Before #}
<ul>
{% for post in posts %}
      <li><a href="{{post.url}}">{{   post.title|upper }}</a></li>
{%endfor%}
</ul>

{# After #}
<ul>
	{% for post in posts %}
		<li><a href="{{ post.url }}">{{ post.title|upper }}</a></li>
	{% endfor %}
</ul>
```

## Running it

Run `kiln format` with the files or folders to format. Folders are searched for `.kiln` files.

```sh
npx kiln format templates
npx kiln format --check templates
```

- Without `--check`, it rewrites files that need formatting and lists them.
- With `--check`, it changes nothing. It lists files that need formatting and exits with code 1 if there are any, which suits CI and pre-commit hooks.
- Files and folders whose names start with a dot, and `node_modules`, are skipped. So are files you name that aren't `.kiln` files, with a warning.
- A file named twice, or named and also inside a folder you name, is formatted once.
- A path that doesn't exist is reported, and nothing is formatted. It exits with code 2.
- A file it can't format is reported, and the other files are still formatted. It exits with code 1.
- Indentation uses tabs. Pass `--spaces 2`, or any other width, to indent with spaces.

Editors format through the language server. See [Editor support](https://kilnlang.com/docs/tooling/editors/).

## What it changes

- **Indentation.** Each line is indented one step for every block tag and HTML element it sits inside.
- **Spacing inside delimiters.** One space inside `{{ }}` and `{% %}`, and consistent spacing around operators, commas and colons. Trim markers such as `{%-` stay where they are.
- **Blank lines.** Runs of blank lines become one.
- **Line ends.** Trailing spaces are removed, and line breaks become `\n`.

It never joins or splits lines, rewraps text or reorders anything. A line break inside a tag stays, and the lines after it are indented one step further, or to match the brackets they are inside:

```kiln
{% include "components/card" with {
	title: post.title,
	url: post.url,
} %}
```

### Expressions

Inside delimiters, the formatter puts:

- one space either side of operators such as `==`, `+`, `~`, `??`, `=` and `=>`, and of the `?` and `:` in `a ? b : c`.
- no space around `.`, `?.`, `|` and `..`, so you get `post.title|upper` and `1..10`.
- no space after `(` or `[` or before `)` or `]`, and none before a call's brackets: `range(1, 3)`.
- a space after a comma, and after the colon in `name: value`, but not before them.
- a space inside a hash's braces: `{ title: "Home" }`. An empty hash stays `{}`.
- no space after a minus or plus sign that makes a number negative or positive: `-1`.

In `props`, `|` joins types, so it gets a space either side: `variant: "primary" | "secondary"`. Strings are left exactly as written, quotes and all.

### HTML

- Void elements such as `<br>` and `<img>`, and self-closing tags, don't indent what follows.
- An element that the next sibling closes, such as a `<li>` without `</li>`, doesn't indent the next one.
- Attributes on lines of their own are indented one step from their tag. A `>` on a line of its own lines up with the tag.
- An element opened inside a block tag counts only until that block, or that branch of an `if`, ends. Templates that open an element in one branch and close it in another format without errors.

### What it leaves alone

- Everything inside `<pre>`, `<textarea>`, `<xmp>`, `<listing>`, `<plaintext>` and `verbatim`, because whitespace there shows on the page.
- The body of a `set` or `apply` whose tag shares its line with other content. Kiln keeps its whitespace, so changing it would change the value. When the tag stands on its own line, Kiln removes the body's shared indentation, and the formatter indents it.
- Whitespace and blank lines before the first content in the file and after the last. They show wherever the template is placed, such as an icon included inside a link. A file ends with a line break only if it did before, and a file of only whitespace is left as it is.
- Text inside attribute values.
- The lines inside `<script>`, `<style>`, HTML comments and Kiln comments. They move with their opening line, but keep their own indentation relative to it.

## Safety

The formatter only changes whitespace and the spacing inside delimiters. To prove it, it parses the template before and after, and compares the two. If anything differs apart from whitespace at the start and end of lines and runs of blank lines, it changes nothing and reports an error. Please [open an issue](https://github.com/buildlucid/kiln/issues) if you see one.

Whitespace at the start of a line can still change in the output, because the formatter assumes the template writes HTML, where it doesn't show. If a template writes plain text, such as an email's text version, or is included inside a `<pre>` in another template, check the output after formatting.

A template with syntax errors isn't formatted. Fix the errors first. `kiln format` lists them.

## From JavaScript

`format` comes from its own entry point, `@lucidkiln/core/format`. Like the rest of Kiln, it runs anywhere, including in the browser and on Workers.

```ts
import { format } from "@lucidkiln/core/format";

const formatted = format(source);
const withSpaces = format(source, { useTabs: false, tabWidth: 2 });
```

| Option | Description |
| --- | --- |
| `useTabs` | Indent with tabs. Default `true`. |
| `tabWidth` | Spaces per step when `useTabs` is false, and the width of a tab when lining up the lines of a script or comment. A whole number of at least 1. Default `4`. |
| `range` | Only format lines that overlap this range, given as `{ start, end }` offsets. Editors use it to format a selection. |

`format` throws a `KilnSyntaxError` when the template has syntax errors, a `KilnFormatError` if the safety check fails, and a `RangeError` if `tabWidth` isn't a whole number of at least 1.

`formatEdits` takes the same arguments and returns the changes as a list of `{ start, end, text }` edits, from first to last, instead of the new source. Editors apply them so the cursor and undo history stay put.
