Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Add to your `mix.exs`:
```elixir
def deps do
[
{:dprint_markdown_formatter, "~> 0.4.0"}
{:dprint_markdown_formatter, "~> 0.5.0"}
]
end
```
Expand Down
82 changes: 82 additions & 0 deletions lib/dprint_markdown_formatter/ast_processor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,27 @@ defmodule DprintMarkdownFormatter.AstProcessor do
acc
)

# Handle sigil patterns: @moduledoc ~S"""content""" or @moduledoc ~s/content/
{:@, _meta,
[
{attr, _attr_meta,
[
{sigil_type, sigil_meta,
[{:<<>>, _binary_meta, [doc_content]} = binary_node, _modifiers]} =
sigil_node
]}
]}
when is_atom(attr) and is_binary(doc_content) and sigil_type in [:sigil_S, :sigil_s] ->
process_sigil_doc_attribute(
node,
attr,
doc_content,
%{sigil: sigil_node, binary: binary_node, type: sigil_type, meta: sigil_meta},
doc_attributes,
nif_config,
acc
)

_other_node ->
{node, acc}
end
Expand Down Expand Up @@ -148,6 +169,67 @@ defmodule DprintMarkdownFormatter.AstProcessor do
end
end

defp process_sigil_doc_attribute(
node,
attr,
doc_content,
%{sigil: sigil_node, binary: binary_node, type: sigil_type, meta: sigil_meta},
doc_attributes,
nif_config,
acc
) do
if attr in doc_attributes do
case format_markdown_content(doc_content, nif_config) do
{:ok, formatted} when formatted != doc_content ->
create_patch_for_formatted_sigil_content(
node,
formatted,
sigil_node,
binary_node,
sigil_type,
sigil_meta,
acc
)

{:ok, _unchanged} ->
{node, acc}

{:error, _error} ->
{node, acc}
end
else
{node, acc}
end
end

defp create_patch_for_formatted_sigil_content(
node,
formatted,
sigil_node,
_binary_node,
sigil_type,
sigil_meta,
acc
) do
range = Sourceror.get_range(sigil_node)
delimiter = sigil_meta[:delimiter]

sigil_prefix =
case sigil_type do
:sigil_S -> "~S"
:sigil_s -> "~s"
end

replacement = PatchBuilder.build_replacement_sigil_string(formatted, sigil_prefix, delimiter)

patch = %Sourceror.Patch{
range: range,
change: replacement
}

{node, [patch | acc]}
end

defp format_markdown_content(content, nif_config) do
case DprintMarkdownFormatter.Native.format_markdown(content, nif_config) do
{:ok, formatted} ->
Expand Down
66 changes: 66 additions & 0 deletions lib/dprint_markdown_formatter/patch_builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ defmodule DprintMarkdownFormatter.PatchBuilder do
replacements, supporting both simple strings and heredoc formats.
"""

@single_char_delimiters ["\"", "'", "/", "|", "(", "[", "{", "<"]

@doc """
Builds a replacement string for formatted content based on the original
delimiter.
Expand Down Expand Up @@ -76,4 +78,68 @@ defmodule DprintMarkdownFormatter.PatchBuilder do
# Heredoc format - preserve as heredoc
"\"\"\"\n#{formatted}\n\"\"\""
end

@doc """
Builds a replacement string for sigil content, preserving the original sigil
type and delimiter.

Handles all supported sigil delimiters and maintains proper formatting for both
simple and heredoc-style sigils.

## Examples

iex> DprintMarkdownFormatter.PatchBuilder.build_replacement_sigil_string("Hello", "~S", "\\"")
"~S\\"Hello\\""

iex> DprintMarkdownFormatter.PatchBuilder.build_replacement_sigil_string("Hello\\nWorld", "~S", "\\"\\"\\\"")
"~S\\"\\"\\\"\\nHello\\nWorld\\n\\"\\"\\""

iex> DprintMarkdownFormatter.PatchBuilder.build_replacement_sigil_string("Hello", "~s", "/")
"~s/Hello/"
"""
@spec build_replacement_sigil_string(String.t(), String.t(), String.t()) :: String.t()
def build_replacement_sigil_string(formatted, sigil_prefix, delimiter) do
case delimiter do
"\"\"\"" ->
# Heredoc sigil - format exactly like regular heredocs
"#{sigil_prefix}\"\"\"\n#{formatted}\n\"\"\"\n"
Copy link

Copilot AI Aug 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The heredoc format includes a trailing newline that may not match the original formatting. Consider checking the original content's trailing whitespace to preserve the exact format.

Suggested change
"#{sigil_prefix}\"\"\"\n#{formatted}\n\"\"\"\n"
trailing_newline = if String.ends_with?(formatted, "\n"), do: "", else: "\n"
"#{sigil_prefix}\"\"\"\n#{formatted}\n\"\"\"#{trailing_newline}"

Copilot uses AI. Check for mistakes.

"'''" ->
# Triple single quote heredoc - same as triple double quotes
"#{sigil_prefix}'''\n#{formatted}\n'''\n"

single_delimiter when single_delimiter in @single_char_delimiters ->
# Single character delimiters - preserve original delimiter structure
closing_delimiter = get_closing_delimiter(single_delimiter)

# For simple delimiters, match the expected format precisely
# Single-line sigils should not have trailing newline
# Multi-line sigils should have newline after closing delimiter
has_newlines = String.contains?(formatted, "\n")

if has_newlines do
# Multi-line content needs newline after closing delimiter
"#{sigil_prefix}#{single_delimiter}\n#{formatted}\n#{closing_delimiter}\n"
else
# Single-line content - no trailing newline
"#{sigil_prefix}#{single_delimiter}#{formatted}#{closing_delimiter}"
end

_unknown_delimiter ->
# Fallback to heredoc for unknown delimiters
"#{sigil_prefix}\"\"\"\n#{formatted}\n\"\"\"\n"
end
end

# Private helper to get the closing delimiter for bracket-style delimiters
defp get_closing_delimiter(opening) do
case opening do
"(" -> ")"
"[" -> "]"
"{" -> "}"
"<" -> ">"
# For quotes, slashes, pipes - closing is same as opening
other -> other
end
end
end
2 changes: 1 addition & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Add to `mix.exs`:
```elixir
def deps do
[
{:dprint_markdown_formatter, "~> 0.4.0"}
{:dprint_markdown_formatter, "~> 0.5.0"}
]
end
```
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule DprintMarkdownFormatter.MixProject do
use Mix.Project

@version "0.4.0"
@version "0.5.0"
@source_url "https://github.com/fahchen/dprint_markdown_formatter"

def project do
Expand Down
Loading