Skip to content

Commit 6b07489

Browse files
committed
Add new rule FavourSingleton
Fixes #526
1 parent 48f963f commit 6b07489

File tree

10 files changed

+174
-2
lines changed

10 files changed

+174
-2
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,5 @@ The following rules can be specified for linting.
114114
- [CyclomaticComplexity (FL0071)](rules/FL0071.html)
115115
- [FailwithBadUsage (FL0072)](rules/FL0072.html)
116116
- [FavourReRaise (FL0073)](rules/FL0073.html)
117-
- [FavourConsistentThis (FL0074)](rules/FL0074.html)
117+
- [FavourConsistentThis (FL0074)](rules/FL0074.html)
118+
- [FavourSingleton (FL0075)](rules/FL0075.html)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: FL0075
3+
category: how-to
4+
hide_menu: true
5+
---
6+
7+
# FavourSingleton (FL0075)
8+
9+
*Introduced in `0.21.1`*
10+
11+
## Cause
12+
13+
Rule to detect usage of lists or arrays with only one item.
14+
15+
## Rationale
16+
17+
List.singleton/Array.singleton is more readable and explicit than [ foo]/[| foo |].
18+
19+
## How To Fix
20+
21+
Replace all occurrences of single member lists/arrays with a call to List.singleton/Array.singleton.
22+
23+
## Rule Settings
24+
25+
{
26+
"favourSingleton": {
27+
"enabled": false
28+
}
29+
}
30+

src/FSharpLint.Core/Application/Configuration.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ type Configuration =
433433
MaxLinesInFile:RuleConfig<MaxLinesInFile.Config> option
434434
TrailingNewLineInFile:EnabledConfig option
435435
NoTabCharacters:EnabledConfig option
436-
NoPartialFunctions:RuleConfig<NoPartialFunctions.Config> option }
436+
NoPartialFunctions:RuleConfig<NoPartialFunctions.Config> option
437+
FavourSingleton:EnabledConfig option }
437438
with
438439
static member Zero = {
439440
Global = None
@@ -511,6 +512,7 @@ with
511512
TrailingNewLineInFile = None
512513
NoTabCharacters = None
513514
NoPartialFunctions = None
515+
FavourSingleton = None
514516
}
515517

516518
// fsharplint:enable RecordFieldNames
@@ -652,6 +654,7 @@ let flattenConfig (config:Configuration) =
652654
config.TrailingNewLineInFile |> Option.bind (constructRuleIfEnabled TrailingNewLineInFile.rule)
653655
config.NoTabCharacters |> Option.bind (constructRuleIfEnabled NoTabCharacters.rule)
654656
config.NoPartialFunctions |> Option.bind (constructRuleWithConfig NoPartialFunctions.rule)
657+
config.FavourSingleton |> Option.bind (constructRuleIfEnabled FavourSingleton.rule)
655658
|] |> Array.choose id
656659

657660
if config.NonPublicValuesNames.IsSome &&

src/FSharpLint.Core/FSharpLint.Core.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<Compile Include="Rules\Conventions\CyclomaticComplexity.fs" />
5050
<Compile Include="Rules\Conventions\FavourReRaise.fs" />
5151
<Compile Include="Rules\Conventions\FavourConsistentThis.fs" />
52+
<Compile Include="Rules\Conventions\FavourSingleton.fs" />
5253
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\RaiseWithTooManyArgumentsHelper.fs" />
5354
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\FailwithWithSingleArgument.fs" />
5455
<Compile Include="Rules\Conventions\RaiseWithTooManyArguments\RaiseWithSingleArgument.fs" />
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module FSharpLint.Rules.FavourSingleton
2+
3+
open FSharpLint.Framework
4+
open FSharpLint.Framework.Suggestion
5+
open FSharp.Compiler.Syntax
6+
open FSharp.Compiler.Text
7+
open FSharpLint.Framework.Ast
8+
open FSharpLint.Framework.Rules
9+
open System
10+
11+
let runner args =
12+
match args.AstNode with
13+
| AstNode.Binding(SynBinding(_, _, _, _, _, _, _, _, _, expression, _, _)) ->
14+
match expression with
15+
| SynExpr.ArrayOrListOfSeqExpr(_, SynExpr.CompExpr(_, _, expr,range), _) ->
16+
match expr with
17+
| SynExpr.Const(_, range) ->
18+
{ Range = range
19+
Message = String.Format(Resources.GetString "RulesFavourSingleton")
20+
SuggestedFix = None
21+
TypeChecks = List.Empty }
22+
|> Array.singleton
23+
| SynExpr.Ident _ ->
24+
{ Range = range
25+
Message = String.Format(Resources.GetString "RulesFavourSingleton")
26+
SuggestedFix = None
27+
TypeChecks = List.Empty }
28+
|> Array.singleton
29+
| _ -> Array.empty
30+
| _ -> Array.empty
31+
| _ -> Array.empty
32+
let rule =
33+
{ Name = "FavourSingleton"
34+
Identifier = Identifiers.FavourSingleton
35+
RuleConfig =
36+
{ AstNodeRuleConfig.Runner = runner
37+
Cleanup = ignore } }
38+
|> AstNodeRule

src/FSharpLint.Core/Rules/Identifiers.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,4 @@ let CyclomaticComplexity = identifier 71
7979
let FailwithBadUsage = identifier 72
8080
let FavourReRaise = identifier 73
8181
let FavourConsistentThis = identifier 74
82+
let FavourSingleton = identifier 75

src/FSharpLint.Core/Text.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,7 @@
333333
<data name="RulesFavourConsistentThis" xml:space="preserve">
334334
<value>Prefer using '{0}' consistently.</value>
335335
</data>
336+
<data name="RulesFavourSingleton" xml:space="preserve">
337+
<value>Consider using List.singleton/Array.singleton instead of single member list/array.</value>
338+
</data>
336339
</root>

src/FSharpLint.Core/fsharplint.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@
261261
}
262262
},
263263
"favourIgnoreOverLetWild": { "enabled": true },
264+
"favourSingleton": { "enabled": false },
264265
"wildcardNamedWithAsPattern": { "enabled": true },
265266
"uselessBinding": { "enabled": true },
266267
"tupleOfWildcards": { "enabled": true },

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
<Compile Include="Rules\Conventions\NoPartialFunctions.fs" />
3838
<Compile Include="Rules\Conventions\FavourReRaise.fs" />
3939
<Compile Include="Rules\Conventions\FavourConsistentThis.fs" />
40+
<Compile Include="Rules\Conventions\FavourSingleton.fs" />
4041
<Compile Include="Rules\Conventions\Naming\NamingHelpers.fs" />
4142
<Compile Include="Rules\Conventions\Naming\InterfaceNames.fs" />
4243
<Compile Include="Rules\Conventions\Naming\ExceptionNames.fs" />
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
module FSharpLint.Core.Tests.Rules.Conventions.FavourSingleton
2+
3+
open NUnit.Framework
4+
open FSharpLint.Rules
5+
open System
6+
7+
[<TestFixture>]
8+
type TestConventionsFavourSingleton() =
9+
inherit TestAstNodeRuleBase.TestAstNodeRuleBase(FavourSingleton.rule)
10+
11+
[<Test>]
12+
member this.ListWithManyItemsShouldNotProduceError() =
13+
this.Parse """
14+
let foo = [ 10; 20 ]"""
15+
16+
this.AssertNoWarnings()
17+
18+
[<Test>]
19+
member this.ListWithASingleConstantShouldProduceError() =
20+
this.Parse """
21+
let foo = [ 10 ]"""
22+
23+
Assert.IsTrue this.ErrorsExist
24+
Assert.IsTrue(this.ErrorExistsAt(2, 12))
25+
26+
[<Test>]
27+
member this.ListWithASingleIdentShouldProduceError() =
28+
this.Parse """
29+
let bar = true
30+
let foo = [ bar ]"""
31+
32+
Assert.IsTrue this.ErrorsExist
33+
Assert.IsTrue(this.ErrorExistsAt(3, 12))
34+
35+
[<Test>]
36+
member this.ListWithMultipleIdentsShouldNotProduceError() =
37+
this.Parse """
38+
let bar = true
39+
let foo = [ bar; false; true ]"""
40+
41+
this.AssertNoWarnings()
42+
43+
[<Test>]
44+
member this.ListWithManyItemsShouldNotProduceError_Arrays() =
45+
this.Parse """
46+
let foo = [| 10; 20 |]"""
47+
48+
Assert.IsTrue this.NoErrorsExist
49+
50+
[<Test>]
51+
member this.ListWithASingleConstantShouldProduceError_Arrays() =
52+
this.Parse """
53+
let foo = [| 10 |]"""
54+
55+
Assert.IsTrue this.ErrorsExist
56+
Assert.IsTrue(this.ErrorExistsAt(2, 13))
57+
58+
[<Test>]
59+
member this.ListWithASingleIdentShouldProduceError_Arrays() =
60+
this.Parse """
61+
let bar = true
62+
let foo = [| bar |]"""
63+
64+
Assert.IsTrue this.ErrorsExist
65+
Assert.IsTrue(this.ErrorExistsAt(3, 13))
66+
67+
[<Test>]
68+
member this.ListWithMultipleIdentsShouldNotProduceError_Arrays() =
69+
this.Parse """
70+
let bar = true
71+
let foo = [| bar; false; true |]"""
72+
73+
this.AssertNoWarnings()
74+
75+
[<Test>]
76+
member this.SingletonListWithMatchCaseShouldNotProduceError() =
77+
this.Parse """
78+
let foo = List.empty
79+
match foo with
80+
| [x] -> printf x
81+
| _ -> printf "baz" """
82+
83+
this.AssertNoWarnings()
84+
85+
[<Test>]
86+
member this.SingletonArrayWithMatchCaseShouldNotProduceError() =
87+
this.Parse """
88+
let foo = Array.empty
89+
match foo with
90+
| [| x |] -> printf x
91+
| _ -> printf "baz" """
92+
93+
this.AssertNoWarnings()

0 commit comments

Comments
 (0)