Skip to content

Commit 57a1193

Browse files
authored
Merge PR #787 from webwarrior-ws/interpolated-strings
Add InterpolatedStringWithNoSubstitution rule.
2 parents 8b250f5 + ce43cb2 commit 57a1193

File tree

16 files changed

+165
-9
lines changed

16 files changed

+165
-9
lines changed

docs/content/how-tos/rule-configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,4 @@ The following rules can be specified for linting.
127127
- [FavourNonMutablePropertyInitialization (FL0084)](rules/FL0084.html)
128128
- [EnsureTailCallDiagnosticsInRecursiveFunctions (FL0085)](rules/FL0085.html)
129129
- [FavourAsKeyword (FL0086)](rules/FL0086.html)
130+
- [InterpolatedStringWithNoSubstitution (FL0087)](rules/FL0087.html)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
title: FL0087
3+
category: how-to
4+
hide_menu: true
5+
---
6+
7+
# InterpolatedStringWithNoSubstitution (FL0087)
8+
9+
*Introduced in `0.26.8`*
10+
11+
## Cause
12+
13+
Interpolated string (with `$` prefix, such as `$"foo"`) is used without embedded F# expression (e.g. `$"{foo}"` where foo is some value).
14+
15+
Or string formatting function such as `sprintf` or `failwithf` is used without interpolation.
16+
17+
## Rationale
18+
19+
Usage of interpolated string without embedded F# expression(s) may indicate that the expression was intended to be used but wasn't used by mistake.
20+
21+
## How To Fix
22+
23+
Use regular string or embed F# expression using curly braces (`{}`).
24+
25+
Use `failwith` instead of `failwithf` if no formatting/interpolation is needed.
26+
27+
## Rule Settings
28+
29+
{
30+
"interpolatedStringWithNoSubstitution": {
31+
"enabled": true
32+
}
33+
}

src/FSharpLint.Console/Program.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
169169
| FileType.Source -> Lint.lintSource lintParams target
170170
| FileType.Solution -> Lint.lintSolution lintParams target toolsPath
171171
| FileType.Wildcard ->
172-
output.WriteInfo $"Wildcard detected, but not recommended. Using a project (slnx/sln/fsproj) can detect more issues."
172+
output.WriteInfo "Wildcard detected, but not recommended. Using a project (slnx/sln/fsproj) can detect more issues."
173173
let files = expandWildcard target
174174
if List.isEmpty files then
175175
output.WriteInfo $"No files matching pattern '%s{target}' were found."

src/FSharpLint.Core/Application/Configuration.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,8 @@ type Configuration =
473473
NoPartialFunctions:RuleConfig<NoPartialFunctions.Config> option
474474
SuggestUseAutoProperty:EnabledConfig option
475475
EnsureTailCallDiagnosticsInRecursiveFunctions:EnabledConfig option
476-
FavourAsKeyword:EnabledConfig option }
476+
FavourAsKeyword:EnabledConfig option
477+
InterpolatedStringWithNoSubstitution:EnabledConfig option }
477478
with
478479
static member Zero = {
479480
Global = None
@@ -568,6 +569,7 @@ with
568569
SuggestUseAutoProperty = None
569570
EnsureTailCallDiagnosticsInRecursiveFunctions = None
570571
FavourAsKeyword = None
572+
InterpolatedStringWithNoSubstitution = None
571573
}
572574

573575
// fsharplint:enable RecordFieldNames
@@ -763,6 +765,7 @@ let flattenConfig (config:Configuration) =
763765
config.SuggestUseAutoProperty |> Option.bind (constructRuleIfEnabled SuggestUseAutoProperty.rule)
764766
config.EnsureTailCallDiagnosticsInRecursiveFunctions |> Option.bind (constructRuleIfEnabled EnsureTailCallDiagnosticsInRecursiveFunctions.rule)
765767
config.FavourAsKeyword |> Option.bind (constructRuleIfEnabled FavourAsKeyword.rule)
768+
config.InterpolatedStringWithNoSubstitution |> Option.bind (constructRuleIfEnabled InterpolatedStringWithNoSubstitution.rule)
766769
|]
767770

768771
findDeprecation config deprecatedAllRules allRules

src/FSharpLint.Core/FSharpLint.Core.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
<Compile Include="Framework\Suppression.fs" />
3232
<!-- Rules -->
3333
<Compile Include="Rules\Identifiers.fs" />
34+
<Compile Include="Rules\Smells\InterpolatedStringWithNoSubstitution.fs" />
3435
<Compile Include="Rules\Formatting\TupleFormatting\TupleFormattingHelper.fs" />
3536
<Compile Include="Rules\Formatting\TupleFormatting\TupleCommaSpacing.fs" />
3637
<Compile Include="Rules\Formatting\TupleFormatting\TupleIndentation.fs" />

