Skip to content

Commit c621861

Browse files
authored
Merge PR #803 from knocte/favour-singleton-rebase
Add new rule FavourSingleton.
2 parents 0b3c84a + 5ba2426 commit c621861

File tree

12 files changed

+188
-4
lines changed

12 files changed

+188
-4
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,4 @@ The following rules can be specified for linting.
129129
- [FavourAsKeyword (FL0086)](rules/FL0086.html)
130130
- [InterpolatedStringWithNoSubstitution (FL0087)](rules/FL0087.html)
131131
- [IndexerAccessorStyleConsistency (FL0088)](rules/FL0088.html)
132+
- [FavourSingleton (FL0089)](rules/FL0089.html)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
title: FL0089
3+
category: how-to
4+
hide_menu: true
5+
---
6+
7+
# FavourSingleton (FL0089)
8+
9+
*Introduced in `0.26.10`*
10+
11+
## Cause
12+
13+
Rule to detect usage of lists or arrays with only one item.
14+
15+
## Rationale
16+
17+
`List.singleton foo`/`Array.singleton foo` is more readable and explicit than `[foo]`/`[|foo|]`.
18+
Especially if we take in account that F# newbies may not yet be familiar with the difference between
19+
inline array vs list instantiation; and square brackets with only one element could potentially be
20+
confused with an indexing accessor.
21+
22+
## How To Fix
23+
24+
Replace all occurrences of single member lists/arrays with a call to List.singleton/Array.singleton.
25+
26+
## Rule Settings
27+
28+
{
29+
"favourSingleton": {
30+
"enabled": false
31+
}
32+
}

src/FSharpLint.Core/Application/Configuration.fs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,8 @@ type Configuration =
513513
SuggestUseAutoProperty:EnabledConfig option
514514
EnsureTailCallDiagnosticsInRecursiveFunctions:EnabledConfig option
515515
FavourAsKeyword:EnabledConfig option
516-
InterpolatedStringWithNoSubstitution:EnabledConfig option }
516+
InterpolatedStringWithNoSubstitution:EnabledConfig option
517+
FavourSingleton:EnabledConfig option }
517518
with
518519
static member Zero = {
519520
Global = None
@@ -610,6 +611,7 @@ with
610611
EnsureTailCallDiagnosticsInRecursiveFunctions = None
611612
FavourAsKeyword = None
612613
InterpolatedStringWithNoSubstitution = None
614+
FavourSingleton = None
613615
}
614616

615617
// fsharplint:enable RecordFieldNames
@@ -807,6 +809,7 @@ let flattenConfig (config:Configuration) =
807809
config.EnsureTailCallDiagnosticsInRecursiveFunctions |> Option.bind (constructRuleIfEnabled EnsureTailCallDiagnosticsInRecursiveFunctions.rule)
808810
config.FavourAsKeyword |> Option.bind (constructRuleIfEnabled FavourAsKeyword.rule)
809811
config.InterpolatedStringWithNoSubstitution |> Option.bind (constructRuleIfEnabled InterpolatedStringWithNoSubstitution.rule)
812+
config.FavourSingleton |> Option.bind (constructRuleIfEnabled FavourSingleton.rule)
810813
|]
811814

812815
findDeprecation config deprecatedAllRules allRules

src/FSharpLint.Core/FSharpLint.Core.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
<Compile Include="Rules\Smells\RaiseWithTooManyArguments\InvalidArgWithTwoArguments.fs" />
7070
<Compile Include="Rules\Smells\RaiseWithTooManyArguments\FailwithfWithArgumentsMatchingFormatString.fs" />
7171
<Compile Include="Rules\Conventions\FailwithBadUsage.fs" />
72+
<Compile Include="Rules\Conventions\FavourSingleton.fs" />
7273
<Compile Include="Rules\Conventions\SourceLength\SourceLengthHelper.fs" />
7374
<Compile Include="Rules\Conventions\SourceLength\MaxLinesInLambdaFunction.fs" />
7475
<Compile Include="Rules\Conventions\SourceLength\MaxLinesInMatchLambdaFunction.fs" />

