Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 2 new rules NestedFunctionNames and UnnestedFunctionNames #564

Merged
merged 4 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/content/how-tos/rule-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,5 @@ The following rules can be specified for linting.
- [AvoidSinglePipeOperator (FL0077)](rules/FL0077.html)
- [AsyncExceptionWithoutReturn (FL0078)](rules/FL0078.html)
- [SuggestUseAutoProperty (FL0079)](rules/FL0079.html)
- [UnnestedFunctionNames (FL0080)](rules/FL0080.html)
- [NestedFunctionNames (FL0081)](rules/FL0081.html)
33 changes: 33 additions & 0 deletions docs/content/how-tos/rules/FL0080.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: FL0080
category: how-to
hide_menu: true
---

# UnnestedFunctionNames (FL0080)

*Introduced in `0.21.8`*

## Cause

Unnested function naming does not match the specified config.

## Rationale

Consistency aides readability.

## How To Fix

Update the unnested function names to be consistent with the rules you have specified.

## Rule Settings

{
"UnnestedFunctionNames": {
"enabled": false,
"config": {
"naming": "PascalCase",
"underscores": "None"
}
}
}
33 changes: 33 additions & 0 deletions docs/content/how-tos/rules/FL0081.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: FL0081
category: how-to
hide_menu: true
---

# NestedFunctionNames (FL0081)

*Introduced in `0.21.8`*

## Cause

Nested function naming does not match the specified config.

## Rationale

Consistency aides readability.

## How To Fix

Update the nested function names to be consistent with the rules you have specified.

## Rule Settings

{
"NestedFunctionNames": {
"enabled": false,
"config": {
"naming": "CamelCase",
"underscores": "None"
}
}
}
2 changes: 2 additions & 0 deletions src/FSharpLint.Core/FSharpLint.Core.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
<Compile Include="Rules\Conventions\Naming\LiteralNames.fs" />
<Compile Include="Rules\Conventions\Naming\NamespaceNames.fs" />
<Compile Include="Rules\Conventions\Naming\MemberNames.fs" />
<Compile Include="Rules\Conventions\Naming\UnnestedFunctionNames.fs" />
<Compile Include="Rules\Conventions\Naming\NestedFunctionNames.fs" />
<Compile Include="Rules\Conventions\Naming\ParameterNames.fs" />
<Compile Include="Rules\Conventions\Naming\MeasureTypeNames.fs" />
<Compile Include="Rules\Conventions\Naming\ActivePatternNames.fs" />
Expand Down
18 changes: 17 additions & 1 deletion src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,22 @@ let rec getPatternIdents<'T> (accessibility:AccessControlLevel) (getIdents:GetId
| SynPat.DeprecatedCharRange(_) | SynPat.InstanceMember(_) | SynPat.FromParseError(_) -> Array.empty

let rec identFromSimplePat = function
| SynSimplePat.Id(ident, _, _, _, _, _) -> Some(ident)
| SynSimplePat.Id(ident, _, _, _, _, _) -> Some ident
| SynSimplePat.Typed(p, _, _) -> identFromSimplePat p
| SynSimplePat.Attrib(_) -> None

let rec isNested args nodeIndex =
let parent = args.SyntaxArray.[nodeIndex].ParentIndex
let actual = args.SyntaxArray.[parent].Actual

match actual with
| AstNode.Expression (SynExpr.LetOrUse _) -> true
| _ -> false

let getFunctionIdents (pattern:SynPat) =
match pattern with
| SynPat.LongIdent (longIdent, _, _, SynArgPats.Pats _, _, _) ->
match List.tryLast longIdent.Lid with
| Some ident -> (ident, ident.idText, None) |> Array.singleton
| None -> Array.empty
| _ -> Array.empty
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module FSharpLint.Rules.NestedFunctionNames

open FSharp.Compiler.Syntax
open FSharpLint.Framework.Ast
open FSharpLint.Framework.AstInfo
open FSharpLint.Framework.Rules
open FSharpLint.Rules.Helper.Naming

let private getIdentifiers (args: AstNodeRuleParams) =
match args.AstNode with
| AstNode.Binding (SynBinding (_, _, _, _, _attributes, _, _, pattern, _, _, _, _)) ->
if isNested args args.NodeIndex then
let maxAccessibility = AccessControlLevel.Public
getPatternIdents maxAccessibility (fun _a11y innerPattern -> getFunctionIdents innerPattern) true pattern
else
Array.empty
| _ -> Array.empty

let rule config =
{ Name = "NestedFunctionNames"
Identifier = Identifiers.NestedFunctionNames
RuleConfig =
{ NamingRuleConfig.Config = config
GetIdentifiersToCheck = getIdentifiers } }
|> toAstNodeRule
|> AstNodeRule
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module FSharpLint.Rules.UnnestedFunctionNames

open FSharp.Compiler.Syntax
open FSharpLint.Framework.Ast
open FSharpLint.Framework.AstInfo
open FSharpLint.Framework.Rules
open FSharpLint.Rules.Helper.Naming

