Module Sexps_rewriter

This is thin wrapper on top of File_rewriter that specializes in applying rewrites to sexp files.

The primary motivation for which this library is created is to automatically apply some linting rules to the lots of dune files present in a large monorepo.

A note on editing files with a single sexp (rather than a list of them): it should just work, since one sexp is just a sub case of many sexps.

type t
module Parse_error : sig ... end
val create : path:Fpath.t -> original_contents:string -> (t, Parse_error.t) Stdlib.Result.t

A t shall be created from a string that would yield a successful parsing of many sexps and positions from a file, using the Parsexp library. The path is provided to create a Loc.t for error messages, but no I/O is actually performed on disk - the sexps are parsed from the string provided by the parameter original_contents.

Rewrites

module Visitor_decision : sig ... end

While we visit the sexps from the original file, we may decide what to do at each step of the iteration:

val visit : t -> f: (Sexplib0.Sexp.t -> range:Loc.Range.t -> file_rewriter:File_rewriter.t -> Visitor_decision.t) -> unit

Visiting the input, perhaps recursively. Unless you are breaking the execution with Break or Skip, the function f provided will be called on all the sexps (including recursively on all internal ones) of the input.

The expected way to use this function is to do some pattern matching on the Sexp.t currently at hand, and use the File_rewriter api if some rewrites are required. If the current sexp is not one you are targeting you may simply ignore it and return Continue, to be called again on other parts of the input.

Note that you may visit the same input multiple times (that is, calling visit multiple times, with different invocations of f), however, be mindful that the file_rewriter that you manipulate is the same each time, thus the final computation of the output will fail if you enqueue incompatible rewrites in it.

val reset : t -> unit

You may decide at a given moment that you want to discard all the rewrites done so far, and just start over. This has the same effect than starting fresh with a newly created t.

Output

val contents : t -> string

Produce the resulting buffer, with all the rewrites applied. Note that t may continue to be used further from here, and you can call contents again later. This raises File_rewriter.Invalid_rewrites if inconsistent rewrites have been submitted to t's file_rewriter.

val contents_result : t -> (string, File_rewriter.Invalid_rewrites.t) Stdlib.Result.t

Same as contents but wraps the output into a result type rather than raising an exception.

Getters

val file_rewriter : t -> File_rewriter.t

If you need access to the internal file_rewriter, this accessor is exposed.

val original_sexps : t -> Sexplib0.Sexp.t list

Access the raw sexps that were parsed from the original contents.

val path : t -> Fpath.t

Retrieve the path provided when t was created.

Utils on positions and ranges

module Position : sig ... end
exception Position_not_found of {
  1. positions : Parsexp.Positions.t;
  2. sexp : Sexplib0.Sexp.t;
}

An exception raised when trying to locate the position of an unknown sexp when traversing the input. We don't expose functions returning results rather than raising this exception in this interface because we can't think of an actual use case for the erroring case. If you are trying to locate positions from an invalid sexp, we envision that this is the result of a programming error which should be fixed.

val position : t -> Sexplib0.Sexp.t -> Parsexp.Positions.range

Assuming the supplied Sexp.t is phys_equal to a Sexp.t found during a call to visit, the function position will be able to return its position. This is using Parsexp.Positions.t under the hood, which keeps track of the positions of all the sexps that have been parsed. If the Sexp.t cannot be found as phys_equal to one of the parsed ones, this function will raise Position_not_found.

val loc : t -> Sexplib0.Sexp.t -> Loc.t

This is just a shortcut to calling position and converting its result with Position.loc. It raises if position raises.

val range : t -> Sexplib0.Sexp.t -> Loc.Range.t

This is just a shortcut to calling position and converting its result with Position.range. It raises if position raises.

val start_offset : t -> Sexplib0.Sexp.t -> Loc.Offset.t
val stop_offset : t -> Sexplib0.Sexp.t -> Loc.Offset.t
module Private : sig ... end