Logo ionide-analyzers

NoArgFormatStringAnalyzer

Applying a printf-style function (sprintf, failwithf, printf, printfn, eprintf, eprintfn, fprintf, fprintfn, bprintf) to a format string that has no format specifiers still goes through the F# format parsing machinery on every call. When the string is a constant, the same result can be obtained with the plain string literal.

Problem

// Analyzer will trigger
let header = sprintf "<pre><code>"
let fail () = failwithf "boom"
printfn "hello"
fprintfn tw "hello"
Printf.bprintf sb "hello"

Fix

let header = "<pre><code>"
let fail () = failwith "boom"
stdout.WriteLine "hello"
tw.WriteLine "hello"
sb.Append "hello" |> ignore

An escaped percent sign (%%) is not a format specifier, so sprintf "100%%" is also reported and the fix unescapes it to "100%".

Format strings with at least one specifier, interpolated strings and non-constant format values are not reported:

// Does not trigger analyzer
let langCode = sprintf "language-%s" "fsharp"
let greet name = sprintf $"hello %s{name}"

Code fix

This analyzer has a code fix for Ionide.

val header: string
val sprintf: format: Printf.StringFormat<'T> -> 'T
val fail: unit -> 'a
val failwithf: format: Printf.StringFormat<'T,'Result> -> 'T
val printfn: format: Printf.TextWriterFormat<'T> -> 'T
val fprintfn: textWriter: System.IO.TextWriter -> format: Printf.TextWriterFormat<'T> -> 'T
module Printf from Microsoft.FSharp.Core
val bprintf: builder: System.Text.StringBuilder -> format: Printf.BuilderFormat<'T> -> 'T
val failwith: message: string -> 'T
val stdout<'T> : System.IO.TextWriter
val ignore: value: 'T -> unit
val langCode: string
val greet: name: string -> string
val name: string

Type something to start searching.