src/FSharpLint.Core/Rules/Identifiers.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,4 @@ let UnneededRecKeyword = identifier 83
9191
let FavourNonMutablePropertyInitialization = identifier 84
9292
let EnsureTailCallDiagnosticsInRecursiveFunctions = identifier 85
9393
let FavourAsKeyword = identifier 86
94+
let InterpolatedStringWithNoSubstitution = identifier 87
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
module FSharpLint.Rules.InterpolatedStringWithNoSubstitution
2+
3+
open System
4+
open FSharpLint.Framework
5+
open FSharpLint.Framework.Suggestion
6+
open FSharp.Compiler.Syntax
7+
open FSharpLint.Framework.Ast
8+
open FSharpLint.Framework.Rules
9+
10+
let private makeWarning range =
11+
{
12+
Range = range
13+
Message = Resources.GetString "InterpolatedStringWithNoSubstitution"
14+
SuggestedFix = None
15+
TypeChecks = List.Empty
16+
}
17+
18+
let checkInterpolatedString (contents: List<SynInterpolatedStringPart>) range =
19+
if contents |> List.exists (fun part -> part.IsFillExpr) then
20+
Array.empty
21+
else
22+
Array.singleton (makeWarning range)
23+
24+
let stringFormattingFunctionNames = [ "sprintf"; "failwithf" ]
25+
26+
// https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/plaintext-formatting#format-specifiers-for-printf
27+
let formatSpecifierRegex = Text.RegularExpressions.Regex(@"($|[^\%])\%[^\s\%]")
28+
29+
let checkFormattingFunctionApplication (formatString: string) range =
30+
if formatSpecifierRegex.IsMatch formatString then
31+
Array.empty
32+
else
33+
Array.singleton (makeWarning range)
34+
35+
let runner (args: AstNodeRuleParams) =
36+
match args.AstNode with
37+
| AstNode.Expression(SynExpr.InterpolatedString(contents, _synStringKind, range)) ->
38+
checkInterpolatedString contents range
39+
| AstNode.Expression(SynExpr.App(_, false, SynExpr.Ident(ident), SynExpr.Const(SynConst.String(text, _, _stringRange),_), range))
40+
when stringFormattingFunctionNames |> List.contains ident.idText ->
41+
checkFormattingFunctionApplication text range
42+
| _ -> Array.empty
43+
44+
let rule =
45+
AstNodeRule
46+
{
47+
Name = "InterpolatedStringWithNoSubstitution"
48+
Identifier = Identifiers.InterpolatedStringWithNoSubstitution
49+
RuleConfig =
50+
{
51+
AstNodeRuleConfig.Runner = runner
52+
Cleanup = ignore
53+
}
54+
}

src/FSharpLint.Core/Text.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,4 +381,7 @@
381381
<data name="RulesFavourAsKeyword" xml:space="preserve">
382382
<value>Prefer using the 'as' pattern to match a constant and bind it to a variable.</value>
383383
</data>
384+
<data name="InterpolatedStringWithNoSubstitution" xml:space="preserve">
385+
<value>Do not use interpolated string syntax (with $ prefix) or formatting functions (sprintf, failwithf) when not really performing any interpolation.</value>
386+
</data>
384387
</root>

src/FSharpLint.Core/fsharplint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@
333333
},
334334
"ensureTailCallDiagnosticsInRecursiveFunctions": { "enabled": true },
335335
"favourAsKeyword": { "enabled": true },
336+
"interpolatedStringWithNoSubstitution": { "enabled": true },
336337
"hints": {
337338
"add": [
338339
"not (a = b) ===> a <> b",

tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
<Compile Include="Rules\TestIndentationRule.fs" />
1515
<Compile Include="Rules\TestNoTabCharactersRule.fs" />
1616
<Compile Include="Rules\TestLineRule.fs" />
17+
<Compile Include="Rules\Smells\InterpolatedStringWithNoSubstitution.fs" />
1718
<Compile Include="Rules\Formatting\TupleFormatting\TupleParentheses.fs" />
1819
<Compile Include="Rules\Formatting\TupleFormatting\TupleCommaSpacing.fs" />
1920
<Compile Include="Rules\Formatting\TupleFormatting\TupleIndentation.fs" />

0 commit comments

Comments
 (0)