src/FSharpLint.Core/Framework/ParseFile.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ module ParseFile =
5757
let getProjectOptionsFromScript (checker:FSharpChecker) file (source:string) = async {
5858
let sourceText = SourceText.ofString source
5959
let assumeDotNetFramework = false
60-
let otherOpts = [| "--targetprofile:netstandard" |]
60+
let otherOpts = Array.singleton "--targetprofile:netstandard"
6161

6262
let! options, _diagnostics =
6363
checker.GetProjectOptionsFromScript(file, sourceText, assumeDotNetFramework = assumeDotNetFramework, useSdkRefs = not assumeDotNetFramework, otherFlags = otherOpts)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
let generateViolation range =
13+
let msg = Resources.GetString "RulesFavourSingleton"
14+
Array.singleton
15+
{ Range = range
16+
Message = msg
17+
SuggestedFix = None
18+
TypeChecks = List.Empty }
19+
match args.AstNode with
20+
| AstNode.Binding(SynBinding(_, _, _, _, _, _, _, _, _, expression, _, _, _)) ->
21+
match expression with
22+
| SynExpr.ArrayOrListComputed(_isArray, innerExpr, range) ->
23+
match innerExpr with
24+
| SynExpr.Const(_, range) ->
25+
generateViolation range
26+
| SynExpr.Ident _ ->
27+
generateViolation range
28+
| _ -> Array.empty
29+
| _ -> Array.empty
30+
| _ -> Array.empty
31+
let rule =
32+
AstNodeRule
33+
{ Name = "FavourSingleton"
34+
Identifier = Identifiers.FavourSingleton
35+
RuleConfig =
36+
{ AstNodeRuleConfig.Runner = runner
37+
Cleanup = ignore } }

src/FSharpLint.Core/Rules/Identifiers.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,4 @@ let EnsureTailCallDiagnosticsInRecursiveFunctions = identifier 85
9393
let FavourAsKeyword = identifier 86
9494
let InterpolatedStringWithNoSubstitution = identifier 87
9595
let IndexerAccessorStyleConsistency = identifier 88
96+
let FavourSingleton = identifier 89

src/FSharpLint.Core/Text.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,4 +387,7 @@
387387
<data name="RulesIndexerAccessorStyleConsistency" xml:space="preserve">
388388
<value>Consider switching the indexer accessor to {0} style.</value>
389389
</data>
390+
<data name="RulesFavourSingleton" xml:space="preserve">
391+
<value>Consider using List.singleton/Array.singleton instead of single member list/array.</value>
392+
</data>
390393
</root>

src/FSharpLint.Core/fsharplint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@
340340
"style": "OCaml"
341341
}
342342
},
343+
"favourSingleton": { "enabled": false },
343344
"hints": {
344345
"add": [
345346
"not (a = b) ===> a <> b",
@@ -450,7 +451,7 @@
450451
"pattern: x::[] ===> [x]",
451452

452453
"x @ [] ===> x",
453-
"[x] @ y ===> x::y",
454+
"(List.singleton x) @ y ===> x :: y",
454455

455456
"List.isEmpty [] ===> true",
456457
"Array.isEmpty [||] ===> true",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
<Compile Include="Rules\Conventions\FavourNonMutablePropertyInitialization.fs" />
4949
<Compile Include="Rules\Conventions\EnsureTailCallDiagnosticsInRecursiveFunctions.fs" />
5050
<Compile Include="Rules\Conventions\IndexerAccessorStyleConsistency.fs" />
51+
<Compile Include="Rules\Conventions\FavourSingleton.fs" />
5152
<Compile Include="Rules\Conventions\Naming\NamingHelpers.fs" />
5253
<Compile Include="Rules\Conventions\Naming\InterfaceNames.fs" />
5354
<Compile Include="Rules\Conventions\Naming\ExceptionNames.fs" />

0 commit comments

Comments
 (0)