defmodule Surface.Formatter do @moduledoc "Functions for formatting Surface code snippets." alias Surface.Formatter.Phases @typedoc """ Options that can be passed to `Surface.Formatter.format_string!/2`. - `:line_length` - Maximum line length before wrapping opening tags - `:indent` - Starting indentation depth depending on the context of the ~F sigil """ @type option :: {:line_length, integer} | {:indent, integer} @typedoc """ The name of an HTML/Surface tag, such as `div`, `ListItem`, or `#Markdown`. """ @type tag :: String.t() @typedoc "The value of a parsed HTML/Component attribute." @type attribute_value :: integer | boolean | String.t() | {:attribute_expr, interpolated_expression :: String.t(), term} | [String.t()] @typedoc "A parsed HTML/Component attribute name and value." @type attribute :: {name :: String.t(), attribute_value, term} @typedoc "A node output by `Surface.Compiler.Parser.parse`." @type surface_node :: String.t() | {:interpolation, String.t(), map} | {tag, list(attribute), list(surface_node), map} @typedoc """ Whitespace nodes that can be rendered by `Surface.Formatter.Render.node/2`. The Surface parser does not return these, but formatter phases introduce these nodes in preparation for rendering. - `:newline` adds a newline (`\\n`) character - `:space` adds a space (` `) character - `:indent` adds spaces at the appropriate indentation amount - `:indent_one_less` adds spaces at 1 indentation level removed (used for closing tags) """ @type whitespace :: :newline | :space | :indent | :indent_one_less @typedoc """ A node that will ultimately be sent to `Surface.Formatter.Render.node/2` for rendering. The output of `Surface.Compiler.Parser.parse` is ran through the various formatting phases, which ultimately output a tree of this type. """ @type formatter_node :: surface_node | whitespace @doc """ Formats the given Surface code string. (Typically the contents of an `~F` sigil or `.sface` file.) In short: - HTML/Surface elements are indented to the right of their parents. - Attributes are split on multiple lines if the line is too long; otherwise on the same line. - Elixir code snippets (inside `{ }`) are ran through the Elixir code formatter. - Lack of whitespace is preserved, so that intended behaviors are not removed. (For example, `Foo bar baz` will not have newlines or spaces added.) Below the **Options** section is a non-exhaustive list of behaviors of the formatter. # Options * `:line_length` - the line length to aim for when formatting the document. Defaults to 98. As with the Elixir formatter, this value is used as reference but is not always enforced depending on the context. # Indentation The formatter ensures that children are indented one tab (two spaces) in from their parent. # Whitespace ## Whitespace that exists As in regular HTML, any string of continuous whitespace is considered equivalent to any other string of continuous whitespace. There are four exceptions: 1. Macro components (with names starting with `#`, such as `<#Markdown>`) 2. `
` tags
3. `` tags
4. `