-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TypedInterpolatedStringsAnalyzer (#40)
* Add TypedInterpolatedStringsAnalyzer * Update tools * Add changelog entry. * Add description. * Put todo as comment
- Loading branch information
Showing
11 changed files
with
163 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
--- | ||
title: TypedInterpolatedStrings Analyzer | ||
category: analyzers | ||
categoryindex: 1 | ||
index: 9 | ||
--- | ||
|
||
# TypedInterpolatedStrings Analyzer | ||
|
||
Using interpolated strings can catch you off guard when refactoring types. | ||
|
||
## Problem | ||
|
||
```fsharp | ||
let v = 1 // Changing the type won't affect `s` | ||
let s = $"{v}" | ||
``` | ||
|
||
## Fix | ||
|
||
Add an explicit type format: | ||
|
||
```fsharp | ||
let v = 1 // Changing the type will affect `s` | ||
let s = $"%i{v}" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
module GR.FSharp.Analyzers.TypedInterpolatedStringsAnalyzer | ||
|
||
open System.Text.RegularExpressions | ||
open FSharp.Analyzers.SDK | ||
open FSharp.Analyzers.SDK.ASTCollecting | ||
open FSharp.Compiler.Syntax | ||
|
||
[<Literal>] | ||
let Code = "GRA-INTERPOLATED-001" | ||
|
||
/// See https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/plaintext-formatting#format-specifiers-for-printf | ||
let specifierRegex = | ||
Regex (@"\%(\+|\-)?\d*\.?\d*(b|s|c|d|i|u|x|X|o|B|e|E|f|F|g|G|M|O|A)$") | ||
|
||
[<CliAnalyzer("TypedInterpolatedStringsAnalyzer", | ||
"Warns about missing type specifiers in interpolated strings", | ||
"https://g-research.github.io/fsharp-analyzers/analyzers/TypedInterpolatedStringsAnalyzer.html")>] | ||
let typedInterpolatedStringsAnalyzer : Analyzer<CliContext> = | ||
fun (ctx : CliContext) -> | ||
async { | ||
let messages = ResizeArray<Message> () | ||
|
||
let walker = | ||
{ new SyntaxCollectorBase() with | ||
override x.WalkExpr (expr : SynExpr) = | ||
match expr with | ||
| SynExpr.InterpolatedString (contents = contents) -> | ||
contents | ||
|> List.pairwise | ||
|> List.iter (fun (p1, p2) -> | ||
match p1, p2 with | ||
| SynInterpolatedStringPart.String (value = s), | ||
SynInterpolatedStringPart.FillExpr (fillExpr = e) -> | ||
if not (isNull s) && not (specifierRegex.IsMatch s) then | ||
messages.Add | ||
{ | ||
Type = "TypedInterpolatedStringsAnalyzer" | ||
Message = | ||
"Interpolated hole expression without format detected. Use prefix with the correct % to enforce type safety." | ||
Code = Code | ||
Severity = Warning | ||
Range = e.Range | ||
Fixes = [] | ||
} | ||
| _ -> () | ||
) | ||
| _ -> () | ||
} | ||
|
||
walkAst walker ctx.ParseFileResults.ParseTree | ||
|
||
return Seq.toList messages | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
tests/FSharp.Analyzers.Tests/TypedInterpolatedStringsAnalyzerTests.fs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
module GR.FSharp.Analyzers.Tests.TypedInterpolatedStringsAnalyzerTests | ||
|
||
open System.Collections | ||
open System.IO | ||
open NUnit.Framework | ||
open FSharp.Compiler.CodeAnalysis | ||
open FSharp.Analyzers.SDK.Testing | ||
open GR.FSharp.Analyzers | ||
open GR.FSharp.Analyzers.Tests.Common | ||
|
||
let mutable projectOptions : FSharpProjectOptions = FSharpProjectOptions.zero | ||
|
||
[<SetUp>] | ||
let Setup () = | ||
task { | ||
let! options = mkOptionsFromProject "net7.0" [] | ||
projectOptions <- options | ||
} | ||
|
||
type TestCases() = | ||
|
||
interface IEnumerable with | ||
member _.GetEnumerator () : IEnumerator = | ||
constructTestCaseEnumerator [| "typedInterpolatedStrings" |] | ||
|
||
[<TestCaseSource(typeof<TestCases>)>] | ||
let TypedInterpolatedStringsAnalyzerTests (fileName : string) = | ||
task { | ||
let fileName = Path.Combine (dataFolder, fileName) | ||
|
||
let! messages = | ||
File.ReadAllText fileName | ||
|> getContext projectOptions | ||
|> TypedInterpolatedStringsAnalyzer.typedInterpolatedStringsAnalyzer | ||
|
||
do! assertExpected fileName messages | ||
} | ||
|
||
type NegativeTestCases() = | ||
|
||
interface IEnumerable with | ||
member _.GetEnumerator () : IEnumerator = | ||
constructTestCaseEnumerator [| "typedInterpolatedStrings" ; "negative" |] | ||
|
||
[<TestCaseSource(typeof<NegativeTestCases>)>] | ||
let NegativeTests (fileName : string) = | ||
task { | ||
let fileName = Path.Combine (dataFolder, fileName) | ||
|
||
let! messages = | ||
File.ReadAllText fileName | ||
|> getContext projectOptions | ||
|> TypedInterpolatedStringsAnalyzer.typedInterpolatedStringsAnalyzer | ||
|
||
Assert.IsEmpty messages | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
module I | ||
|
||
open System | ||
|
||
ignore $"Hey {42}" | ||
ignore $"{4.} a {obj()} b {TimeSpan.Zero}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
GRA-INTERPOLATED-001 | Warning | (5,14 - 5,16) | Interpolated hole expression without format detected. Use prefix with the correct % to enforce type safety. | ||
GRA-INTERPOLATED-001 | Warning | (6,10 - 6,12) | Interpolated hole expression without format detected. Use prefix with the correct % to enforce type safety. | ||
GRA-INTERPOLATED-001 | Warning | (6,17 - 6,22) | Interpolated hole expression without format detected. Use prefix with the correct % to enforce type safety. | ||
GRA-INTERPOLATED-001 | Warning | (6,27 - 6,40) | Interpolated hole expression without format detected. Use prefix with the correct % to enforce type safety. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module I | ||
|
||
open System | ||
|
||
ignore $"Hey %i{42}" | ||
ignore $"%f{4.} a %A{obj()} b %O{TimeSpan.Zero}" | ||
ignore $"%0.17f{43.2}" | ||
ignore $"%b{false}" |