diff --git a/docs/content/how-tos/rule-configuration.md b/docs/content/how-tos/rule-configuration.md index 7870db243..3d02b3871 100644 --- a/docs/content/how-tos/rule-configuration.md +++ b/docs/content/how-tos/rule-configuration.md @@ -131,3 +131,4 @@ The following rules can be specified for linting. - [IndexerAccessorStyleConsistency (FL0088)](rules/FL0088.html) - [FavourSingleton (FL0089)](rules/FL0089.html) - [NoAsyncRunSynchronouslyInLibrary (FL0090)](rules/FL0090.html) +- [DisallowShadowing (FL0091)](rules/FL0091.html) diff --git a/docs/content/how-tos/rules/FL0091.md b/docs/content/how-tos/rules/FL0091.md new file mode 100644 index 000000000..4ca517ca0 --- /dev/null +++ b/docs/content/how-tos/rules/FL0091.md @@ -0,0 +1,29 @@ +--- +title: FL0091 +category: how-to +hide_menu: true +--- + +# DisallowShadowing (FL0091) + +*Introduced in `0.26.12`* + +## Cause + +A variable or parameter shadows another one with the same name. + +## Rationale + +Sometimes shadowing can cause confusion. + +## How To Fix + +Rename varaible or parameter in question so it has unique name in its scope. + +## Rule Settings + + { + "disallowShadowing": { + "enabled": false + } + } diff --git a/src/FSharpLint.Console/Program.fs b/src/FSharpLint.Console/Program.fs index d62691ed3..a46a3260b 100644 --- a/src/FSharpLint.Console/Program.fs +++ b/src/FSharpLint.Console/Program.fs @@ -175,9 +175,9 @@ let private lint handleLintResult lintResult with | exn -> - let target = if fileType = FileType.Source then "source" else target + let targetStr = if fileType = FileType.Source then "source" else target handleError - $"Lint failed while analysing %s{target}.{Environment.NewLine}Failed with: %s{exn.Message}{Environment.NewLine}Stack trace: {exn.StackTrace}" + $"Lint failed while analysing %s{targetStr}.{Environment.NewLine}Failed with: %s{exn.Message}{Environment.NewLine}Stack trace: {exn.StackTrace}" exitCode diff --git a/src/FSharpLint.Core/Application/Configuration.fs b/src/FSharpLint.Core/Application/Configuration.fs index f02086d1e..c9ca066d9 100644 --- a/src/FSharpLint.Core/Application/Configuration.fs +++ b/src/FSharpLint.Core/Application/Configuration.fs @@ -385,7 +385,8 @@ type ConventionsConfig = favourConsistentThis:RuleConfig option suggestUseAutoProperty:EnabledConfig option usedUnderscorePrefixedElements:EnabledConfig option - ensureTailCallDiagnosticsInRecursiveFunctions:EnabledConfig option} + ensureTailCallDiagnosticsInRecursiveFunctions:EnabledConfig option + disallowShadowing:EnabledConfig option} with member this.Flatten() = Array.concat @@ -413,6 +414,7 @@ with this.suggestUseAutoProperty |> Option.bind (constructRuleIfEnabled SuggestUseAutoProperty.rule) |> Option.toArray this.ensureTailCallDiagnosticsInRecursiveFunctions |> Option.bind (constructRuleIfEnabled EnsureTailCallDiagnosticsInRecursiveFunctions.rule) |> Option.toArray this.indexerAccessorStyleConsistency |> Option.bind (constructRuleWithConfig IndexerAccessorStyleConsistency.rule) |> Option.toArray + this.disallowShadowing |> Option.bind (constructRuleIfEnabled DisallowShadowing.rule) |> Option.toArray |] [] @@ -555,7 +557,8 @@ type Configuration = FavourAsKeyword:EnabledConfig option InterpolatedStringWithNoSubstitution:EnabledConfig option FavourSingleton:EnabledConfig option - NoAsyncRunSynchronouslyInLibrary:EnabledConfig option} + NoAsyncRunSynchronouslyInLibrary:EnabledConfig option + DisallowShadowing:EnabledConfig option } with static member Zero = { Global = None @@ -658,6 +661,7 @@ with InterpolatedStringWithNoSubstitution = None FavourSingleton = None NoAsyncRunSynchronouslyInLibrary = None + DisallowShadowing = None } // fsharplint:enable RecordFieldNames @@ -862,6 +866,7 @@ let flattenConfig (config:Configuration) = config.InterpolatedStringWithNoSubstitution |> Option.bind (constructRuleIfEnabled InterpolatedStringWithNoSubstitution.rule) config.FavourSingleton |> Option.bind (constructRuleIfEnabled FavourSingleton.rule) config.NoAsyncRunSynchronouslyInLibrary |> Option.bind (constructRuleIfEnabled NoAsyncRunSynchronouslyInLibrary.rule) + config.DisallowShadowing |> Option.bind (constructRuleIfEnabled DisallowShadowing.rule) |] findDeprecation config deprecatedAllRules allRules diff --git a/src/FSharpLint.Core/FSharpLint.Core.fsproj b/src/FSharpLint.Core/FSharpLint.Core.fsproj index e0810b760..52c24497c 100644 --- a/src/FSharpLint.Core/FSharpLint.Core.fsproj +++ b/src/FSharpLint.Core/FSharpLint.Core.fsproj @@ -60,6 +60,7 @@ + diff --git a/src/FSharpLint.Core/Framework/AstInfo.fs b/src/FSharpLint.Core/Framework/AstInfo.fs index 62bfd7c3e..da4a79a7f 100644 --- a/src/FSharpLint.Core/Framework/AstInfo.fs +++ b/src/FSharpLint.Core/Framework/AstInfo.fs @@ -120,9 +120,9 @@ module AstInfo = if Seq.isEmpty str then true else - let operator = List.tryFind (fun (op: string) -> str.StartsWith(op)) operators + let maybeOperator = List.tryFind (fun (op: string) -> str.StartsWith op) operators - match operator with + match maybeOperator with | Some(operator) -> str.Substring(operator.Length) |> isSequenceOfOperators | None -> false diff --git a/src/FSharpLint.Core/Framework/HintParser.fs b/src/FSharpLint.Core/Framework/HintParser.fs index 89b2eec85..54067ac58 100644 --- a/src/FSharpLint.Core/Framework/HintParser.fs +++ b/src/FSharpLint.Core/Framework/HintParser.fs @@ -83,9 +83,9 @@ module HintParser = [ attempt pident .>>. many (attempt (skipChar '.' >>. pident)) .>>. opt (skipChar '.' >>. pidentorop) - |>> (fun ((startIdent, idents), operator) -> + |>> (fun ((startIdent, idents), maybeOperator) -> let identifiers = startIdent::idents - match operator with + match maybeOperator with | Some(operator) -> identifiers@[operator] | None -> identifiers) attempt (pidentorop |>> fun identOrOpChars -> [identOrOpChars]) diff --git a/src/FSharpLint.Core/Framework/Utilities.fs b/src/FSharpLint.Core/Framework/Utilities.fs index a7d81f16e..b793c4a41 100644 --- a/src/FSharpLint.Core/Framework/Utilities.fs +++ b/src/FSharpLint.Core/Framework/Utilities.fs @@ -87,10 +87,10 @@ module ExpressionUtilities = /// Tries to find the source code within a given range. let tryFindTextOfRange (range:Range) (text:string) = - let startIndex = findPos range.Start text - let endIndex = findPos range.End text + let maybeStartIndex = findPos range.Start text + let maybeEndIndex = findPos range.End text - match (startIndex, endIndex) with + match (maybeStartIndex, maybeEndIndex) with | Some(startIndex), Some(endIndex) -> text.Substring(startIndex, endIndex - startIndex) |> Some | _ -> None @@ -141,7 +141,8 @@ module ExpressionUtilities = |> Option.defaultValue 0 let rangeContainsOtherRange (containingRange:Range) (range:Range) = - range.StartLine >= containingRange.StartLine && range.EndLine <= containingRange.EndLine + (range.StartLine, range.StartColumn) >= (containingRange.StartLine, containingRange.StartColumn) + && (range.EndLine, range.EndColumn) <= (containingRange.EndLine, containingRange.EndColumn) module String = diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/TupleOfWildcards.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/TupleOfWildcards.fs index 87821f70f..581a46d5e 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/TupleOfWildcards.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/TupleOfWildcards.fs @@ -9,7 +9,7 @@ open FSharpLint.Framework.Rules let private checkTupleOfWildcards fileContents pattern identifier identifierRange = let rec isWildcard = function - | SynPat.Paren(pattern, _) -> isWildcard pattern + | SynPat.Paren(innerPattern, _) -> isWildcard innerPattern | SynPat.Wild(_) -> true | _ -> false diff --git a/src/FSharpLint.Core/Rules/Conventions/DisallowShadowing.fs b/src/FSharpLint.Core/Rules/Conventions/DisallowShadowing.fs new file mode 100644 index 000000000..92cb0559e --- /dev/null +++ b/src/FSharpLint.Core/Rules/Conventions/DisallowShadowing.fs @@ -0,0 +1,162 @@ +module FSharpLint.Rules.DisallowShadowing + +open System +open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.Syntax +open FSharpLint.Framework +open FSharpLint.Framework.Suggestion +open FSharpLint.Framework.Ast +open FSharpLint.Framework.Rules + +let private extractIdentifiersFromSimplePats (simplePats: SynSimplePats) : List = + let rec extractIdentifier (pattern: SynSimplePat) = + match pattern with + | SynSimplePat.Id(ident, _, _, _, _, _) -> + ident + | SynSimplePat.Attrib(pat, _, _) + | SynSimplePat.Typed(pat, _, _) -> + extractIdentifier pat + + match simplePats with + | SynSimplePats.SimplePats(patterns, _, _) -> + patterns |> List.map extractIdentifier + + +let private checkIdentifier (args: AstNodeRuleParams) (identifier: Ident) : array = + let name = identifier.idText + match args.CheckInfo with + | Some checkResults when not (name.StartsWith '_') -> + let allUsages = checkResults.GetAllUsesOfAllSymbolsInFile() + let definitionsWithSameName = + allUsages + |> Seq.filter (fun usage -> usage.IsFromDefinition && usage.Symbol.DisplayName = name) + + let definitionsBeforeCurrent = + definitionsWithSameName + |> Seq.filter + (fun usage -> + (usage.Range.StartLine, usage.Range.StartColumn) + < (identifier.idRange.StartLine, identifier.idRange.StartColumn) ) + |> Seq.toArray + + let rangeIncludedsDefinitions (definitions: array) range = + definitions + |> Array.exists (fun usage -> ExpressionUtilities.rangeContainsOtherRange range usage.Range) + + let rangeIncludedsDefinitionsBeforeCurrent = + rangeIncludedsDefinitions definitionsBeforeCurrent + + let processBinding binding = + match binding with + | SynBinding(_, _, _, _, _, _, _, _, _, _, range, _, _) -> + rangeIncludedsDefinitionsBeforeCurrent range + + let processArgs (args: SynSimplePats) = + args + |> extractIdentifiersFromSimplePats + |> List.exists (fun ident -> rangeIncludedsDefinitionsBeforeCurrent ident.idRange) + + let rec processExpression (expression: SynExpr) = + match expression with + | SynExpr.LetOrUse(_, _, bindings, _, _, _) -> + bindings |> List.exists processBinding + | SynExpr.Sequential(_, _, expr1, expr2, _, _) -> + processExpression expr1 || processExpression expr2 + | SynExpr.Lambda(_, _, args, body, _, _, _) -> + processExpression body || processArgs args + | _ -> false + + let rec processPattern (definitions: array) (pattern: SynPat) = + match pattern with + | SynPat.Named(SynIdent(ident, _), _, _, _) -> rangeIncludedsDefinitions definitions ident.idRange + | SynPat.Ands(pats, _) -> pats |> List.exists (processPattern definitions) + | SynPat.Or(lhs, rhs, _, _) -> + let definitionsExcludingLhs = + definitions + |> Array.filter (fun usage -> not <| ExpressionUtilities.rangeContainsOtherRange lhs.Range usage.Range) + processPattern definitionsExcludingLhs lhs || processPattern definitionsExcludingLhs rhs + | SynPat.ArrayOrList(_, pats, _) -> pats |> List.exists (processPattern definitions) + | SynPat.As(_lhs, rhs, _) -> processPattern definitions rhs + | SynPat.OptionalVal(ident, _) -> rangeIncludedsDefinitions definitions ident.idRange + | SynPat.Paren(pat, _) -> processPattern definitions pat + | SynPat.Record(fieldPats, _) -> fieldPats |> List.exists (fun (_, _, pat) -> processPattern definitions pat) + | SynPat.Tuple(_, pats, _, _) -> pats |> List.exists (processPattern definitions) + | SynPat.Typed(pat, _, _) -> processPattern definitions pat + | _ -> false + + let processParentBinding (binding: SynBinding) = + if ExpressionUtilities.rangeContainsOtherRange binding.RangeOfBindingWithRhs identifier.idRange then + // inside binding's scope + rangeIncludedsDefinitionsBeforeCurrent binding.RangeOfHeadPattern + else + // outside binding's scope + match binding with + | SynBinding(_, _, _, _, _, _, _, headPat, _, _, _, _, _) -> + match headPat with + | SynPat.Named(SynIdent(ident, _), _, _, _) -> rangeIncludedsDefinitionsBeforeCurrent ident.idRange + | SynPat.LongIdent(SynLongIdent(identParts, _, _), _, _, _, _, _) -> + identParts |> List.exists (fun ident -> rangeIncludedsDefinitionsBeforeCurrent ident.idRange) + | _ -> false + + let processModuleDeclaration (moduleDecl: SynModuleDecl) = + match moduleDecl with + | SynModuleDecl.Let(_, bindings, _) -> + bindings + |> List.exists processParentBinding + | _ -> false + + let processAstNode (node: AstNode) = + match node with + | ModuleOrNamespace(SynModuleOrNamespace(_, _, _, declarations, _, _, _, _, _)) -> + declarations |> List.exists processModuleDeclaration + | Expression(expression) -> + processExpression (ExpressionUtilities.removeParens expression) + | Lambda(lambda, _) -> + processExpression lambda.Body + || (lambda.Arguments |> List.exists processArgs) + | Match(SynMatchClause(pattern, _, _, _, _, _)) -> + processPattern definitionsBeforeCurrent pattern + | MemberDefinition(SynMemberDefn.Member(memberDefn, _)) -> + processBinding memberDefn + | TypeDefinition(SynTypeDefn(_, _, _, Some(SynMemberDefn.ImplicitCtor(_, _, ctorArgs, _, _, _, _)), _, _)) -> + processPattern definitionsBeforeCurrent ctorArgs + | _ -> false + + let parents = args.GetParents args.NodeIndex + let isShadowing = + let currentDefinition = + definitionsWithSameName |> Seq.tryFind (fun definition -> definition.Range = identifier.idRange) + match currentDefinition with + | Some _ -> + parents |> List.exists processAstNode + | None -> + false + + if isShadowing then + Array.singleton { + Range = identifier.idRange + Message = Resources.GetString "RulesDisallowShadowing" + SuggestedFix = None + TypeChecks = List.Empty } + else + Array.empty + + | _ -> Array.empty + +let runner (args: AstNodeRuleParams) = + match args.AstNode with + | Pattern(SynPat.Named(SynIdent(ident, _), _, _, _)) -> + checkIdentifier args ident + | Lambda(lambda, _) -> + lambda.Arguments + |> List.collect extractIdentifiersFromSimplePats + |> List.toArray + |> Array.collect (fun each -> checkIdentifier args each) + | _ -> + Array.empty + +let rule = + { Name = "DisallowShadowing" + Identifier = Identifiers.DisallowShadowing + RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } + |> AstNodeRule diff --git a/src/FSharpLint.Core/Rules/Identifiers.fs b/src/FSharpLint.Core/Rules/Identifiers.fs index 01889c1bc..a20a2ff63 100644 --- a/src/FSharpLint.Core/Rules/Identifiers.fs +++ b/src/FSharpLint.Core/Rules/Identifiers.fs @@ -95,3 +95,4 @@ let InterpolatedStringWithNoSubstitution = identifier 87 let IndexerAccessorStyleConsistency = identifier 88 let FavourSingleton = identifier 89 let NoAsyncRunSynchronouslyInLibrary = identifier 90 +let DisallowShadowing = identifier 91 diff --git a/src/FSharpLint.Core/Rules/Smells/AsyncExceptionWithoutReturn.fs b/src/FSharpLint.Core/Rules/Smells/AsyncExceptionWithoutReturn.fs index 978ed38ef..48a1878f1 100644 --- a/src/FSharpLint.Core/Rules/Smells/AsyncExceptionWithoutReturn.fs +++ b/src/FSharpLint.Core/Rules/Smells/AsyncExceptionWithoutReturn.fs @@ -17,28 +17,28 @@ let rec checkExpression (expression: SynExpr) (range: range) (continuation: unit firstExpression range (fun () -> (checkExpression secondExpression secondExpression.Range returnEmptyArray)) - | SynExpr.Paren (innerExpression, _, _, range) -> checkExpression innerExpression range returnEmptyArray - | SynExpr.While (_, _, innerExpression, range) -> checkExpression innerExpression range returnEmptyArray - | SynExpr.For (_, _, _, _, _, _, _, innerExpression, range) -> checkExpression innerExpression range returnEmptyArray - | SynExpr.ForEach (_, _, _, _, _, _, innerExpression, range) -> checkExpression innerExpression range returnEmptyArray - | SynExpr.Match (_, _, clauses, range, _) -> - let subExpressions = clauses |> List.map (fun (SynMatchClause (_, _, clause, range, _, _)) -> (clause, range)) + | SynExpr.Paren (innerExpression, _, _, innerRange) -> checkExpression innerExpression innerRange returnEmptyArray + | SynExpr.While (_, _, innerExpression, innerRange) -> checkExpression innerExpression innerRange returnEmptyArray + | SynExpr.For (_, _, _, _, _, _, _, innerExpression, innerRange) -> checkExpression innerExpression innerRange returnEmptyArray + | SynExpr.ForEach (_, _, _, _, _, _, innerExpression, innerRange) -> checkExpression innerExpression innerRange returnEmptyArray + | SynExpr.Match (_, _, clauses, innerRange, _) -> + let subExpressions = clauses |> List.map (fun (SynMatchClause (_, _, clause, innerRange, _, _)) -> (clause, innerRange)) checkMultipleExpressions subExpressions returnEmptyArray - | SynExpr.Do (innerExpression, range) -> checkExpression innerExpression range returnEmptyArray + | SynExpr.Do (innerExpression, innerRange) -> checkExpression innerExpression innerRange returnEmptyArray | SynExpr.TryWith (tryExpression, withCases, tryRange, _, _, _) -> let subExpressions = withCases |> List.map (fun (SynMatchClause (_, _, withCase, withRange, _, _)) -> (withCase, withRange)) checkMultipleExpressions subExpressions (fun () -> checkExpression tryExpression tryRange returnEmptyArray) - | SynExpr.TryFinally (tryExpression, finallyExpr, range, _, _, _) -> - checkExpression tryExpression range (fun () -> checkExpression finallyExpr range returnEmptyArray) - | SynExpr.IfThenElse (_, thenExpr, elseExpr, _, _, range, _) -> + | SynExpr.TryFinally (tryExpression, finallyExpr, innerRange, _, _, _) -> + checkExpression tryExpression range (fun () -> checkExpression finallyExpr innerRange returnEmptyArray) + | SynExpr.IfThenElse (_, thenExpr, elseExpr, _, _, innerRange, _) -> checkExpression thenExpr - range + innerRange (fun () -> match elseExpr with | Some elseExpression -> - checkExpression elseExpression range returnEmptyArray + checkExpression elseExpression innerRange returnEmptyArray | None -> Array.empty) | SynExpr.App (_, _, SynExpr.Ident failwithId, _, _) when @@ -53,18 +53,18 @@ let rec checkExpression (expression: SynExpr) (range: range) (continuation: unit SuggestedFix = None TypeChecks = List.Empty } - | SynExpr.App (_, _, funcExpr, _, range) -> - checkExpression funcExpr range returnEmptyArray - | SynExpr.LetOrUse (_, _, _, body, range, _) -> - checkExpression body range returnEmptyArray + | SynExpr.App (_, _, funcExpr, _, innerRange) -> + checkExpression funcExpr innerRange returnEmptyArray + | SynExpr.LetOrUse (_, _, _, body, innerRange, _) -> + checkExpression body innerRange returnEmptyArray | _ -> Array.empty) (continuation ()) and [] checkMultipleExpressions (expressions: list) (continuation: unit -> array) = match expressions with - | (expression, range) :: tail -> + | (expression, innerRange) :: tail -> checkExpression expression - range + innerRange (fun () -> checkMultipleExpressions tail diff --git a/src/FSharpLint.Core/Text.resx b/src/FSharpLint.Core/Text.resx index a6f38a3f4..0bea583b5 100644 --- a/src/FSharpLint.Core/Text.resx +++ b/src/FSharpLint.Core/Text.resx @@ -393,4 +393,7 @@ Async.RunSynchronously should not be used in libraries. + + Rename variable or parameter so that it doesn't shadow another one with the same name. + diff --git a/src/FSharpLint.Core/fsharplint.json b/src/FSharpLint.Core/fsharplint.json index 7b32a6b17..585e0cade 100644 --- a/src/FSharpLint.Core/fsharplint.json +++ b/src/FSharpLint.Core/fsharplint.json @@ -342,6 +342,7 @@ }, "favourSingleton": { "enabled": false }, "noAsyncRunSynchronouslyInLibrary": { "enabled": true }, + "disallowShadowing": { "enabled": false }, "hints": { "add": [ "not (a = b) ===> a <> b", diff --git a/tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj b/tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj index d57f7fff5..b2aa4cb8e 100644 --- a/tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj +++ b/tests/FSharpLint.Core.Tests/FSharpLint.Core.Tests.fsproj @@ -50,6 +50,7 @@ + diff --git a/tests/FSharpLint.Core.Tests/Rules/Conventions/DisallowShadowing.fs b/tests/FSharpLint.Core.Tests/Rules/Conventions/DisallowShadowing.fs new file mode 100644 index 000000000..49452c5da --- /dev/null +++ b/tests/FSharpLint.Core.Tests/Rules/Conventions/DisallowShadowing.fs @@ -0,0 +1,190 @@ +module FSharpLint.Core.Tests.Rules.Conventions.DisallowShadowing + +open NUnit.Framework + +open FSharpLint.Rules +open FSharpLint.Core.Tests + +[] +type TestConventionsDisallowShadowing() = + inherit TestAstNodeRuleBase.TestAstNodeRuleBase(DisallowShadowing.rule) + + [] + member this.``Should produce error for shadowed variable``() = + this.Parse """ +let foo = 0 + +module Foo = + let foo = 1""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable, inside function``() = + this.Parse """ +let bar () = + let foo = 0 + let foo = 1 + foo""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable (function argument)``() = + this.Parse """ +let foo = 0 +let bar foo = foo + 1""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable (function argument, inside function)``() = + this.Parse """ +let baz () = + let foo = 0 + let bar foo = foo + 1 + ()""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable (function argument, nested)``() = + this.Parse """ +let baz foo = + let bar foo = foo + 1 + ()""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable (function argument, tuple)``() = + this.Parse """ +let foo = 0 +let bar (foo, baz) = foo + baz""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable (lambda function argument)``() = + this.Parse """ +let foo = 0 +(fun foo -> foo + 1) 0 |> ignore""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable inside lambda function``() = + this.Parse """ +(fun foo -> + let foo = foo + 1 + foo) 0 |> ignore""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable (match pattern)``() = + this.Parse """ +let foo = 0 +match 1 with +| foo -> foo""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable (as match pattern)``() = + this.Parse """ +let foo = 0 +match (1, 2) with +| (x, y) as foo -> foo""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable inside match pattern``() = + this.Parse """ +match 1 with +| foo -> + let foo = foo + 1 + foo""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable inside type definition``() = + this.Parse """ +type Foo(foo) = + let foo = foo + 1 + + member this.Bar = foo""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should produce error for shadowed variable inside member definition``() = + this.Parse """ +type Foo() = + member this.Bar(foo) = + let foo = foo + 1 + foo""" + + Assert.IsTrue this.ErrorsExist + + [] + member this.``Should not produce error when variable with same name exists in another module``() = + this.Parse """ +module Foo = + let foo = 0 +let foo = 1""" + + Assert.IsTrue this.NoErrorsExist + + [] + member this.``Should not produce error when parameter with same name exists in another function``() = + this.Parse """ +let foo target = + target + +let bar target = + target + 1""" + + Assert.IsTrue this.NoErrorsExist + + [] + member this.``Should not produce error when variable name starts with underscore``() = + this.Parse """ +let _foo = 0 + +module Foo = + let _foo = 1""" + + Assert.IsTrue this.NoErrorsExist + + [] + member this.``Should not produce error for bindings in match patterns that are not shadowing``() = + this.Parse """ +match node with +| HintNode(expr, depth, rest) -> + match (getKey expr, expr) with + | (SyntaxHintNode.Wildcard as key), HintExpr(Expression.Wildcard) + | (SyntaxHintNode.Variable as key), HintPat(Pattern.Variable(_)) -> Some(key, expr, depth, rest) + | _ -> None +| EndOfHint(_) -> None""" + + Assert.IsTrue this.NoErrorsExist + + [] + member this.``Should not produce error for vars used as args to active patterns``() = + this.Parse """ +let private (|RaiseWithTooManyArgs|_|) identifier maxArgs = function + | ExpressionUtilities.Identifier([ ident ], _)::arguments + when List.length arguments > maxArgs && ident.idText = identifier -> + Some() + | _ -> None + +let checkRaiseWithTooManyArgs (raiseType:string) (count:int) = + match expressions with + | RaiseWithTooManyArgs raiseType count -> Array.empty + | _ -> Array.empty +""" + + Assert.IsTrue this.NoErrorsExist