Skip to content

Commit

Permalink
Core: add 2 new rules
Browse files Browse the repository at this point in the history
Add NestedFunctionNames and UnnestedFunctionNames rules.
These rules allow configuring naming conventions for nested and
unnested function names.
  • Loading branch information
su8898 authored and webwarrior-ws committed Dec 19, 2023
1 parent 815efa4 commit e4110c4
Show file tree
Hide file tree
Showing 12 changed files with 422 additions and 0 deletions.
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

Missing "return" keyword inside async blocks when throwing exceptions.

## Rationale

When returning values or throwing exception in async blocks, the "return" keyword must be used.

## How To Fix

Add "return" keyword to your raise/failwith/failwithf statment.

## 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
19 changes: 19 additions & 0 deletions src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,22 @@ let rec identFromSimplePat = function
| 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 _ =
function
| SynPat.LongIdent (longIdent, _, _, pats, _, _) ->
match pats with
| SynArgPats.Pats _ ->
match List.tryLast longIdent.Lid with
| Some ident -> (ident, ident.idText, None) |> Array.singleton
| None -> Array.empty
| _ -> Array.empty
| _ -> Array.empty
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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
getPatternIdents AccessControlLevel.Public getFunctionIdents 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,25 @@
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
getPatternIdents AccessControlLevel.Public getFunctionIdents 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,91 @@
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()
Loading

0 comments on commit e4110c4

Please sign in to comment.