let private getIdentifiers (args: AstNodeRuleParams) =
match args.AstNode with
| AstNode.Binding (SynBinding (_, _, _, _, _attributes, _, _, pattern, _, _, _, _)) ->
if isNested args args.NodeIndex then
Array.empty
else
let maxAccessibility = AccessControlLevel.Public
getPatternIdents maxAccessibility (fun _a11y innerPattern -> getFunctionIdents innerPattern) true pattern
| _ -> Array.empty

let rule config =
{ Name = "UnnestedFunctionNames"
Identifier = Identifiers.UnnestedFunctionNames
RuleConfig =
{ NamingRuleConfig.Config = config
GetIdentifiersToCheck = getIdentifiers } }
|> toAstNodeRule
|> AstNodeRule
2 changes: 2 additions & 0 deletions src/FSharpLint.Core/Rules/Identifiers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,5 @@ let FavourStaticEmptyFields = identifier 76
let AvoidSinglePipeOperator = identifier 77
let AsyncExceptionWithoutReturn = identifier 78
let SuggestUseAutoProperty = identifier 79
let UnnestedFunctionNames = identifier 80
let NestedFunctionNames = identifier 81
14 changes: 14 additions & 0 deletions src/FSharpLint.Core/fsharplint.json
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,20 @@
"underscores": "AllowPrefix"
}
},
"unnestedFunctionNames": {
"enabled": false,
"config": {
"naming": "PascalCase",
"underscores": "None"
}
},
"nestedFunctionNames": {
"enabled": false,
"config": {
"naming": "CamelCase",
"underscores": "None"
}
},
"maxNumberOfItemsInTuple": {
"enabled": false,
"config": {
Expand Down
2 changes: 2 additions & 0 deletions tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
<Compile Include="Rules\Conventions\Naming\LiteralNames.fs" />
<Compile Include="Rules\Conventions\Naming\NamespaceNames.fs" />
<Compile Include="Rules\Conventions\Naming\MemberNames.fs" />
<Compile Include="Rules\Conventions\Naming\UnnestedFunctionNames.fs" />
<Compile Include="Rules\Conventions\Naming\NestedFunctionNames.fs" />
<Compile Include="Rules\Conventions\Naming\ParameterNames.fs" />
<Compile Include="Rules\Conventions\Naming\MeasureTypeNames.fs" />
<Compile Include="Rules\Conventions\Naming\ActivePatternNames.fs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
module FSharpLint.Core.Tests.Rules.Conventions.NestedFunctionNames

open NUnit.Framework
open FSharpLint.Framework.Rules
open FSharpLint.Rules

let config =
{ NamingConfig.Naming = Some NamingCase.CamelCase
Underscores = Some NamingUnderscores.None
Prefix = None
Suffix = None }

[<TestFixture>]
type TestConventionsNestedFunctionNames() =
inherit TestAstNodeRuleBase.TestAstNodeRuleBase(NestedFunctionNames.rule config)

[<Test>]
member this.UnnestedFunctionNameInPascalCaseMustBeIgnored() =
this.Parse """
module Program =
let CylinderVolume radius length =
let pi = 3.14159
length * pi * radius * radius"""

this.AssertNoWarnings()

[<Test>]
member this.NestedFunctionNameIsCamelCase() =
this.Parse """
module Program =
let CylinderVolume radius length =
let nestedFunction arg1 =
arg1 + 1

let pi = 3.14159
length * pi * radius * radius"""

this.AssertNoWarnings()

[<Test>]
member this.NestedFunctionNameIsPascalCase() =
this.Parse """
module Program =
let CylinderVolume radius length =
let NestedFunction arg1 =
arg1 + 1

let pi = 3.14159
length * pi * radius * radius"""

Assert.IsTrue(this.ErrorExistsAt(4, 8))

[<Test>]
member this.NestedFunctionNameInTypeIsPascalCase() =
this.Parse """
type Record =
{ Dog: int }
member this.CylinderVolume length radius =
let NestedFunction arg1 =
arg1 + 1
let pi = 3.14159
length * pi * radius * radius"""

Assert.IsTrue(this.ErrorExistsAt(5, 12))

[<Test>]
member this.UnnestedFunctionNameIsPascalCase() =
this.Parse """
module Program =
let CylinderVolume() =
let radius = 1
let pi = 3.14159
length * pi * radius * radius

let CylinderVolume2() =
let radius = 1
let pi = 3.14159
length * pi * radius * radius"""

this.AssertNoWarnings()

[<Test>]
member this.PrivateUnnestedFunctionNameIsPascalCase() =
this.Parse """
module Program =
let private CylinderVolume() =
let radius = 1
let pi = 3.14159
length * pi * radius * radius"""

this.AssertNoWarnings()

[<Test>]
member this.PrivateUnnestedFunctionNameIsCamelCase() =
this.Parse """
module Program =
let private cylinderVolume() =
let radius = 1
let pi = 3.14159
length * pi * radius * radius"""

this.AssertNoWarnings()
Loading