You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Rules should not be based on personal opinion. They should be based on (and point to) official recommendations or other well-reasoned/researched sources. If you want a rule that is not covered there, try to get it added to official coding guidelines first.
Rules should be sufficiently generally applicable that they can be enabled by default.
Configuration options should be minimal. Like Fantomas, Flinter should do "the right thing" out of the box and push toward uniform(ly good) F# code. Flinter should improve the F# ecosystem; that is harder to do when everyone can configure everything.
Out of scope
Code formatting (handled by Fantomas)
Anything that is better handled by the compiler itself (raise an issue there instead)
Ideas for categories (not complete)
Complexity (e.g., cyclomatic complexity, function/method size, number of activated entities)
Consistency (e.g., naming)
Correctness (e.g., missing use, recursive async CE via do! instead of return!, recursive task CE, missing tail recursion (or is that not generally useful?))
Performance (e.g., use struct single-case DUs)
Redundancy (e.g., dot in index notation, named unit values, string applied to string argument, etc.)
Quick-notes, to be elaborated/merged into the below:
let/let! instead of use/use! on IDisposable and IAsyncDisposable (for bang operators, depending on the CE, these may be wrapped in arbitrary container types like Async or Task)
Named parameters or values that are typed to unit (e.g. Option.defaultWith (fun thisIsUnit -> ...))
Usage of Option.Value, List.Head and similar?
Recommend Sealed and AbstractClass on types with only static members?
Don't catch an exception and throw from the with expression without the thrown exception containing the caught exception (loses stack trace and other information)
Use string interpolation instead of sprintf, failwithf etc. (when not piped)
Limit number of "activated objects" in any given function (section 7.2.7 of the book)
A method should interact with a maximum number of local variables, method parameters, and class fields.
As a rule of thumb, try to keep this number at 7 or below. Keeping it low ensures that the method implementation fits in your head.
What about nested functions? There's no single-level "Class -> Method" structure; functions can have arbitrarily nested inner functions (often closing over some values from the outer scope).
Cyclomatic complexity (section 7.1.2 of the book)
It starts at 1 and increases by 1 for each branch. This includes not only if/elseif/else, but also ternary operators, null coalescing operators, pattern match cases, loop bodies, etc.
Counting all pattern match cases would make it impossible to avoid this rule. A single function with a top-level pattern match against an enum with too many cases would trigger this rule, and nothing sensible could be done about it.
As a rule of thumb, try to keep this number at 7 or below. Keeping it low ensures that the method implementation fits in your head.
Parse, don't validate (section section 7.2.5 of the book)
If you just validate a string (or any other type, for that matter) and then pass that string further into your code, other parts of the code have no guarantees about what you validated, and it's hard to know at any point in the code what guarantees the string comes with. Should the deeper code validate the string again? It's impossible to know at that level; it must assume knowledge about what happens outside it.
Instead, when you validate data, wrap it in a type that carries the guarantees you desire. Do this as soon as possible in your code (i.e., just after receiving it from the outside world), and then only work with the custom type inside your code.
As a simple example, if you receive an email address via a string in an API, once you have validated that the string is a valid email address, wrap the string in a custom EmailAddress type and use that everywhere else. All code that uses EmailAddress knows that they have a valid email address, as opposed to if they had just worked with a plain string.
How to implement this? E.g. if passing a primitive like a string into a function, passing it to a predicate (a bool-returning function), and then returning the string (unmodified). Might be complicated.
Not sure how to implement this, since it's impossible to know generally which expressions have side effect. But could trigger on:
mutable/ref module-level values
module-level calls to functions/methods that accept or return unit (also check in module-level values, recursively)
This will likely also detect partial application of function that are bound as values, not functions, where a partially applied parameter is impure. For example, let f = f' a b if a or b (any part of them if multi-part) are mutable.
Also detect a list of well-known impure calls that are not caught by the above rules? E.g. DateTime.Now etc.
Do not have a property that when prefixed with Get has the same name as a method. This pattern typically indicates that the property should really be a method. (What about Set?)
DO name collection properties with a plural phrase describing the items in the collection instead of using a singular phrase followed by "List" or "Collection".
Events should not be prefixed or suffixed with Before or After
AVOID using marker interfaces (interfaces with no members). Use an attribute instead.
DO provide at least one type that is an implementation of an interface.
DO provide at least one API that consumes each interface you define (a method taking the interface as a parameter or a property typed as the interface).
Don't throw Exception (failwith/failwithf) or SystemException; use a more specific exception
But does this really matter if it's an exception that should never occur? For example, Felicity throws many exceptions from its internals that are never intended to be caught, just to supply a helpful error message.
DO NOT catch System.Exception or System.SystemException in framework code, unless you intend to rethrow.
AVOID catching System.Exception or System.SystemException, except in top-level exception handlers.
DO NOT throw or derive from ApplicationException.
DO NOT allow publicly callable APIs to explicitly or implicitly throw NullReferenceException, AccessViolationException, or IndexOutOfRangeException. These exceptions are reserved and thrown by the execution engine and in most cases indicate a bug.
DO NOT explicitly throw StackOverflowException
DO NOT catch StackOverflowException.
DO NOT explicitly throw OutOfMemoryException
DO NOT explicitly throw COMException, ExecutionEngineException, and SEHException`
Check existing linter rules for ideas (note: Before implementation, they should be grounded in something more than just "implemented in another linter")
FSharpLint
Recursive async functions should end with return! not do!
Redundant new keyword?
failwith, raise, nulArg, invalidOp being passed more than one argument
invalidArg being passed more than two arguments
failwithf being passed more arguments than contained in the format string
All the "max lines in ..." and "max number of ...": Are these all better covered by "cyclomatic complexity" and "max number of activated objects" (from "Code that fits in your head")?
Simplify lambda (only if lambda re-implements function, no partial application? There are some important false positives here; see own reported issues in JetBrains issue tracker for examples)
Replace lambda with piping/composition (see caveats above)
Avoid partial functions like List.find (but what it you know it will exist? Pointless to use pattern matching only to have the None case be failwith "Unreachable code")
Favor typed ignore
Use reraise () instead of raise inside a try ... with block (not applicable inside a CE)
Consistent this identifier in classes/interfaces (is this covered by the F# style guide?)
This issue serves as notes for rule ideas.
In general
Out of scope
Ideas for categories (not complete)
missing, recursiveuse
async
CE viado!
instead ofreturn!
, recursivetask
CE, missing tail recursion (or is that not generally useful?))unit
values,string
applied to string argument, etc.)Rule ideas
Quick-notes, to be elaborated/merged into the below:
let
/let!
instead ofuse
/use!
onIDisposable
andIAsyncDisposable
(for bang operators, depending on the CE, these may be wrapped in arbitrary container types likeAsync
orTask
)Foo<'a>
) except foroption
,voption
,list
,[]
,ref
(source: formatting guidelines)Option.defaultWith (fun thisIsUnit -> ...)
)Option.Value
,List.Head
and similar?Sealed
andAbstractClass
on types with only static members?with
expression without the thrown exception containing the caught exception (loses stack trace and other information)sprintf
,failwithf
etc. (when not piped)Guidelines from the book "Code that fits in your head" by Mark Seemann
Limit number of "activated objects" in any given function (section 7.2.7 of the book)
Cyclomatic complexity (section 7.1.2 of the book)
Parse, don't validate (section section 7.2.5 of the book)
As a simple example, if you receive an email address via a string in an API, once you have validated that the string is a valid email address, wrap the string in a custom
EmailAddress
type and use that everywhere else. All code that usesEmailAddress
knows that they have a valid email address, as opposed to if they had just worked with a plain string.bool
-returning function), and then returning the string (unmodified). Might be complicated.Guidelines from F# coding conventions
Prefer namespaces instead of modules at the top level
module Foo.Bar
as a namespaceCarefully apply AutoOpen
Use classes to contain values that have side effects
unit
(also check in module-level values, recursively)let f = f' a b
ifa
orb
(any part of them if multi-part) are mutable.DateTime.Now
etc.Do not use monadic error handling to replace exceptions
Result.Error
or aChoice
case with an exceptionError exn.Message
?_
and returningNone
Strive to keep all helper functionality private
Helper
,Helpers
,Util
,Utils
, etc.?Explicitly annotate all parameter and return types in public functions
OutputType
isExe
)Consider giving a meaningful name to your generic arguments
'a
,'b
,'T
, etc. Only if public, and only in libraries, not console apps (whereOutputType
isExe
)Consider naming generic type parameters with PascalCase
'a
,'b'
, etc., since that is common in F#Consider structs for small types with high allocation rates
Prefer
let mutable
toref
Avoid the use of the
AllowNullLiteral
attributeAvoid the use of the
Unchecked.defaultof<_>
Avoid the use of the
DefaultValue
attributeAvoid inheritance-based type hierarchies and implementation inheritance
Use object expressions to implement interfaces if you don't need a class
Guidelines from F# component design guidelines
Use XML docs for the entire public API surface (applicable for libraries)
Follow .NET naming conventions
Use namespaces or modules to contain your types and modules
Is this only relevant for single-file projects? For multi-file projects, the compiler enforces this, right?
Use interfaces to group related operations
Hide the representations of record and union types if the design of these types is likely to evolve
Avoid the use of implementation inheritance for extensibility
Don't return long tuples
Use correct Async prefix/postfix for Async/Task overloads of sync methods
Avoid defining custom symbolic operators
Carefully use type abbreviations)
Rules from the sections relevant only when intending consumption from other .NET languages (not 1st priority)
Stuff from .NET framework design guidelines
Naming guidelines - common capitalization mistakes
userName
(username seems to be a closed-form compound word)Names of common types
Attribute
suffix for types inheriting fromSystem.Attribute
EventArgs
suffix for types inheriting fromSystem.EventArgs
Enum
,Flag
, orFlags
Dictionary
suffix for types implementingIDictionary
Collection
suffix for types implementingIEnumerable
,ICollection
, orIList
Stream
suffix for types inheriting fromSystem.IO.Stream
System.FlagsAttribute
System.FlagsAttribute
Member names
Get
has the same name as a method. This pattern typically indicates that the property should really be a method. (What aboutSet
?)Before
orAfter
Parameter names
left
andright
for binary operator overload parameter names if there is no meaning to the parameters.value
for unary operator overload parameter names if there is no meaning to the parameters.Interface design
Struct design
IEquatable<T>
on value types.Enum design
System.FlagsAttribute
)Property design
Int32
,Int64
,String
,Object
, or an enum.Event design (low priority?)
Field design
Parameter design
Exception throwing
**Using standard exception types
Exception
(failwith
/failwithf
) orSystemException
; use a more specific exceptionSystem.Exception
orSystem.SystemException
in framework code, unless you intend to rethrow.ApplicationException
.NullReferenceException
,AccessViolationException
, orIndexOutOfRangeException
. These exceptions are reserved and thrown by the execution engine and in most cases indicate a bug.StackOverflowException
StackOverflowException
.OutOfMemoryException
COMException,
ExecutionEngineException, and
SEHException`Usage guidelines: Arrays
Usage guidelines: Attributes
Usage guidelines: Collections
Officially documented best practices:
Check existing linter rules for ideas (note: Before implementation, they should be grounded in something more than just "implemented in another linter")
FSharpLint
return!
notdo!
new
keyword?failwith
,raise
,nulArg
,invalidOp
being passed more than one argumentinvalidArg
being passed more than two argumentsfailwithf
being passed more arguments than contained in the format stringignore
overlet _
_ as x
withx
(_, _)
with wildcard_
List.find
(but what it you know it will exist? Pointless to use pattern matching only to have theNone
case befailwith "Unreachable code"
)reraise ()
instead ofraise
inside atry ... with
block (not applicable inside a CE)this
identifier in classes/interfaces (is this covered by the F# style guide?)Various Roslyn analyzers
TODO: For all rules that work with accessibility modifiers, what if signatures files are used?
Dev notes
The text was updated successfully, but these errors were encountered: