diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 9c8817e4a..9785438c9 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -3,4 +3,10 @@ # that they are unlikely to be what you are interested in when blaming. # Like formatting with Fantomas # https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view -# Add formatting commits here + +# Console,Core,Tests: fix FSharpLint warning (Fixing the warning for AvoidTooShortNames rule.) +e9410e83af1de4d76230b0e8d44ae6a99fac6d7b + +# Core: rename some funcs and params +628b1786e7f913ecd87e42a45ab7f195c536a6cb + diff --git a/build.fsx b/build.fsx index 3430f4ca5..ebdc6b542 100644 --- a/build.fsx +++ b/build.fsx @@ -39,6 +39,7 @@ open Fake.Api open System open System.IO +open System.Text.Json.Nodes Target.initEnvironment() @@ -249,12 +250,64 @@ Target.create "Push" (fun _ -> Target.create "SelfCheck" (fun _ -> - let srcDir = Path.Combine(rootDir.FullName, "src") |> DirectoryInfo - - let consoleProj = Path.Combine(srcDir.FullName, "FSharpLint.Console", "FSharpLint.Console.fsproj") |> FileInfo - let sol = Path.Combine(rootDir.FullName, solutionFileName) |> FileInfo - exec "dotnet" $"run --framework net9.0 lint %s{sol.FullName}" consoleProj.Directory.FullName -) + let runLinter () = + let srcDir = Path.Combine(rootDir.FullName, "src") |> DirectoryInfo + + let consoleProj = Path.Combine(srcDir.FullName, "FSharpLint.Console", "FSharpLint.Console.fsproj") |> FileInfo + let sol = Path.Combine(rootDir.FullName, solutionFileName) |> FileInfo + + exec "dotnet" $"run --framework net9.0 lint %s{sol.FullName}" consoleProj.Directory.FullName + + printfn "Running self-check with default rules..." + runLinter () + + let fsharplintJsonDir = Path.Combine("src", "FSharpLint.Core", "fsharplint.json") + let fsharplintJsonText = File.ReadAllText fsharplintJsonDir + + let excludedRules = + [ + // Formatting rules (maybe mark them as DEPRECATED soon, recommending the use of `fantomas` instead) + "typedItemSpacing" + "unionDefinitionIndentation" + "moduleDeclSpacing" + "classMemberSpacing" + "tupleCommaSpacing" + "tupleIndentation" + "patternMatchClausesOnNewLine" + "patternMatchOrClausesOnNewLine" + "patternMatchClauseIndentation" + "patternMatchExpressionIndentation" + "indentation" + "maxCharactersOnLine" + "trailingNewLineInFile" + "trailingWhitespaceOnLine" + + // TODO: we should enable at some point + "typePrefixing" + "unnestedFunctionNames" + "nestedFunctionNames" + "nestedStatements" + + // Running NoPartialFunctions on this file causes a bug in FSharp.Compiler, so skip it for now + "noPartialFunctions" + + // rule is too complex, we can enable it later + "cyclomaticComplexity" + ] + + let jsonObj = JsonObject.Parse fsharplintJsonText + + for pair in jsonObj.AsObject() do + if pair.Value.GetValueKind() = Text.Json.JsonValueKind.Object then + match pair.Value.AsObject().TryGetPropertyValue("enabled") with + | true, isRule when not (List.contains pair.Key excludedRules) -> + isRule.AsValue().ReplaceWith true + | _ -> () + + File.WriteAllText(fsharplintJsonDir, jsonObj.ToJsonString()) + + printfn "Now re-running self-check with more rules enabled..." + runLinter ()) // -------------------------------------------------------------------------------------- // Build order diff --git a/src/FSharpLint.Console/Output.fs b/src/FSharpLint.Console/Output.fs index b0b48f071..089a9cda0 100644 --- a/src/FSharpLint.Console/Output.fs +++ b/src/FSharpLint.Console/Output.fs @@ -23,7 +23,7 @@ type StandardOutput () = if String.length errorLine = 0 then "^" else errorLine - |> Seq.mapi (fun i _ -> if i = range.StartColumn then "^" else " ") + |> Seq.mapi (fun index _ -> if index = range.StartColumn then "^" else " ") |> Seq.reduce (+) $"{getErrorMessage range}{Environment.NewLine}{errorLine}{Environment.NewLine}{highlightColumnLine}" @@ -59,5 +59,4 @@ type MSBuildOutput () = <| warning.RuleIdentifier <| warning.Details.Message member _.WriteError (error:string) = - $"FSharpLint error: {error}" - |> Console.Error.WriteLine \ No newline at end of file + Console.Error.WriteLine $"FSharpLint error: {error}" diff --git a/src/FSharpLint.Console/Program.fs b/src/FSharpLint.Console/Program.fs index 86439c632..0049ff76a 100644 --- a/src/FSharpLint.Console/Program.fs +++ b/src/FSharpLint.Console/Program.fs @@ -56,8 +56,8 @@ let private parserProgress (output:Output.IOutput) = function String.Format(Resources.GetString("ConsoleFinishedFile"), List.length warnings) |> output.WriteInfo | Failed (file, parseException) -> String.Format(Resources.GetString("ConsoleFailedToParseFile"), file) |> output.WriteError - $"Exception Message:{Environment.NewLine}{parseException.Message}{Environment.NewLine}Exception Stack Trace:{Environment.NewLine}{parseException.StackTrace}{Environment.NewLine}" - |> output.WriteError + output.WriteError + $"Exception Message:{Environment.NewLine}{parseException.Message}{Environment.NewLine}Exception Stack Trace:{Environment.NewLine}{parseException.StackTrace}{Environment.NewLine}" /// Infers the file type of the target based on its file extension. let internal inferFileType (target:string) = @@ -84,7 +84,7 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo. let version = Assembly.GetExecutingAssembly().GetCustomAttributes false |> Seq.pick (function | :? AssemblyInformationalVersionAttribute as aiva -> Some aiva.InformationalVersion | _ -> None) - $"Current version: {version}" |> output.WriteInfo + output.WriteInfo $"Current version: {version}" Environment.Exit 0 let handleError (str:string) = @@ -129,10 +129,10 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo. | _ -> Lint.lintProject lintParams target toolsPath handleLintResult lintResult with - | e -> + | exn -> let target = if fileType = FileType.Source then "source" else target - $"Lint failed while analysing %s{target}.{Environment.NewLine}Failed with: %s{e.Message}{Environment.NewLine}Stack trace: {e.StackTrace}" - |> handleError + handleError + $"Lint failed while analysing %s{target}.{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 de6621336..9537b8318 100644 --- a/src/FSharpLint.Core/Application/Configuration.fs +++ b/src/FSharpLint.Core/Application/Configuration.fs @@ -61,9 +61,11 @@ module IgnoreFiles = let private pathMatchesGlob (globs:Regex list) (path:string list) isDirectory = let rec getRemainingGlobSeqForMatches (pathSegment:string) (globSeqs:Regex list list) = - globSeqs |> List.choose (function - | globSegment::remaining when globSegment.IsMatch(pathSegment) -> Some remaining - | _ -> None) + List.choose + (function + | (globSegment: Regex)::remaining when globSegment.IsMatch(pathSegment) -> Some remaining + | _ -> None) + globSeqs let rec doesGlobSeqMatchPathSeq remainingPath currentlyMatchingGlobs = match remainingPath with @@ -73,7 +75,7 @@ module IgnoreFiles = let currentlyMatchingGlobs = getRemainingGlobSeqForMatches currentSegment currentlyMatchingGlobs - let aGlobWasCompletelyMatched = currentlyMatchingGlobs |> List.exists List.isEmpty + let aGlobWasCompletelyMatched = List.exists List.isEmpty currentlyMatchingGlobs let matched = aGlobWasCompletelyMatched && (isDirectory || (not isDirectory && List.isEmpty remaining)) @@ -81,18 +83,18 @@ module IgnoreFiles = else doesGlobSeqMatchPathSeq remaining currentlyMatchingGlobs | [] -> false - doesGlobSeqMatchPathSeq path [] + doesGlobSeqMatchPathSeq path List.Empty let shouldFileBeIgnored (ignorePaths:Ignore list) (filePath:string) = let segments = filePath.Split Path.DirectorySeparatorChar |> Array.toList - ignorePaths |> List.fold (fun isCurrentlyIgnored ignoreGlob -> + List.fold (fun isCurrentlyIgnored ignoreGlob -> match ignoreGlob with | Ignore(glob, IsDirectory(isDirectory)) when not isCurrentlyIgnored && pathMatchesGlob glob segments isDirectory -> true | Negate(glob, IsDirectory(isDirectory)) when isCurrentlyIgnored && pathMatchesGlob glob segments isDirectory -> false - | _ -> isCurrentlyIgnored) false + | _ -> isCurrentlyIgnored) false ignorePaths // Non-standard record field naming for config serialization. // fsharplint:disable RecordFieldNames @@ -107,13 +109,13 @@ let constructRuleIfEnabled rule ruleConfig = if ruleConfig.Enabled then Some rul let constructRuleWithConfig rule ruleConfig = if ruleConfig.Enabled then - ruleConfig.Config |> Option.map rule + Option.map rule ruleConfig.Config else None let constructTypePrefixingRuleWithConfig rule (ruleConfig: RuleConfig) = if ruleConfig.Enabled then - let config = ruleConfig.Config |> Option.defaultValue { Mode = TypePrefixing.Mode.Hybrid } + let config = Option.defaultValue { Mode = TypePrefixing.Mode.Hybrid } ruleConfig.Config Some(rule config) else None @@ -124,11 +126,12 @@ type TupleFormattingConfig = tupleParentheses:EnabledConfig option } with member this.Flatten() = - [| - this.tupleCommaSpacing |> Option.bind (constructRuleIfEnabled TupleCommaSpacing.rule) - this.tupleIndentation |> Option.bind (constructRuleIfEnabled TupleIndentation.rule) - this.tupleParentheses |> Option.bind (constructRuleIfEnabled TupleParentheses.rule) - |] |> Array.choose id + Array.choose id + [| + Option.bind (constructRuleIfEnabled TupleCommaSpacing.rule) this.tupleCommaSpacing + Option.bind (constructRuleIfEnabled TupleIndentation.rule) this.tupleIndentation + Option.bind (constructRuleIfEnabled TupleParentheses.rule) this.tupleParentheses + |] type PatternMatchFormattingConfig = { patternMatchClausesOnNewLine:EnabledConfig option @@ -137,12 +140,13 @@ type PatternMatchFormattingConfig = patternMatchExpressionIndentation:EnabledConfig option } with member this.Flatten() = - [| - this.patternMatchClausesOnNewLine |> Option.bind (constructRuleIfEnabled PatternMatchClausesOnNewLine.rule) - this.patternMatchOrClausesOnNewLine |> Option.bind (constructRuleIfEnabled PatternMatchOrClausesOnNewLine.rule) - this.patternMatchClauseIndentation |> Option.bind (constructRuleWithConfig PatternMatchClauseIndentation.rule) - this.patternMatchExpressionIndentation |> Option.bind (constructRuleIfEnabled PatternMatchExpressionIndentation.rule) - |] |> Array.choose id + Array.choose id + [| + Option.bind (constructRuleIfEnabled PatternMatchClausesOnNewLine.rule) this.patternMatchClausesOnNewLine + Option.bind (constructRuleIfEnabled PatternMatchOrClausesOnNewLine.rule) this.patternMatchOrClausesOnNewLine + Option.bind (constructRuleWithConfig PatternMatchClauseIndentation.rule) this.patternMatchClauseIndentation + Option.bind (constructRuleIfEnabled PatternMatchExpressionIndentation.rule) this.patternMatchExpressionIndentation + |] type FormattingConfig = { typedItemSpacing:RuleConfig option @@ -154,15 +158,16 @@ type FormattingConfig = patternMatchFormatting:PatternMatchFormattingConfig option } with member this.Flatten() = - [| - this.typedItemSpacing |> Option.bind (constructRuleWithConfig TypedItemSpacing.rule) |> Option.toArray - this.typePrefixing |> Option.bind (constructTypePrefixingRuleWithConfig TypePrefixing.rule) |> Option.toArray - this.unionDefinitionIndentation |> Option.bind (constructRuleIfEnabled UnionDefinitionIndentation.rule) |> Option.toArray - this.moduleDeclSpacing |> Option.bind (constructRuleIfEnabled ModuleDeclSpacing.rule) |> Option.toArray - this.classMemberSpacing |> Option.bind (constructRuleIfEnabled ClassMemberSpacing.rule) |> Option.toArray - this.tupleFormatting |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat - this.patternMatchFormatting |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat - |] |> Array.concat + Array.concat + [| + this.typedItemSpacing |> Option.bind (constructRuleWithConfig TypedItemSpacing.rule) |> Option.toArray + this.typePrefixing |> Option.bind (constructTypePrefixingRuleWithConfig TypePrefixing.rule) |> Option.toArray + this.unionDefinitionIndentation |> Option.bind (constructRuleIfEnabled UnionDefinitionIndentation.rule) |> Option.toArray + this.moduleDeclSpacing |> Option.bind (constructRuleIfEnabled ModuleDeclSpacing.rule) |> Option.toArray + this.classMemberSpacing |> Option.bind (constructRuleIfEnabled ClassMemberSpacing.rule) |> Option.toArray + this.tupleFormatting |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat + this.patternMatchFormatting |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat + |] type RaiseWithTooManyArgsConfig = { failwithBadUsage:EnabledConfig option @@ -173,14 +178,15 @@ type RaiseWithTooManyArgsConfig = failwithfWithArgumentsMatchingFormatString:EnabledConfig option } with member this.Flatten() = - [| - this.failwithBadUsage |> Option.bind (constructRuleIfEnabled FailwithBadUsage.rule) |> Option.toArray - this.raiseWithSingleArgument |> Option.bind (constructRuleIfEnabled RaiseWithSingleArgument.rule) |> Option.toArray - this.nullArgWithSingleArgument |> Option.bind (constructRuleIfEnabled NullArgWithSingleArgument.rule) |> Option.toArray - this.invalidOpWithSingleArgument |> Option.bind (constructRuleIfEnabled InvalidOpWithSingleArgument.rule) |> Option.toArray - this.invalidArgWithTwoArguments |> Option.bind (constructRuleIfEnabled InvalidArgWithTwoArguments.rule) |> Option.toArray - this.failwithfWithArgumentsMatchingFormatString |> Option.bind (constructRuleIfEnabled FailwithfWithArgumentsMatchingFormatString.rule) |> Option.toArray - |] |> Array.concat + Array.concat + [| + this.failwithBadUsage |> Option.bind (constructRuleIfEnabled FailwithBadUsage.rule) |> Option.toArray + this.raiseWithSingleArgument |> Option.bind (constructRuleIfEnabled RaiseWithSingleArgument.rule) |> Option.toArray + this.nullArgWithSingleArgument |> Option.bind (constructRuleIfEnabled NullArgWithSingleArgument.rule) |> Option.toArray + this.invalidOpWithSingleArgument |> Option.bind (constructRuleIfEnabled InvalidOpWithSingleArgument.rule) |> Option.toArray + this.invalidArgWithTwoArguments |> Option.bind (constructRuleIfEnabled InvalidArgWithTwoArguments.rule) |> Option.toArray + this.failwithfWithArgumentsMatchingFormatString |> Option.bind (constructRuleIfEnabled FailwithfWithArgumentsMatchingFormatString.rule) |> Option.toArray + |] type SourceLengthConfig = { maxLinesInLambdaFunction:RuleConfig option @@ -197,20 +203,21 @@ type SourceLengthConfig = maxLinesInClass:RuleConfig option } with member this.Flatten() = - [| - this.maxLinesInLambdaFunction |> Option.bind (constructRuleWithConfig MaxLinesInLambdaFunction.rule) |> Option.toArray - this.maxLinesInMatchLambdaFunction |> Option.bind (constructRuleWithConfig MaxLinesInMatchLambdaFunction.rule) |> Option.toArray - this.maxLinesInValue |> Option.bind (constructRuleWithConfig MaxLinesInValue.rule) |> Option.toArray - this.maxLinesInFunction |> Option.bind (constructRuleWithConfig MaxLinesInFunction.rule) |> Option.toArray - this.maxLinesInMember |> Option.bind (constructRuleWithConfig MaxLinesInMember.rule) |> Option.toArray - this.maxLinesInConstructor |> Option.bind (constructRuleWithConfig MaxLinesInConstructor.rule) |> Option.toArray - this.maxLinesInProperty |> Option.bind (constructRuleWithConfig MaxLinesInProperty.rule) |> Option.toArray - this.maxLinesInModule |> Option.bind (constructRuleWithConfig MaxLinesInModule.rule) |> Option.toArray - this.maxLinesInRecord |> Option.bind (constructRuleWithConfig MaxLinesInRecord.rule) |> Option.toArray - this.maxLinesInEnum |> Option.bind (constructRuleWithConfig MaxLinesInEnum.rule) |> Option.toArray - this.maxLinesInUnion |> Option.bind (constructRuleWithConfig MaxLinesInUnion.rule) |> Option.toArray - this.maxLinesInClass |> Option.bind (constructRuleWithConfig MaxLinesInClass.rule) |> Option.toArray - |] |> Array.concat + Array.concat + [| + this.maxLinesInLambdaFunction |> Option.bind (constructRuleWithConfig MaxLinesInLambdaFunction.rule) |> Option.toArray + this.maxLinesInMatchLambdaFunction |> Option.bind (constructRuleWithConfig MaxLinesInMatchLambdaFunction.rule) |> Option.toArray + this.maxLinesInValue |> Option.bind (constructRuleWithConfig MaxLinesInValue.rule) |> Option.toArray + this.maxLinesInFunction |> Option.bind (constructRuleWithConfig MaxLinesInFunction.rule) |> Option.toArray + this.maxLinesInMember |> Option.bind (constructRuleWithConfig MaxLinesInMember.rule) |> Option.toArray + this.maxLinesInConstructor |> Option.bind (constructRuleWithConfig MaxLinesInConstructor.rule) |> Option.toArray + this.maxLinesInProperty |> Option.bind (constructRuleWithConfig MaxLinesInProperty.rule) |> Option.toArray + this.maxLinesInModule |> Option.bind (constructRuleWithConfig MaxLinesInModule.rule) |> Option.toArray + this.maxLinesInRecord |> Option.bind (constructRuleWithConfig MaxLinesInRecord.rule) |> Option.toArray + this.maxLinesInEnum |> Option.bind (constructRuleWithConfig MaxLinesInEnum.rule) |> Option.toArray + this.maxLinesInUnion |> Option.bind (constructRuleWithConfig MaxLinesInUnion.rule) |> Option.toArray + this.maxLinesInClass |> Option.bind (constructRuleWithConfig MaxLinesInClass.rule) |> Option.toArray + |] type NamesConfig = { interfaceNames:RuleConfig option @@ -233,27 +240,28 @@ type NamesConfig = internalValuesNames:RuleConfig option } with member this.Flatten() = - [| - this.interfaceNames |> Option.bind (constructRuleWithConfig InterfaceNames.rule) |> Option.toArray - this.genericTypesNames |> Option.bind (constructRuleWithConfig GenericTypesNames.rule) |> Option.toArray - this.exceptionNames |> Option.bind (constructRuleWithConfig ExceptionNames.rule) |> Option.toArray - this.typeNames |> Option.bind (constructRuleWithConfig TypeNames.rule) |> Option.toArray - this.recordFieldNames |> Option.bind (constructRuleWithConfig RecordFieldNames.rule) |> Option.toArray - this.enumCasesNames |> Option.bind (constructRuleWithConfig EnumCasesNames.rule) |> Option.toArray - this.unionCasesNames |> Option.bind (constructRuleWithConfig UnionCasesNames.rule) |> Option.toArray - this.moduleNames |> Option.bind (constructRuleWithConfig ModuleNames.rule) |> Option.toArray - this.literalNames |> Option.bind (constructRuleWithConfig LiteralNames.rule) |> Option.toArray - this.namespaceNames |> Option.bind (constructRuleWithConfig NamespaceNames.rule) |> Option.toArray - this.memberNames |> Option.bind (constructRuleWithConfig MemberNames.rule) |> Option.toArray - this.parameterNames |> Option.bind (constructRuleWithConfig ParameterNames.rule) |> Option.toArray - this.measureTypeNames |> Option.bind (constructRuleWithConfig MeasureTypeNames.rule) |> Option.toArray - this.activePatternNames |> Option.bind (constructRuleWithConfig ActivePatternNames.rule) |> Option.toArray - this.publicValuesNames |> Option.bind (constructRuleWithConfig PublicValuesNames.rule) |> Option.toArray - this.nonPublicValuesNames |> Option.bind (constructRuleWithConfig PrivateValuesNames.rule) |> Option.toArray - this.nonPublicValuesNames |> Option.bind (constructRuleWithConfig InternalValuesNames.rule) |> Option.toArray - this.privateValuesNames |> Option.bind (constructRuleWithConfig PrivateValuesNames.rule) |> Option.toArray - this.internalValuesNames|> Option.bind (constructRuleWithConfig InternalValuesNames.rule) |> Option.toArray - |] |> Array.concat + Array.concat + [| + this.interfaceNames |> Option.bind (constructRuleWithConfig InterfaceNames.rule) |> Option.toArray + this.genericTypesNames |> Option.bind (constructRuleWithConfig GenericTypesNames.rule) |> Option.toArray + this.exceptionNames |> Option.bind (constructRuleWithConfig ExceptionNames.rule) |> Option.toArray + this.typeNames |> Option.bind (constructRuleWithConfig TypeNames.rule) |> Option.toArray + this.recordFieldNames |> Option.bind (constructRuleWithConfig RecordFieldNames.rule) |> Option.toArray + this.enumCasesNames |> Option.bind (constructRuleWithConfig EnumCasesNames.rule) |> Option.toArray + this.unionCasesNames |> Option.bind (constructRuleWithConfig UnionCasesNames.rule) |> Option.toArray + this.moduleNames |> Option.bind (constructRuleWithConfig ModuleNames.rule) |> Option.toArray + this.literalNames |> Option.bind (constructRuleWithConfig LiteralNames.rule) |> Option.toArray + this.namespaceNames |> Option.bind (constructRuleWithConfig NamespaceNames.rule) |> Option.toArray + this.memberNames |> Option.bind (constructRuleWithConfig MemberNames.rule) |> Option.toArray + this.parameterNames |> Option.bind (constructRuleWithConfig ParameterNames.rule) |> Option.toArray + this.measureTypeNames |> Option.bind (constructRuleWithConfig MeasureTypeNames.rule) |> Option.toArray + this.activePatternNames |> Option.bind (constructRuleWithConfig ActivePatternNames.rule) |> Option.toArray + this.publicValuesNames |> Option.bind (constructRuleWithConfig PublicValuesNames.rule) |> Option.toArray + this.nonPublicValuesNames |> Option.bind (constructRuleWithConfig PrivateValuesNames.rule) |> Option.toArray + this.nonPublicValuesNames |> Option.bind (constructRuleWithConfig InternalValuesNames.rule) |> Option.toArray + this.privateValuesNames |> Option.bind (constructRuleWithConfig PrivateValuesNames.rule) |> Option.toArray + this.internalValuesNames|> Option.bind (constructRuleWithConfig InternalValuesNames.rule) |> Option.toArray + |] type NumberOfItemsConfig = { maxNumberOfItemsInTuple:RuleConfig option @@ -262,12 +270,13 @@ type NumberOfItemsConfig = maxNumberOfBooleanOperatorsInCondition:RuleConfig option } with member this.Flatten() = - [| - this.maxNumberOfItemsInTuple |> Option.bind (constructRuleWithConfig MaxNumberOfItemsInTuple.rule) |> Option.toArray - this.maxNumberOfFunctionParameters |> Option.bind (constructRuleWithConfig MaxNumberOfFunctionParameters.rule) |> Option.toArray - this.maxNumberOfMembers |> Option.bind (constructRuleWithConfig MaxNumberOfMembers.rule) |> Option.toArray - this.maxNumberOfBooleanOperatorsInCondition |> Option.bind (constructRuleWithConfig MaxNumberOfBooleanOperatorsInCondition.rule) |> Option.toArray - |] |> Array.concat + Array.concat + [| + this.maxNumberOfItemsInTuple |> Option.bind (constructRuleWithConfig MaxNumberOfItemsInTuple.rule) |> Option.toArray + this.maxNumberOfFunctionParameters |> Option.bind (constructRuleWithConfig MaxNumberOfFunctionParameters.rule) |> Option.toArray + this.maxNumberOfMembers |> Option.bind (constructRuleWithConfig MaxNumberOfMembers.rule) |> Option.toArray + this.maxNumberOfBooleanOperatorsInCondition |> Option.bind (constructRuleWithConfig MaxNumberOfBooleanOperatorsInCondition.rule) |> Option.toArray + |] type BindingConfig = { favourIgnoreOverLetWild:EnabledConfig option @@ -278,14 +287,15 @@ type BindingConfig = favourTypedIgnore:EnabledConfig option } with member this.Flatten() = - [| - this.favourIgnoreOverLetWild |> Option.bind (constructRuleIfEnabled FavourIgnoreOverLetWild.rule) |> Option.toArray - this.favourTypedIgnore |> Option.bind (constructRuleIfEnabled FavourTypedIgnore.rule) |> Option.toArray - this.wildcardNamedWithAsPattern |> Option.bind (constructRuleIfEnabled WildcardNamedWithAsPattern.rule) |> Option.toArray - this.uselessBinding |> Option.bind (constructRuleIfEnabled UselessBinding.rule) |> Option.toArray - this.tupleOfWildcards |> Option.bind (constructRuleIfEnabled TupleOfWildcards.rule) |> Option.toArray - this.favourAsKeyword |> Option.bind (constructRuleIfEnabled FavourAsKeyword.rule) |> Option.toArray - |] |> Array.concat + Array.concat + [| + this.favourIgnoreOverLetWild |> Option.bind (constructRuleIfEnabled FavourIgnoreOverLetWild.rule) |> Option.toArray + this.favourTypedIgnore |> Option.bind (constructRuleIfEnabled FavourTypedIgnore.rule) |> Option.toArray + this.wildcardNamedWithAsPattern |> Option.bind (constructRuleIfEnabled WildcardNamedWithAsPattern.rule) |> Option.toArray + this.uselessBinding |> Option.bind (constructRuleIfEnabled UselessBinding.rule) |> Option.toArray + this.tupleOfWildcards |> Option.bind (constructRuleIfEnabled TupleOfWildcards.rule) |> Option.toArray + this.favourAsKeyword |> Option.bind (constructRuleIfEnabled FavourAsKeyword.rule) |> Option.toArray + |] type ConventionsConfig = { recursiveAsyncFunction:EnabledConfig option @@ -312,30 +322,31 @@ type ConventionsConfig = ensureTailCallDiagnosticsInRecursiveFunctions:EnabledConfig option} with member this.Flatten() = - [| - this.recursiveAsyncFunction |> Option.bind (constructRuleIfEnabled RecursiveAsyncFunction.rule) |> Option.toArray - this.avoidTooShortNames |> Option.bind (constructRuleIfEnabled AvoidTooShortNames.rule) |> Option.toArray - this.redundantNewKeyword |> Option.bind (constructRuleIfEnabled RedundantNewKeyword.rule) |> Option.toArray - this.favourNonMutablePropertyInitialization |> Option.bind (constructRuleIfEnabled FavourNonMutablePropertyInitialization.rule) |> Option.toArray - this.favourReRaise |> Option.bind (constructRuleIfEnabled FavourReRaise.rule) |> Option.toArray - this.favourStaticEmptyFields |> Option.bind (constructRuleIfEnabled FavourStaticEmptyFields.rule) |> Option.toArray - this.asyncExceptionWithoutReturn |> Option.bind (constructRuleIfEnabled AsyncExceptionWithoutReturn.rule) |> Option.toArray - this.unneededRecKeyword |> Option.bind (constructRuleIfEnabled UnneededRecKeyword.rule) |> Option.toArray - this.nestedStatements |> Option.bind (constructRuleWithConfig NestedStatements.rule) |> Option.toArray - this.favourConsistentThis |> Option.bind (constructRuleWithConfig FavourConsistentThis.rule) |> Option.toArray - this.cyclomaticComplexity |> Option.bind (constructRuleWithConfig CyclomaticComplexity.rule) |> Option.toArray - this.reimplementsFunction |> Option.bind (constructRuleIfEnabled ReimplementsFunction.rule) |> Option.toArray - this.canBeReplacedWithComposition |> Option.bind (constructRuleIfEnabled CanBeReplacedWithComposition.rule) |> Option.toArray - this.avoidSinglePipeOperator|> Option.bind (constructRuleIfEnabled AvoidSinglePipeOperator.rule) |> Option.toArray - this.usedUnderscorePrefixedElements |> Option.bind (constructRuleIfEnabled UsedUnderscorePrefixedElements.rule) |> Option.toArray - this.raiseWithTooManyArgs |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat - this.sourceLength |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat - this.naming |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat - this.numberOfItems |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat - this.binding |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat - this.suggestUseAutoProperty |> Option.bind (constructRuleIfEnabled SuggestUseAutoProperty.rule) |> Option.toArray - this.ensureTailCallDiagnosticsInRecursiveFunctions |> Option.bind (constructRuleIfEnabled EnsureTailCallDiagnosticsInRecursiveFunctions.rule) |> Option.toArray - |] |> Array.concat + Array.concat + [| + this.recursiveAsyncFunction |> Option.bind (constructRuleIfEnabled RecursiveAsyncFunction.rule) |> Option.toArray + this.avoidTooShortNames |> Option.bind (constructRuleIfEnabled AvoidTooShortNames.rule) |> Option.toArray + this.redundantNewKeyword |> Option.bind (constructRuleIfEnabled RedundantNewKeyword.rule) |> Option.toArray + this.favourNonMutablePropertyInitialization |> Option.bind (constructRuleIfEnabled FavourNonMutablePropertyInitialization.rule) |> Option.toArray + this.favourReRaise |> Option.bind (constructRuleIfEnabled FavourReRaise.rule) |> Option.toArray + this.favourStaticEmptyFields |> Option.bind (constructRuleIfEnabled FavourStaticEmptyFields.rule) |> Option.toArray + this.asyncExceptionWithoutReturn |> Option.bind (constructRuleIfEnabled AsyncExceptionWithoutReturn.rule) |> Option.toArray + this.unneededRecKeyword |> Option.bind (constructRuleIfEnabled UnneededRecKeyword.rule) |> Option.toArray + this.nestedStatements |> Option.bind (constructRuleWithConfig NestedStatements.rule) |> Option.toArray + this.favourConsistentThis |> Option.bind (constructRuleWithConfig FavourConsistentThis.rule) |> Option.toArray + this.cyclomaticComplexity |> Option.bind (constructRuleWithConfig CyclomaticComplexity.rule) |> Option.toArray + this.reimplementsFunction |> Option.bind (constructRuleIfEnabled ReimplementsFunction.rule) |> Option.toArray + this.canBeReplacedWithComposition |> Option.bind (constructRuleIfEnabled CanBeReplacedWithComposition.rule) |> Option.toArray + this.avoidSinglePipeOperator|> Option.bind (constructRuleIfEnabled AvoidSinglePipeOperator.rule) |> Option.toArray + this.usedUnderscorePrefixedElements |> Option.bind (constructRuleIfEnabled UsedUnderscorePrefixedElements.rule) |> Option.toArray + this.raiseWithTooManyArgs |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat + this.sourceLength |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat + this.naming |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat + this.numberOfItems |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat + this.binding |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat + this.suggestUseAutoProperty |> Option.bind (constructRuleIfEnabled SuggestUseAutoProperty.rule) |> Option.toArray + this.ensureTailCallDiagnosticsInRecursiveFunctions |> Option.bind (constructRuleIfEnabled EnsureTailCallDiagnosticsInRecursiveFunctions.rule) |> Option.toArray + |] type TypographyConfig = { indentation:EnabledConfig option @@ -346,16 +357,17 @@ type TypographyConfig = noTabCharacters:EnabledConfig option } with member this.Flatten() = - [| - this.indentation |> Option.bind (constructRuleIfEnabled Indentation.rule) |> Option.toArray - this.maxCharactersOnLine |> Option.bind (constructRuleWithConfig MaxCharactersOnLine.rule) |> Option.toArray - this.trailingWhitespaceOnLine |> Option.bind (constructRuleWithConfig TrailingWhitespaceOnLine.rule) |> Option.toArray - this.maxLinesInFile |> Option.bind (constructRuleWithConfig MaxLinesInFile.rule) |> Option.toArray - this.trailingNewLineInFile |> Option.bind (constructRuleIfEnabled TrailingNewLineInFile.rule) |> Option.toArray - this.noTabCharacters |> Option.bind (constructRuleIfEnabled NoTabCharacters.rule) |> Option.toArray - |] |> Array.concat - -let private getOrEmptyList hints = hints |> Option.defaultValue [||] + Array.concat + [| + this.indentation |> Option.bind (constructRuleIfEnabled Indentation.rule) |> Option.toArray + this.maxCharactersOnLine |> Option.bind (constructRuleWithConfig MaxCharactersOnLine.rule) |> Option.toArray + this.trailingWhitespaceOnLine |> Option.bind (constructRuleWithConfig TrailingWhitespaceOnLine.rule) |> Option.toArray + this.maxLinesInFile |> Option.bind (constructRuleWithConfig MaxLinesInFile.rule) |> Option.toArray + this.trailingNewLineInFile |> Option.bind (constructRuleIfEnabled TrailingNewLineInFile.rule) |> Option.toArray + this.noTabCharacters |> Option.bind (constructRuleIfEnabled NoTabCharacters.rule) |> Option.toArray + |] + +let private getOrEmptyList hints = Option.defaultValue Array.empty hints type HintConfig = { add:string [] option @@ -577,7 +589,7 @@ let loadConfig (configPath:string) = let defaultConfiguration = let assembly = typeof.GetTypeInfo().Assembly let resourceName = Assembly.GetExecutingAssembly().GetManifestResourceNames() - |> Seq.find (fun n -> n.EndsWith("fsharplint.json", System.StringComparison.Ordinal)) + |> Seq.find (fun resourceFile -> resourceFile.EndsWith("fsharplint.json", System.StringComparison.Ordinal)) use stream = assembly.GetManifestResourceStream(resourceName) match stream with | null -> failwithf "Resource '%s' not found in assembly '%s'" resourceName (assembly.FullName) @@ -617,105 +629,7 @@ let private parseHints (hints:string []) = |> Array.toList |> MergeSyntaxTrees.mergeHints -let flattenConfig (config:Configuration) = - let deprecatedAllRules = - [| - config.formatting |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat - config.conventions |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat - config.typography |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat - config.Hints |> Option.map (fun config -> HintMatcher.rule { HintMatcher.Config.HintTrie = parseHints (getOrEmptyList config.add) }) |> Option.toArray - |] |> Array.concat - - let allRules = - [| - config.TypedItemSpacing |> Option.bind (constructRuleWithConfig TypedItemSpacing.rule) - config.TypePrefixing |> Option.bind (constructTypePrefixingRuleWithConfig TypePrefixing.rule) - config.UnionDefinitionIndentation |> Option.bind (constructRuleIfEnabled UnionDefinitionIndentation.rule) - config.ModuleDeclSpacing |> Option.bind (constructRuleIfEnabled ModuleDeclSpacing.rule) - config.ClassMemberSpacing |> Option.bind (constructRuleIfEnabled ClassMemberSpacing.rule) - config.TupleCommaSpacing |> Option.bind (constructRuleIfEnabled TupleCommaSpacing.rule) - config.TupleIndentation |> Option.bind (constructRuleIfEnabled TupleIndentation.rule) - config.TupleParentheses |> Option.bind (constructRuleIfEnabled TupleParentheses.rule) - config.PatternMatchClausesOnNewLine |> Option.bind (constructRuleIfEnabled PatternMatchClausesOnNewLine.rule) - config.PatternMatchOrClausesOnNewLine |> Option.bind (constructRuleIfEnabled PatternMatchOrClausesOnNewLine.rule) - config.PatternMatchClauseIndentation |> Option.bind (constructRuleWithConfig PatternMatchClauseIndentation.rule) - config.PatternMatchExpressionIndentation |> Option.bind (constructRuleIfEnabled PatternMatchExpressionIndentation.rule) - config.RecursiveAsyncFunction |> Option.bind (constructRuleIfEnabled RecursiveAsyncFunction.rule) - config.AvoidTooShortNames |> Option.bind (constructRuleIfEnabled AvoidTooShortNames.rule) - config.RedundantNewKeyword |> Option.bind (constructRuleIfEnabled RedundantNewKeyword.rule) - config.FavourNonMutablePropertyInitialization |> Option.bind (constructRuleIfEnabled FavourNonMutablePropertyInitialization.rule) - config.FavourReRaise |> Option.bind (constructRuleIfEnabled FavourReRaise.rule) - config.FavourStaticEmptyFields |> Option.bind (constructRuleIfEnabled FavourStaticEmptyFields.rule) - config.AsyncExceptionWithoutReturn |> Option.bind (constructRuleIfEnabled AsyncExceptionWithoutReturn.rule) - config.UnneededRecKeyword |> Option.bind (constructRuleIfEnabled UnneededRecKeyword.rule) - config.NestedStatements |> Option.bind (constructRuleWithConfig NestedStatements.rule) - config.FavourConsistentThis |> Option.bind (constructRuleWithConfig FavourConsistentThis.rule) - config.CyclomaticComplexity |> Option.bind (constructRuleWithConfig CyclomaticComplexity.rule) - config.ReimplementsFunction |> Option.bind (constructRuleIfEnabled ReimplementsFunction.rule) - config.CanBeReplacedWithComposition |> Option.bind (constructRuleIfEnabled CanBeReplacedWithComposition.rule) - config.AvoidSinglePipeOperator |> Option.bind (constructRuleIfEnabled AvoidSinglePipeOperator.rule) - config.UsedUnderscorePrefixedElements |> Option.bind (constructRuleIfEnabled UsedUnderscorePrefixedElements.rule) - config.FailwithBadUsage |> Option.bind (constructRuleIfEnabled FailwithBadUsage.rule) - config.RaiseWithSingleArgument |> Option.bind (constructRuleIfEnabled RaiseWithSingleArgument.rule) - config.FailwithWithSingleArgument |> Option.bind (constructRuleIfEnabled FailwithWithSingleArgument.rule) - config.NullArgWithSingleArgument |> Option.bind (constructRuleIfEnabled NullArgWithSingleArgument.rule) - config.InvalidOpWithSingleArgument |> Option.bind (constructRuleIfEnabled InvalidOpWithSingleArgument.rule) - config.InvalidArgWithTwoArguments |> Option.bind (constructRuleIfEnabled InvalidArgWithTwoArguments.rule) - config.FailwithfWithArgumentsMatchingFormatString |> Option.bind (constructRuleIfEnabled FailwithfWithArgumentsMatchingFormatString.rule) - config.MaxLinesInLambdaFunction |> Option.bind (constructRuleWithConfig MaxLinesInLambdaFunction.rule) - config.MaxLinesInMatchLambdaFunction |> Option.bind (constructRuleWithConfig MaxLinesInMatchLambdaFunction.rule) - config.MaxLinesInValue |> Option.bind (constructRuleWithConfig MaxLinesInValue.rule) - config.MaxLinesInFunction |> Option.bind (constructRuleWithConfig MaxLinesInFunction.rule) - config.MaxLinesInMember |> Option.bind (constructRuleWithConfig MaxLinesInMember.rule) - config.MaxLinesInConstructor |> Option.bind (constructRuleWithConfig MaxLinesInConstructor.rule) - config.MaxLinesInProperty |> Option.bind (constructRuleWithConfig MaxLinesInProperty.rule) - config.MaxLinesInModule |> Option.bind (constructRuleWithConfig MaxLinesInModule.rule) - config.MaxLinesInRecord |> Option.bind (constructRuleWithConfig MaxLinesInRecord.rule) - config.MaxLinesInEnum |> Option.bind (constructRuleWithConfig MaxLinesInEnum.rule) - config.MaxLinesInUnion |> Option.bind (constructRuleWithConfig MaxLinesInUnion.rule) - config.MaxLinesInClass |> Option.bind (constructRuleWithConfig MaxLinesInClass.rule) - config.InterfaceNames |> Option.bind (constructRuleWithConfig InterfaceNames.rule) - config.GenericTypesNames |> Option.bind (constructRuleWithConfig GenericTypesNames.rule) - config.ExceptionNames |> Option.bind (constructRuleWithConfig ExceptionNames.rule) - config.TypeNames |> Option.bind (constructRuleWithConfig TypeNames.rule) - config.RecordFieldNames |> Option.bind (constructRuleWithConfig RecordFieldNames.rule) - config.EnumCasesNames |> Option.bind (constructRuleWithConfig EnumCasesNames.rule) - config.UnionCasesNames |> Option.bind (constructRuleWithConfig UnionCasesNames.rule) - config.ModuleNames |> Option.bind (constructRuleWithConfig ModuleNames.rule) - config.LiteralNames |> Option.bind (constructRuleWithConfig LiteralNames.rule) - config.NamespaceNames |> Option.bind (constructRuleWithConfig NamespaceNames.rule) - config.MemberNames |> Option.bind (constructRuleWithConfig MemberNames.rule) - config.ParameterNames |> Option.bind (constructRuleWithConfig ParameterNames.rule) - config.MeasureTypeNames |> Option.bind (constructRuleWithConfig MeasureTypeNames.rule) - config.ActivePatternNames |> Option.bind (constructRuleWithConfig ActivePatternNames.rule) - config.PublicValuesNames |> Option.bind (constructRuleWithConfig PublicValuesNames.rule) - config.NonPublicValuesNames |> Option.bind (constructRuleWithConfig PrivateValuesNames.rule) - config.NonPublicValuesNames |> Option.bind (constructRuleWithConfig InternalValuesNames.rule) - config.PrivateValuesNames |> Option.bind (constructRuleWithConfig PrivateValuesNames.rule) - config.InternalValuesNames |> Option.bind (constructRuleWithConfig InternalValuesNames.rule) - config.UnnestedFunctionNames |> Option.bind (constructRuleWithConfig UnnestedFunctionNames.rule) - config.NestedFunctionNames |> Option.bind (constructRuleWithConfig NestedFunctionNames.rule) - config.MaxNumberOfItemsInTuple |> Option.bind (constructRuleWithConfig MaxNumberOfItemsInTuple.rule) - config.MaxNumberOfFunctionParameters |> Option.bind (constructRuleWithConfig MaxNumberOfFunctionParameters.rule) - config.MaxNumberOfMembers |> Option.bind (constructRuleWithConfig MaxNumberOfMembers.rule) - config.MaxNumberOfBooleanOperatorsInCondition |> Option.bind (constructRuleWithConfig MaxNumberOfBooleanOperatorsInCondition.rule) - config.FavourIgnoreOverLetWild |> Option.bind (constructRuleIfEnabled FavourIgnoreOverLetWild.rule) - config.FavourTypedIgnore |> Option.bind (constructRuleIfEnabled FavourTypedIgnore.rule) - config.WildcardNamedWithAsPattern |> Option.bind (constructRuleIfEnabled WildcardNamedWithAsPattern.rule) - config.UselessBinding |> Option.bind (constructRuleIfEnabled UselessBinding.rule) - config.TupleOfWildcards |> Option.bind (constructRuleIfEnabled TupleOfWildcards.rule) - config.Indentation |> Option.bind (constructRuleIfEnabled Indentation.rule) - config.MaxCharactersOnLine |> Option.bind (constructRuleWithConfig MaxCharactersOnLine.rule) - config.TrailingWhitespaceOnLine |> Option.bind (constructRuleWithConfig TrailingWhitespaceOnLine.rule) - config.MaxLinesInFile |> Option.bind (constructRuleWithConfig MaxLinesInFile.rule) - config.TrailingNewLineInFile |> Option.bind (constructRuleIfEnabled TrailingNewLineInFile.rule) - config.NoTabCharacters |> Option.bind (constructRuleIfEnabled NoTabCharacters.rule) - config.NoPartialFunctions |> Option.bind (constructRuleWithConfig NoPartialFunctions.rule) - config.SuggestUseAutoProperty |> Option.bind (constructRuleIfEnabled SuggestUseAutoProperty.rule) - config.EnsureTailCallDiagnosticsInRecursiveFunctions |> Option.bind (constructRuleIfEnabled EnsureTailCallDiagnosticsInRecursiveFunctions.rule) - config.FavourAsKeyword |> Option.bind (constructRuleIfEnabled FavourAsKeyword.rule) - |] |> Array.choose id - +let findDeprecation config deprecatedAllRules allRules = if config.NonPublicValuesNames.IsSome && (config.PrivateValuesNames.IsSome || config.InternalValuesNames.IsSome) then failwith "nonPublicValuesNames has been deprecated, use privateValuesNames and/or internalValuesNames instead" @@ -736,10 +650,119 @@ let flattenConfig (config:Configuration) = | IndentationRule rule -> indentationRule <- Some rule | NoTabCharactersRule rule -> noTabCharactersRule <- Some rule) - { LoadedRules.GlobalConfig = getGlobalConfig config.Global - DeprecatedRules = deprecatedAllRules - AstNodeRules = astNodeRules.ToArray() - LineRules = - { GenericLineRules = lineRules.ToArray() - IndentationRule = indentationRule - NoTabCharactersRule = noTabCharactersRule } } + { + LoadedRules.GlobalConfig = getGlobalConfig config.Global + DeprecatedRules = deprecatedAllRules + AstNodeRules = astNodeRules.ToArray() + LineRules = + { + GenericLineRules = lineRules.ToArray() + IndentationRule = indentationRule + NoTabCharactersRule = noTabCharactersRule + } + } + +// fsharplint:disable MaxLinesInFunction +let flattenConfig (config:Configuration) = + let deprecatedAllRules = + Array.concat + [| + config.formatting |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat + config.conventions |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat + config.typography |> Option.map (fun config -> config.Flatten()) |> Option.toArray |> Array.concat + config.Hints |> Option.map (fun config -> HintMatcher.rule { HintMatcher.Config.HintTrie = parseHints (getOrEmptyList config.add) }) |> Option.toArray + |] + + let allRules = + Array.choose + id + [| + config.TypedItemSpacing |> Option.bind (constructRuleWithConfig TypedItemSpacing.rule) + config.TypePrefixing |> Option.bind (constructTypePrefixingRuleWithConfig TypePrefixing.rule) + config.UnionDefinitionIndentation |> Option.bind (constructRuleIfEnabled UnionDefinitionIndentation.rule) + config.ModuleDeclSpacing |> Option.bind (constructRuleIfEnabled ModuleDeclSpacing.rule) + config.ClassMemberSpacing |> Option.bind (constructRuleIfEnabled ClassMemberSpacing.rule) + config.TupleCommaSpacing |> Option.bind (constructRuleIfEnabled TupleCommaSpacing.rule) + config.TupleIndentation |> Option.bind (constructRuleIfEnabled TupleIndentation.rule) + config.TupleParentheses |> Option.bind (constructRuleIfEnabled TupleParentheses.rule) + config.PatternMatchClausesOnNewLine |> Option.bind (constructRuleIfEnabled PatternMatchClausesOnNewLine.rule) + config.PatternMatchOrClausesOnNewLine |> Option.bind (constructRuleIfEnabled PatternMatchOrClausesOnNewLine.rule) + config.PatternMatchClauseIndentation |> Option.bind (constructRuleWithConfig PatternMatchClauseIndentation.rule) + config.PatternMatchExpressionIndentation |> Option.bind (constructRuleIfEnabled PatternMatchExpressionIndentation.rule) + config.RecursiveAsyncFunction |> Option.bind (constructRuleIfEnabled RecursiveAsyncFunction.rule) + config.AvoidTooShortNames |> Option.bind (constructRuleIfEnabled AvoidTooShortNames.rule) + config.RedundantNewKeyword |> Option.bind (constructRuleIfEnabled RedundantNewKeyword.rule) + config.FavourNonMutablePropertyInitialization |> Option.bind (constructRuleIfEnabled FavourNonMutablePropertyInitialization.rule) + config.FavourReRaise |> Option.bind (constructRuleIfEnabled FavourReRaise.rule) + config.FavourStaticEmptyFields |> Option.bind (constructRuleIfEnabled FavourStaticEmptyFields.rule) + config.AsyncExceptionWithoutReturn |> Option.bind (constructRuleIfEnabled AsyncExceptionWithoutReturn.rule) + config.UnneededRecKeyword |> Option.bind (constructRuleIfEnabled UnneededRecKeyword.rule) + config.NestedStatements |> Option.bind (constructRuleWithConfig NestedStatements.rule) + config.FavourConsistentThis |> Option.bind (constructRuleWithConfig FavourConsistentThis.rule) + config.CyclomaticComplexity |> Option.bind (constructRuleWithConfig CyclomaticComplexity.rule) + config.ReimplementsFunction |> Option.bind (constructRuleIfEnabled ReimplementsFunction.rule) + config.CanBeReplacedWithComposition |> Option.bind (constructRuleIfEnabled CanBeReplacedWithComposition.rule) + config.AvoidSinglePipeOperator |> Option.bind (constructRuleIfEnabled AvoidSinglePipeOperator.rule) + config.UsedUnderscorePrefixedElements |> Option.bind (constructRuleIfEnabled UsedUnderscorePrefixedElements.rule) + config.FailwithBadUsage |> Option.bind (constructRuleIfEnabled FailwithBadUsage.rule) + config.RaiseWithSingleArgument |> Option.bind (constructRuleIfEnabled RaiseWithSingleArgument.rule) + config.FailwithWithSingleArgument |> Option.bind (constructRuleIfEnabled FailwithWithSingleArgument.rule) + config.NullArgWithSingleArgument |> Option.bind (constructRuleIfEnabled NullArgWithSingleArgument.rule) + config.InvalidOpWithSingleArgument |> Option.bind (constructRuleIfEnabled InvalidOpWithSingleArgument.rule) + config.InvalidArgWithTwoArguments |> Option.bind (constructRuleIfEnabled InvalidArgWithTwoArguments.rule) + config.FailwithfWithArgumentsMatchingFormatString |> Option.bind (constructRuleIfEnabled FailwithfWithArgumentsMatchingFormatString.rule) + config.MaxLinesInLambdaFunction |> Option.bind (constructRuleWithConfig MaxLinesInLambdaFunction.rule) + config.MaxLinesInMatchLambdaFunction |> Option.bind (constructRuleWithConfig MaxLinesInMatchLambdaFunction.rule) + config.MaxLinesInValue |> Option.bind (constructRuleWithConfig MaxLinesInValue.rule) + config.MaxLinesInFunction |> Option.bind (constructRuleWithConfig MaxLinesInFunction.rule) + config.MaxLinesInMember |> Option.bind (constructRuleWithConfig MaxLinesInMember.rule) + config.MaxLinesInConstructor |> Option.bind (constructRuleWithConfig MaxLinesInConstructor.rule) + config.MaxLinesInProperty |> Option.bind (constructRuleWithConfig MaxLinesInProperty.rule) + config.MaxLinesInModule |> Option.bind (constructRuleWithConfig MaxLinesInModule.rule) + config.MaxLinesInRecord |> Option.bind (constructRuleWithConfig MaxLinesInRecord.rule) + config.MaxLinesInEnum |> Option.bind (constructRuleWithConfig MaxLinesInEnum.rule) + config.MaxLinesInUnion |> Option.bind (constructRuleWithConfig MaxLinesInUnion.rule) + config.MaxLinesInClass |> Option.bind (constructRuleWithConfig MaxLinesInClass.rule) + config.InterfaceNames |> Option.bind (constructRuleWithConfig InterfaceNames.rule) + config.GenericTypesNames |> Option.bind (constructRuleWithConfig GenericTypesNames.rule) + config.ExceptionNames |> Option.bind (constructRuleWithConfig ExceptionNames.rule) + config.TypeNames |> Option.bind (constructRuleWithConfig TypeNames.rule) + config.RecordFieldNames |> Option.bind (constructRuleWithConfig RecordFieldNames.rule) + config.EnumCasesNames |> Option.bind (constructRuleWithConfig EnumCasesNames.rule) + config.UnionCasesNames |> Option.bind (constructRuleWithConfig UnionCasesNames.rule) + config.ModuleNames |> Option.bind (constructRuleWithConfig ModuleNames.rule) + config.LiteralNames |> Option.bind (constructRuleWithConfig LiteralNames.rule) + config.NamespaceNames |> Option.bind (constructRuleWithConfig NamespaceNames.rule) + config.MemberNames |> Option.bind (constructRuleWithConfig MemberNames.rule) + config.ParameterNames |> Option.bind (constructRuleWithConfig ParameterNames.rule) + config.MeasureTypeNames |> Option.bind (constructRuleWithConfig MeasureTypeNames.rule) + config.ActivePatternNames |> Option.bind (constructRuleWithConfig ActivePatternNames.rule) + config.PublicValuesNames |> Option.bind (constructRuleWithConfig PublicValuesNames.rule) + config.NonPublicValuesNames |> Option.bind (constructRuleWithConfig PrivateValuesNames.rule) + config.NonPublicValuesNames |> Option.bind (constructRuleWithConfig InternalValuesNames.rule) + config.PrivateValuesNames |> Option.bind (constructRuleWithConfig PrivateValuesNames.rule) + config.InternalValuesNames |> Option.bind (constructRuleWithConfig InternalValuesNames.rule) + config.UnnestedFunctionNames |> Option.bind (constructRuleWithConfig UnnestedFunctionNames.rule) + config.NestedFunctionNames |> Option.bind (constructRuleWithConfig NestedFunctionNames.rule) + config.MaxNumberOfItemsInTuple |> Option.bind (constructRuleWithConfig MaxNumberOfItemsInTuple.rule) + config.MaxNumberOfFunctionParameters |> Option.bind (constructRuleWithConfig MaxNumberOfFunctionParameters.rule) + config.MaxNumberOfMembers |> Option.bind (constructRuleWithConfig MaxNumberOfMembers.rule) + config.MaxNumberOfBooleanOperatorsInCondition |> Option.bind (constructRuleWithConfig MaxNumberOfBooleanOperatorsInCondition.rule) + config.FavourIgnoreOverLetWild |> Option.bind (constructRuleIfEnabled FavourIgnoreOverLetWild.rule) + config.FavourTypedIgnore |> Option.bind (constructRuleIfEnabled FavourTypedIgnore.rule) + config.WildcardNamedWithAsPattern |> Option.bind (constructRuleIfEnabled WildcardNamedWithAsPattern.rule) + config.UselessBinding |> Option.bind (constructRuleIfEnabled UselessBinding.rule) + config.TupleOfWildcards |> Option.bind (constructRuleIfEnabled TupleOfWildcards.rule) + config.Indentation |> Option.bind (constructRuleIfEnabled Indentation.rule) + config.MaxCharactersOnLine |> Option.bind (constructRuleWithConfig MaxCharactersOnLine.rule) + config.TrailingWhitespaceOnLine |> Option.bind (constructRuleWithConfig TrailingWhitespaceOnLine.rule) + config.MaxLinesInFile |> Option.bind (constructRuleWithConfig MaxLinesInFile.rule) + config.TrailingNewLineInFile |> Option.bind (constructRuleIfEnabled TrailingNewLineInFile.rule) + config.NoTabCharacters |> Option.bind (constructRuleIfEnabled NoTabCharacters.rule) + config.NoPartialFunctions |> Option.bind (constructRuleWithConfig NoPartialFunctions.rule) + config.SuggestUseAutoProperty |> Option.bind (constructRuleIfEnabled SuggestUseAutoProperty.rule) + config.EnsureTailCallDiagnosticsInRecursiveFunctions |> Option.bind (constructRuleIfEnabled EnsureTailCallDiagnosticsInRecursiveFunctions.rule) + config.FavourAsKeyword |> Option.bind (constructRuleIfEnabled FavourAsKeyword.rule) + |] + + findDeprecation config deprecatedAllRules allRules diff --git a/src/FSharpLint.Core/Application/Lint.fs b/src/FSharpLint.Core/Application/Lint.fs index a8170f0fc..bf9a2a986 100644 --- a/src/FSharpLint.Core/Application/Lint.fs +++ b/src/FSharpLint.Core/Application/Lint.fs @@ -61,10 +61,10 @@ module Lint = with get() = let getParseFailureReason = function | ParseFile.FailedToParseFile failures -> - let getFailureReason (x:FSharpDiagnostic) = - $"failed to parse file {x.FileName}, message: {x.Message}" + let getFailureReason (fSharpDiagnostic:FSharpDiagnostic) = + $"failed to parse file {fSharpDiagnostic.FileName}, message: {fSharpDiagnostic.Message}" - String.Join(", ", failures |> Array.map getFailureReason) + String.Join(", ", Array.map getFailureReason failures) | ParseFile.AbortedTypeCheck -> "Type check failed. You might want to build your solution/project first and try again." match this with @@ -85,8 +85,8 @@ module Lint = $"Lint failed to parse files. Failed with: {failureReasons}" [] - type Result<'T> = - | Success of 'T + type Result<'SuccessType> = + | Success of 'SuccessType | Failure of LintFailure /// Provides information on what the linter is currently doing. @@ -104,9 +104,9 @@ module Lint = /// Path of the F# file the progress information is for. member this.FilePath() = match this with - | Starting(f) - | ReachedEnd(f, _) - | Failed(f, _) -> f + | Starting(filePath) + | ReachedEnd(filePath, _) + | Failed(filePath, _) -> filePath [] type LintInfo = @@ -119,71 +119,101 @@ module Lint = { IndentationRuleContext:Map NoTabCharactersRuleContext:(string * Range) list } - let runAstNodeRules (rules:RuleMetadata []) (globalConfig:Rules.GlobalRuleConfig) typeCheckResults (filePath:string) (fileContent:string) (lines:string []) syntaxArray = + type RunAstNodeRulesConfig = + { + Rules: RuleMetadata[] + GlobalConfig: Rules.GlobalRuleConfig + TypeCheckResults: FSharpCheckFileResults option + FilePath: string + FileContent: string + Lines: string[] + SyntaxArray: AbstractSyntaxArray.Node array + } + + let runAstNodeRules (config: RunAstNodeRulesConfig) = let mutable indentationRuleState = Map.empty let mutable noTabCharactersRuleState = List.empty + let collect index (astNode: AbstractSyntaxArray.Node) = + let getParents (depth:int) = AbstractSyntaxArray.getBreadcrumbs depth config.SyntaxArray index + let astNodeParams = + { + AstNode = astNode.Actual + NodeHashcode = astNode.Hashcode + NodeIndex = index + SyntaxArray = config.SyntaxArray + GetParents = getParents + FilePath = config.FilePath + FileContent = config.FileContent + Lines = config.Lines + CheckInfo = config.TypeCheckResults + GlobalConfig = config.GlobalConfig + } + // Build state for rules with context. + indentationRuleState <- Indentation.ContextBuilder.builder indentationRuleState astNode.Actual + noTabCharactersRuleState <- NoTabCharacters.ContextBuilder.builder noTabCharactersRuleState astNode.Actual + + Array.collect + (fun (rule: RuleMetadata) -> runAstNodeRule rule astNodeParams) + config.Rules + // Collect suggestions for AstNode rules, and build context for following rules. let astNodeSuggestions = - syntaxArray - |> Array.mapi (fun i astNode -> (i, astNode)) - |> Array.collect (fun (i, astNode) -> - let getParents (depth:int) = AbstractSyntaxArray.getBreadcrumbs depth syntaxArray i - let astNodeParams = - { AstNode = astNode.Actual - NodeHashcode = astNode.Hashcode - NodeIndex = i - SyntaxArray = syntaxArray - GetParents = getParents - FilePath = filePath - FileContent = fileContent - Lines = lines - CheckInfo = typeCheckResults - GlobalConfig = globalConfig } - // Build state for rules with context. - indentationRuleState <- Indentation.ContextBuilder.builder indentationRuleState astNode.Actual - noTabCharactersRuleState <- NoTabCharacters.ContextBuilder.builder noTabCharactersRuleState astNode.Actual - - rules - |> Array.collect (fun rule -> runAstNodeRule rule astNodeParams)) + config.SyntaxArray + |> Array.mapi (fun index astNode -> (index, astNode)) + |> Array.collect (fun (index, astNode) -> collect index astNode) let context = { IndentationRuleContext = indentationRuleState NoTabCharactersRuleContext = noTabCharactersRuleState } - rules |> Array.iter (fun rule -> rule.RuleConfig.Cleanup()) + Array.iter (fun (rule: RuleMetadata) -> rule.RuleConfig.Cleanup()) config.Rules (astNodeSuggestions, context) - let runLineRules (lineRules:Configuration.LineRules) (globalConfig:Rules.GlobalRuleConfig) (filePath:string) (fileContent:string) (lines:string []) (context:Context) = - fileContent - |> String.toLines - |> Array.collect (fun (line, lineNumber, isLastLine) -> + type RunLineRulesConfig = + { + LineRules: Configuration.LineRules + GlobalConfig: Rules.GlobalRuleConfig + FilePath: string + FileContent: string + Lines: string[] + Context: Context + } + let runLineRules (config: RunLineRulesConfig) = + let collectErrors (line: string) (lineNumber: int) (isLastLine: bool) = let lineParams = - { LineRuleParams.Line = line - LineNumber = lineNumber + 1 - IsLastLine = isLastLine - FilePath = filePath - FileContent = fileContent - Lines = lines - GlobalConfig = globalConfig } + { + LineRuleParams.Line = line + LineNumber = lineNumber + 1 + IsLastLine = isLastLine + FilePath = config.FilePath + FileContent = config.FileContent + Lines = config.Lines + GlobalConfig = config.GlobalConfig + } let indentationError = - lineRules.IndentationRule - |> Option.map (fun rule -> runLineRuleWithContext rule context.IndentationRuleContext lineParams) + Option.map + (fun rule -> runLineRuleWithContext rule config.Context.IndentationRuleContext lineParams) + config.LineRules.IndentationRule let noTabCharactersError = - lineRules.NoTabCharactersRule - |> Option.map (fun rule -> runLineRuleWithContext rule context.NoTabCharactersRuleContext lineParams) + Option.map + (fun rule -> runLineRuleWithContext rule config.Context.NoTabCharactersRuleContext lineParams) + config.LineRules.NoTabCharactersRule let lineErrors = - lineRules.GenericLineRules - |> Array.collect (fun rule -> runLineRule rule lineParams) + Array.collect (fun rule -> runLineRule rule lineParams) config.LineRules.GenericLineRules [| - indentationError |> Option.toArray - noTabCharactersError |> Option.toArray - lineErrors |> Array.singleton - |]) + Option.toArray indentationError + Option.toArray noTabCharactersError + Array.singleton lineErrors + |] + + config.FileContent + |> String.toLines + |> Array.collect (fun (line, lineNumber, isLastLine) -> collectErrors line lineNumber isLastLine) |> Array.concat |> Array.concat @@ -204,7 +234,7 @@ module Lint = let cancelHasNotBeenRequested () = match lintInfo.CancellationToken with - | Some(x) -> not x.IsCancellationRequested + | Some(value) -> not value.IsCancellationRequested | None -> true let enabledRules = Configuration.flattenConfig lintInfo.Configuration @@ -214,8 +244,8 @@ module Lint = [| enabledRules.LineRules.IndentationRule |> Option.map (fun rule -> rule.Name) |> Option.toArray enabledRules.LineRules.NoTabCharactersRule |> Option.map (fun rule -> rule.Name) |> Option.toArray - enabledRules.LineRules.GenericLineRules |> Array.map (fun rule -> rule.Name) - enabledRules.AstNodeRules |> Array.map (fun rule -> rule.Name) + Array.map (fun rule -> rule.Name) enabledRules.LineRules.GenericLineRules + Array.map (fun rule -> rule.Name) enabledRules.AstNodeRules |] |> Array.concat |> Set.ofArray let suppressionInfo = Suppression.parseSuppressionInfo allRuleNames (Array.toList lines) @@ -224,8 +254,28 @@ module Lint = let syntaxArray = AbstractSyntaxArray.astToArray fileInfo.Ast // Collect suggestions for AstNode rules - let (astNodeSuggestions, context) = runAstNodeRules enabledRules.AstNodeRules enabledRules.GlobalConfig fileInfo.TypeCheckResults fileInfo.File fileInfo.Text lines syntaxArray - let lineSuggestions = runLineRules enabledRules.LineRules enabledRules.GlobalConfig fileInfo.File fileInfo.Text lines context + let (astNodeSuggestions, context) = + runAstNodeRules + { + Rules = enabledRules.AstNodeRules + GlobalConfig = enabledRules.GlobalConfig + TypeCheckResults = fileInfo.TypeCheckResults + FilePath = fileInfo.File + FileContent = fileInfo.Text + Lines = lines + SyntaxArray = syntaxArray + } + + let lineSuggestions = + runLineRules + { + LineRules = enabledRules.LineRules + GlobalConfig = enabledRules.GlobalConfig + FilePath = fileInfo.File + FileContent = fileInfo.Text + Lines = lines + Context = context + } [| lineSuggestions; astNodeSuggestions |] |> Array.concat @@ -257,9 +307,9 @@ module Lint = with | :? TimeoutException -> () // Do nothing. with - | e -> Failed(fileInfo.File, e) |> lintInfo.ReportLinterProgress + | exn -> Failed(fileInfo.File, exn) |> lintInfo.ReportLinterProgress - ReachedEnd(fileInfo.File, fileWarnings |> Seq.toList) |> lintInfo.ReportLinterProgress + ReachedEnd(fileInfo.File, Seq.toList fileWarnings) |> lintInfo.ReportLinterProgress let private runProcess (workingDir:string) (exePath:string) (args:string) = let psi = @@ -273,17 +323,17 @@ module Lint = UseShellExecute = false ) - use p = new System.Diagnostics.Process(StartInfo = psi) + use proc = new System.Diagnostics.Process(StartInfo = psi) let sbOut = System.Text.StringBuilder() - p.OutputDataReceived.Add(fun ea -> sbOut.AppendLine(ea.Data) |> ignore) + proc.OutputDataReceived.Add(fun ea -> sbOut.AppendLine(ea.Data) |> ignore) let sbErr = System.Text.StringBuilder() - p.ErrorDataReceived.Add(fun ea -> sbErr.AppendLine(ea.Data) |> ignore) - p.Start() |> ignore - p.BeginOutputReadLine() - p.BeginErrorReadLine() - p.WaitForExit() + proc.ErrorDataReceived.Add(fun ea -> sbErr.AppendLine(ea.Data) |> ignore) + proc.Start() |> ignore + proc.BeginOutputReadLine() + proc.BeginErrorReadLine() + proc.WaitForExit() - let exitCode = p.ExitCode + let exitCode = proc.ExitCode (exitCode, (workingDir, exePath, args)) @@ -322,12 +372,12 @@ module Lint = | Success of Suggestion.LintWarning list | Failure of LintFailure - member self.TryGetSuccess([] success:byref) = - match self with + member this.TryGetSuccess([] success:byref) = + match this with | Success value -> success <- value; true | _ -> false - member self.TryGetFailure([] failure:byref) = - match self with + member this.TryGetFailure([] failure:byref) = + match this with | Failure value -> failure <- value; true | _ -> false @@ -403,9 +453,9 @@ module Lint = let projectProgress = Option.defaultValue ignore optionalParams.ReportLinterProgress let warningReceived (warning:Suggestion.LintWarning) = - lintWarnings.AddLast warning |> ignore + lintWarnings.AddLast warning |> ignore> - optionalParams.ReceivedWarning |> Option.iter (fun func -> func warning) + Option.iter (fun func -> func warning) optionalParams.ReceivedWarning let checker = FSharpChecker.Create(keepAssemblyContents=true) @@ -428,7 +478,7 @@ module Lint = |> List.filter (not << isIgnoredFile) |> List.map (fun file -> ParseFile.parseFile file checker (Some projectOptions)) - let failedFiles = parsedFiles |> List.choose getFailedFiles + let failedFiles = List.choose getFailedFiles parsedFiles if List.isEmpty failedFiles then parsedFiles @@ -443,7 +493,7 @@ module Lint = | Ok projectOptions -> match parseFilesInProject (Array.toList projectOptions.SourceFiles) projectOptions with | Success _ -> lintWarnings |> Seq.toList |> LintResult.Success - | Failure x -> LintResult.Failure x + | Failure lintFailure -> LintResult.Failure lintFailure | Error error -> MSBuildFailedToLoadProjectFile (projectFilePath, BuildFailure.InvalidProjectFileMessage error) |> LintResult.Failure @@ -490,7 +540,7 @@ module Lint = | LintResult.Success warnings -> (List.append warnings successes, failures) | LintResult.Failure err -> - (successes, err :: failures)) ([], []) + (successes, err :: failures)) (List.Empty, List.Empty) match failures with | [] -> @@ -513,9 +563,9 @@ module Lint = let lintWarnings = LinkedList() let warningReceived (warning:Suggestion.LintWarning) = - lintWarnings.AddLast warning |> ignore + lintWarnings.AddLast warning |> ignore> - optionalParams.ReceivedWarning |> Option.iter (fun func -> func warning) + Option.iter (fun func -> func warning) optionalParams.ReceivedWarning let lintInformation = { Configuration = config CancellationToken = optionalParams.CancellationToken @@ -555,9 +605,9 @@ module Lint = let lintWarnings = LinkedList() let warningReceived (warning:Suggestion.LintWarning) = - lintWarnings.AddLast warning |> ignore + lintWarnings.AddLast warning |> ignore> - optionalParams.ReceivedWarning |> Option.iter (fun func -> func warning) + Option.iter (fun func -> func warning) optionalParams.ReceivedWarning let lintInformation = { Configuration = config diff --git a/src/FSharpLint.Core/Application/Lint.fsi b/src/FSharpLint.Core/Application/Lint.fsi index 7d1f1ff95..7e697df5a 100644 --- a/src/FSharpLint.Core/Application/Lint.fsi +++ b/src/FSharpLint.Core/Application/Lint.fsi @@ -119,11 +119,32 @@ module Lint = member TryGetSuccess : byref -> bool member TryGetFailure : byref -> bool + type RunAstNodeRulesConfig = + { + Rules: RuleMetadata[] + GlobalConfig: Rules.GlobalRuleConfig + TypeCheckResults: FSharpCheckFileResults option + FilePath: string + FileContent: string + Lines: string[] + SyntaxArray: AbstractSyntaxArray.Node array + } + /// Runs all rules which take a node of the AST as input. - val runAstNodeRules : RuleMetadata [] -> Rules.GlobalRuleConfig -> FSharpCheckFileResults option -> string -> string -> string [] -> AbstractSyntaxArray.Node [] -> Suggestion.LintWarning [] * Context + val runAstNodeRules : RunAstNodeRulesConfig -> Suggestion.LintWarning [] * Context + + type RunLineRulesConfig = + { + LineRules: Configuration.LineRules + GlobalConfig: Rules.GlobalRuleConfig + FilePath: string + FileContent: string + Lines: string[] + Context: Context + } /// Runs all rules which take a line of text as input. - val runLineRules : LineRules -> Rules.GlobalRuleConfig -> string -> string -> string [] -> Context -> Suggestion.LintWarning [] + val runLineRules : RunLineRulesConfig -> Suggestion.LintWarning [] /// Lints an entire F# solution by linting all projects specified in the `.sln`, `slnx` or `.slnf` file. val lintSolution : optionalParams:OptionalLintParameters -> solutionFilePath:string -> toolsPath:Ionide.ProjInfo.Types.ToolsPath -> LintResult diff --git a/src/FSharpLint.Core/FSharpLint.Core.fsproj b/src/FSharpLint.Core/FSharpLint.Core.fsproj index ff228aaa4..5d82e3356 100644 --- a/src/FSharpLint.Core/FSharpLint.Core.fsproj +++ b/src/FSharpLint.Core/FSharpLint.Core.fsproj @@ -18,6 +18,8 @@ + + diff --git a/src/FSharpLint.Core/Framework/AbstractSyntaxArray.fs b/src/FSharpLint.Core/Framework/AbstractSyntaxArray.fs index a1715ad15..aaa08e9e9 100644 --- a/src/FSharpLint.Core/Framework/AbstractSyntaxArray.fs +++ b/src/FSharpLint.Core/Framework/AbstractSyntaxArray.fs @@ -274,9 +274,9 @@ module AbstractSyntaxArray = let result = Array.zeroCreate nodes.Count - let mutable i = 0 - while i < nodes.Count do - let skip = skips.[i] + let mutable index = 0 + while index < nodes.Count do + let skip = skips.[index] let node = nodes.[skip.Index] result.[skip.Index] <- @@ -285,20 +285,20 @@ module AbstractSyntaxArray = NumberOfChildren = skip.NumberOfChildren ParentIndex = skip.ParentIndex } - i <- i + 1 + index <- index + 1 result - let getBreadcrumbs maxBreadcrumbs (syntaxArray:Node []) i = - let rec getBreadcrumbs breadcrumbs i = - if i = 0 then - let node = syntaxArray.[i] + let getBreadcrumbs maxBreadcrumbs (syntaxArray:Node []) index = + let rec getBreadcrumbs breadcrumbs index = + if index = 0 then + let node = syntaxArray.[index] node.Actual::breadcrumbs - else if i < syntaxArray.Length && (List.length breadcrumbs) < maxBreadcrumbs then - let node = syntaxArray.[i] + else if index < syntaxArray.Length && (List.length breadcrumbs) < maxBreadcrumbs then + let node = syntaxArray.[index] getBreadcrumbs (node.Actual::breadcrumbs) node.ParentIndex else breadcrumbs - if i = 0 then [] - else getBreadcrumbs [] (syntaxArray.[i].ParentIndex) |> List.rev + if index = 0 then List.Empty + else getBreadcrumbs List.Empty (syntaxArray.[index].ParentIndex) |> List.rev diff --git a/src/FSharpLint.Core/Framework/Ast.fs b/src/FSharpLint.Core/Framework/Ast.fs index 81ab7fc14..a290b4b68 100644 --- a/src/FSharpLint.Core/Framework/Ast.fs +++ b/src/FSharpLint.Core/Framework/Ast.fs @@ -42,8 +42,7 @@ module Ast = /// Concatenates the nested-list structure of `SynAttributes` into a `SynAttribute list` to keep other code /// mostly unchanged. let extractAttributes (attrs:SynAttributes) = - attrs - |> List.collect (fun attrList -> attrList.Attributes) + List.collect (fun (attrList: SynAttributeList) -> attrList.Attributes) attrs /// Inlines pipe operators to give a flat function application expression /// e.g. `x |> List.map id` to `List.map id x`. @@ -61,14 +60,14 @@ module Ast = | "op_PipeLeft" | "op_PipeLeft2" | "op_PipeLeft3" -> flatten (lhs::flattened) rhs | _ -> flatten (lhs::flattened) app - | x -> - let leftExpr, rightExpr = (x, y) + | expression -> + let leftExpr, rightExpr = (expression, y) flatten (rightExpr::flattened) leftExpr | expr -> expr::flattened match functionApplication with | AstNode.Expression(SynExpr.App(_, _, _, _, range) as functionApplication) -> - Some(flatten [] functionApplication, range) + Some(flatten List.Empty functionApplication, range) | _ -> None [] @@ -82,7 +81,7 @@ module Ast = _, [SynMatchClause(SynPat.Wild(_), _, expr, _, _, _)], _, _) -> removeAutoGeneratedMatchesFromLambda expr - | x -> x + | synExpr -> synExpr let (|IsCurriedLambda|_|) = function | SynExpr.Lambda(_, _, parameter, (SynExpr.Lambda(_) as inner), _, _, _) as outer @@ -94,14 +93,17 @@ module Ast = | IsCurriedLambda(parameter, curriedLambda) -> getLambdaParametersAndExpression (parameter::parameters) curriedLambda | SynExpr.Lambda(_, _, parameter, body, _, _, _) -> - { Arguments = parameter::parameters |> List.rev - Body = removeAutoGeneratedMatchesFromLambda body } |> Some + Some + { + Arguments = parameter :: parameters |> List.rev + Body = removeAutoGeneratedMatchesFromLambda body + } | _ -> None match lambda with | AstNode.Expression(SynExpr.Lambda(_, _, _, _, _, range, _) as lambda) -> - getLambdaParametersAndExpression [] lambda - |> Option.map (fun x -> (x, range)) + getLambdaParametersAndExpression List.Empty lambda + |> Option.map (fun lambdaExprs -> (lambdaExprs, range)) | _ -> None let (|Cons|_|) pattern = @@ -122,19 +124,19 @@ module Ast = | _ -> None module List = - let inline revIter f items = - items |> List.rev |> List.iter f + let inline revIter action items = + items |> List.rev |> List.iter action let inline private moduleDeclarationChildren node add = match node with | SynModuleDecl.NestedModule(componentInfo, _, moduleDeclarations, _, _, _) -> - moduleDeclarations |> List.revIter (ModuleDeclaration >> add) + List.revIter (ModuleDeclaration >> add) moduleDeclarations add <| ComponentInfo componentInfo - | SynModuleDecl.Let(_, bindings, _) -> bindings |> List.revIter (Binding >> add) + | SynModuleDecl.Let(_, bindings, _) -> List.revIter (Binding >> add) bindings | SynModuleDecl.Expr(expression, _) -> add <| Expression expression - | SynModuleDecl.Types(typeDefinitions, _) -> typeDefinitions |> List.revIter (TypeDefinition >> add) + | SynModuleDecl.Types(typeDefinitions, _) -> List.revIter (TypeDefinition >> add) typeDefinitions | SynModuleDecl.Exception(SynExceptionDefn.SynExceptionDefn(repr, _, members, _), _) -> - members |> List.revIter (MemberDefinition >> add) + List.revIter (MemberDefinition >> add) members add <| ExceptionRepresentation repr | SynModuleDecl.NamespaceFragment(moduleOrNamespace) -> add <| ModuleOrNamespace moduleOrNamespace | SynModuleDecl.Open(_) @@ -171,7 +173,7 @@ module Ast = | SynType.Array(_, synType, _) -> add <| Type synType | SynType.StaticConstantExpr(expression, _) -> add <| Expression expression | SynType.AnonRecd (_, typeNames, _) -> - typeNames |> List.revIter (snd >> Type >> add) + List.revIter (snd >> Type >> add) typeNames | SynType.Paren(innerType, _) -> add <| Type innerType | SynType.Intersection(synTyparOpt, types, _, _) -> @@ -199,9 +201,9 @@ module Ast = | SynMemberDefn.ImplicitInherit(synType, expression, _, _, _) -> add <| Expression expression add <| Type synType - | SynMemberDefn.LetBindings(bindings, _, _, _) -> bindings |> List.revIter (Binding >> add) + | SynMemberDefn.LetBindings(bindings, _, _, _) -> List.revIter (Binding >> add) bindings | SynMemberDefn.Interface(synType, _, Some(members), _) -> - members |> List.revIter (MemberDefinition >> add) + List.revIter (MemberDefinition >> add) members add <| Type synType | SynMemberDefn.Interface(synType, _, None, _) | SynMemberDefn.Inherit(Some synType, _, _, _) -> add <| Type synType @@ -216,8 +218,8 @@ module Ast = | SynMemberDefn.AutoProperty(_, _, _, None, _, _, _, _, _, expression, _, _) -> add <| Expression expression | SynMemberDefn.GetSetMember(memberDefnForGet, memberDefnForSet, _, _) -> - memberDefnForGet |> Option.iter (Binding >> add) - memberDefnForSet |> Option.iter (Binding >> add) + Option.iter (Binding >> add) memberDefnForGet + Option.iter (Binding >> add) memberDefnForSet let inline private patternChildren node add = match node with @@ -235,7 +237,7 @@ module Ast = | SynPat.Attrib(pattern, _, _) | SynPat.Paren(pattern, _) -> add <| Pattern pattern | SynPat.Named(_) -> () - | SynPat.Record(patternsAndIdentifier, _) -> patternsAndIdentifier |> List.revIter (fun (_, _, pattern) -> pattern |> Pattern |> add) + | SynPat.Record(patternsAndIdentifier, _) -> List.revIter (fun (_, _, pattern) -> pattern |> Pattern |> add) patternsAndIdentifier | SynPat.Const(_) | SynPat.Wild(_) | SynPat.FromParseError(_) @@ -254,7 +256,9 @@ module Ast = add <| Pattern lhs add <| Pattern rhs - let inline private expressionChildren node add = + let inline private expressionChildren (node: SynExpr) (add: AstNode -> unit) = + let addMany = List.iter add + match node with | SynExpr.Paren(expression, _, _, _) | SynExpr.DotGet(expression, _, _, _) @@ -273,9 +277,7 @@ module Ast = | SynExpr.YieldOrReturn(_, expression, _, _) | SynExpr.YieldOrReturnFrom(_, expression, _, _) -> add <| Expression expression | SynExpr.SequentialOrImplicitYield(_, expression1, expression2, ifNotExpression, _) -> - add <| Expression expression1 - add <| Expression expression2 - add <| Expression ifNotExpression + addMany [Expression expression1; Expression expression2; Expression ifNotExpression] | SynExpr.Quote(expression, _, expression1, _, _) | SynExpr.Sequential(_, _, expression, expression1, _, _) | SynExpr.NamedIndexedPropertySet(_, expression, expression1, _) @@ -285,111 +287,99 @@ module Ast = | SynExpr.TryFinally(expression, expression1, _, _, _, _) | SynExpr.Set(expression, expression1, _) | SynExpr.DotSet(expression, _, expression1, _) -> - add <| Expression expression1 - add <| Expression expression + addMany [Expression expression1; Expression expression] | SynExpr.Typed(expression, synType, _) -> - add <| Type synType - add <| Expression expression + addMany [Type synType; Expression expression] | SynExpr.Tuple(_, expressions, _, _) - | SynExpr.ArrayOrList(_, expressions, _) -> expressions |> List.revIter (Expression >> add) + | SynExpr.ArrayOrList(_, expressions, _) -> List.revIter (Expression >> add) expressions | SynExpr.Record(_, Some(expr, _), _, _) -> add <| Expression expr | SynExpr.Record(_, None, _, _) -> () | SynExpr.AnonRecd(_, Some (expr,_), _, _, _) -> add <| Expression expr | SynExpr.AnonRecd(_, None, _, _, _) -> () | SynExpr.ObjExpr(synType, _, _, bindings, _, _, _, _) -> - bindings |> List.revIter (Binding >> add) + List.revIter (Binding >> add) bindings add <| Type synType - | SynExpr.ImplicitZero(_) - | SynExpr.Null(_) - | SynExpr.Const(_) - | SynExpr.DiscardAfterMissingQualificationAfterDot(_) - | SynExpr.FromParseError(_) - | SynExpr.LibraryOnlyILAssembly(_) - | SynExpr.LibraryOnlyStaticOptimization(_) - | SynExpr.LibraryOnlyUnionCaseFieldGet(_) - | SynExpr.LibraryOnlyUnionCaseFieldSet(_) - | SynExpr.ArbitraryAfterError(_) -> () | SynExpr.DotNamedIndexedPropertySet(expression, _, expression1, expression2, _) | SynExpr.For(_, _, _, _, expression, _, expression1, expression2, _) -> - add <| Expression expression2 - add <| Expression expression1 - add <| Expression expression + addMany [Expression expression2; Expression expression1; Expression expression] | SynExpr.LetOrUseBang(_, _, _, pattern, rightHandSide, andBangs, leftHandSide, _, _) -> - add <| Expression rightHandSide - add <| Expression leftHandSide + addMany [Expression rightHandSide; Expression leftHandSide] // TODO: is the the correct way to handle the new `and!` syntax? - andBangs |> List.iter (fun (SynExprAndBang(_, _, _, pattern, body, _, _)) -> - add <| Expression body - add <| Pattern pattern - ) + List.iter (fun (SynExprAndBang(_, _, _, pattern, body, _, _)) -> + addMany [Expression body; Pattern pattern] + ) andBangs add <| Pattern pattern | SynExpr.ForEach(_, _, _, _, pattern, expression, expression1, _) -> - add <| Expression expression1 - add <| Expression expression - add <| Pattern pattern + addMany [Expression expression1; Expression expression; Pattern pattern] | SynExpr.MatchLambda(_, _, matchClauses, _, _) -> - matchClauses |> List.revIter (Match >> add) + List.revIter (Match >> add) matchClauses | SynExpr.TryWith(expression, matchClauses, _, _, _, _) | SynExpr.MatchBang(_, expression, matchClauses, _, _) | SynExpr.Match(_, expression, matchClauses, _, _) -> - matchClauses |> List.revIter (Match >> add) + List.revIter (Match >> add) matchClauses add <| Expression expression | SynExpr.TypeApp(expression, _, types, _, _, _, _) -> - types |> List.revIter (Type >> add) + List.revIter (Type >> add) types add <| Expression expression | SynExpr.New(_, synType, expression, _) | SynExpr.TypeTest(expression, synType, _) | SynExpr.Upcast(expression, synType, _) | SynExpr.Downcast(expression, synType, _) -> - add <| Type synType - add <| Expression expression + addMany [Type synType; Expression expression] | SynExpr.LetOrUse(_, _, bindings, expression, _, _) -> add <| Expression expression - bindings |> List.revIter (Binding >> add) + List.revIter (Binding >> add) bindings | SynExpr.Ident(ident) -> add <| Identifier([ident.idText], ident.idRange) | SynExpr.LongIdent(_, SynLongIdent(ident, _, _), _, range) -> - add <| Identifier(ident |> List.map (fun x -> x.idText), range) + add <| Identifier(List.map (fun (identifier: Ident) -> identifier.idText) ident, range) | SynExpr.IfThenElse(cond, body, Some(elseExpr), _, _, _, _) -> - add <| Else elseExpr - add <| Expression body - add <| Expression cond + addMany [Else elseExpr; Expression body; Expression cond] | SynExpr.IfThenElse(cond, body, None, _, _, _, _) -> - add <| Expression body - add <| Expression cond - | SynExpr.InterpolatedString(contents, _, range) -> - contents - |> List.iter ( + addMany [Expression body; Expression cond] + | SynExpr.InterpolatedString(contents, _, range) -> + List.iter ( function - | SynInterpolatedStringPart.String _ -> - () - | SynInterpolatedStringPart.FillExpr (expr, _ident) -> - add <| Expression expr - ) + | SynInterpolatedStringPart.String _ -> () + | SynInterpolatedStringPart.FillExpr (expr, _ident) -> add <| Expression expr + ) + contents + (* + | SynExpr.ImplicitZero(_) + | SynExpr.Null(_) + | SynExpr.Const(_) + | SynExpr.DiscardAfterMissingQualificationAfterDot(_) + | SynExpr.FromParseError(_) + | SynExpr.LibraryOnlyILAssembly(_) + | SynExpr.LibraryOnlyStaticOptimization(_) + | SynExpr.LibraryOnlyUnionCaseFieldGet(_) + | SynExpr.LibraryOnlyUnionCaseFieldSet(_) + | SynExpr.ArbitraryAfterError(_) | SynExpr.Lambda(_) | SynExpr.DotLambda(_) | SynExpr.App(_) - | SynExpr.Fixed(_) + | SynExpr.Fixed(_) -> () + *) | SynExpr.Typar(_) -> () | SynExpr.WhileBang(_, expression, expression1, _) -> add <| Expression expression1 add <| Expression expression - | SynExpr.DebugPoint(_debugPoint, _, innerExpr) -> + | SynExpr.DebugPoint(_debugPoint, _, innerExpr) -> add <| Expression innerExpr | SynExpr.Dynamic(funcExpr, _, argExpr, _) -> - add <| Expression funcExpr - add <| Expression argExpr - | SynExpr.IndexFromEnd(expr, _) -> + addMany [Expression funcExpr; Expression argExpr] + | SynExpr.IndexFromEnd(expr, _) -> add <| Expression expr | SynExpr.IndexRange(expr1, _, expr2, _, _, _) -> expr1 |> Option.iter (Expression >> add) expr2 |> Option.iter (Expression >> add) + | _ -> () let inline private typeSimpleRepresentationChildren node add = match node with - | SynTypeDefnSimpleRepr.Union(_, unionCases, _) -> unionCases |> List.revIter (UnionCase >> add) - | SynTypeDefnSimpleRepr.Enum(enumCases, _) -> enumCases |> List.revIter (EnumCase >> add) - | SynTypeDefnSimpleRepr.Record(_, fields, _) -> fields |> List.revIter (Field >> add) + | SynTypeDefnSimpleRepr.Union(_, unionCases, _) -> List.revIter (UnionCase >> add) unionCases + | SynTypeDefnSimpleRepr.Enum(enumCases, _) -> List.revIter (EnumCase >> add) enumCases + | SynTypeDefnSimpleRepr.Record(_, fields, _) -> List.revIter (Field >> add) fields | SynTypeDefnSimpleRepr.TypeAbbrev(_, synType, _) -> add <| Type synType | SynTypeDefnSimpleRepr.Exception(exceptionRepr) -> add <| ExceptionRepresentation exceptionRepr | SynTypeDefnSimpleRepr.General(_) @@ -429,7 +419,7 @@ module Ast = let inline private typeRepresentationChildren node add = match node with | SynTypeDefnRepr.ObjectModel(_, members, _) -> - members |> List.revIter (MemberDefinition >> add) + List.revIter (MemberDefinition >> add) members | SynTypeDefnRepr.Simple(typeSimpleRepresentation, _) -> add <| TypeSimpleRepresentation typeSimpleRepresentation | SynTypeDefnRepr.Exception(exceptionRepr) -> @@ -438,53 +428,53 @@ module Ast = let inline private unionCaseChildren node add = match node with | SynUnionCase(caseType=(SynUnionCaseKind.Fields fields)) -> - fields |> List.revIter (Field >> add) + List.revIter (Field >> add) fields | SynUnionCase(caseType=(SynUnionCaseKind.FullType (fullType, _))) -> add (Type fullType) /// Extracts the child nodes to be visited from a given node. let traverseNode node add = match node with - | ModuleDeclaration(x) -> moduleDeclarationChildren x add + | ModuleDeclaration(moduleDecl) -> moduleDeclarationChildren moduleDecl add | ModuleOrNamespace(SynModuleOrNamespace(_, _, _, moduleDeclarations, _, _, _, _, _)) -> - moduleDeclarations |> List.revIter (ModuleDeclaration >> add) + List.revIter (ModuleDeclaration >> add) moduleDeclarations | Binding(SynBinding(_, _, _, _, _, _, _, pattern, _, expression, _, _, _)) -> add <| Expression expression add <| Pattern pattern | ExceptionRepresentation(SynExceptionDefnRepr.SynExceptionDefnRepr(_, unionCase, _, _, _, _)) -> add <| UnionCase unionCase | TypeDefinition(SynTypeDefn(componentInfo, typeRepresentation, members, implicitCtor, _, _)) -> - implicitCtor |> Option.iter (MemberDefinition >> add) - members |> List.revIter (MemberDefinition >> add) + Option.iter (MemberDefinition >> add) implicitCtor + List.revIter (MemberDefinition >> add) members add <| TypeRepresentation typeRepresentation add <| ComponentInfo componentInfo - | TypeSimpleRepresentation(x) -> typeSimpleRepresentationChildren x add - | Type(x) -> typeChildren x add - | Match(x) -> matchChildren x add - | MemberDefinition(x) -> memberDefinitionChildren x add + | TypeSimpleRepresentation(value) -> typeSimpleRepresentationChildren value add + | Type(value) -> typeChildren value add + | Match(value) -> matchChildren value add + | MemberDefinition(value) -> memberDefinitionChildren value add | Field(SynField(_, _, _, synType, _, _, _, _, _)) -> add <| Type synType - | Pattern(x) -> patternChildren x add - | ConstructorArguments(x) -> constructorArgumentsChildren x add - | SimplePattern(x) -> simplePatternChildren x add - | LambdaArg(x) - | SimplePatterns(x) -> simplePatternsChildren x add + | Pattern(value) -> patternChildren value add + | ConstructorArguments(value) -> constructorArgumentsChildren value add + | SimplePattern(value) -> simplePatternChildren value add + | LambdaArg(value) + | SimplePatterns(value) -> simplePatternsChildren value add | InterfaceImplementation(SynInterfaceImpl(synType, _, bindings, _, _)) -> - bindings |> List.revIter (Binding >> add) + List.revIter (Binding >> add) bindings add <| Type synType - | TypeRepresentation(x) -> typeRepresentationChildren x add - | FuncApp(exprs, _) -> exprs |> List.revIter (Expression >> add) + | TypeRepresentation value -> typeRepresentationChildren value add + | FuncApp(exprs, _) -> List.revIter (Expression >> add) exprs | Lambda({ Arguments = args; Body = body }, _) -> add <| LambdaBody(body) args |> List.revIter (LambdaArg >> add) - | LambdaBody(x) - | Else(x) - | Expression(x) -> expressionChildren x add + | LambdaBody(expression) + | Else(expression) + | Expression(expression) -> expressionChildren expression add | File(ParsedInput.ImplFile(ParsedImplFileInput(_, _, _, _, _, moduleOrNamespaces, _, _, _))) -> moduleOrNamespaces |> List.revIter (ModuleOrNamespace >> add) - | UnionCase(x) -> unionCaseChildren x add + | UnionCase(unionCase) -> unionCaseChildren unionCase add | File(ParsedInput.SigFile(_)) | ComponentInfo(_) | EnumCase(_) diff --git a/src/FSharpLint.Core/Framework/AstInfo.fs b/src/FSharpLint.Core/Framework/AstInfo.fs index d3c431280..62bfd7c3e 100644 --- a/src/FSharpLint.Core/Framework/AstInfo.fs +++ b/src/FSharpLint.Core/Framework/AstInfo.fs @@ -28,61 +28,64 @@ module AstInfo = | None -> Function let operatorIdentifiers = - [ "op_Nil" - "op_ColonColon" - "op_Addition" - "op_Splice" - "op_SpliceUntyped" - "op_Increment" - "op_Decrement" - "op_Subtraction" - "op_Multiply" - "op_Exponentiation" - "op_Division" - "op_Append" - "op_Concatenate" - "op_Modulus" - "op_BitwiseAnd" - "op_BitwiseOr" - "op_ExclusiveOr" - "op_LeftShift" - "op_LogicalNot" - "op_RightShift" - "op_UnaryPlus" - "op_UnaryNegation" - "op_AddressOf" - "op_IntegerAddressOf" - "op_BooleanAnd" - "op_BooleanOr" - "op_LessThanOrEqual" - "op_Equality" - "op_Inequality" - "op_GreaterThanOrEqual" - "op_LessThan" - "op_GreaterThan" - "op_PipeRight" - "op_PipeRight2" - "op_PipeRight3" - "op_PipeLeft" - "op_PipeLeft2" - "op_PipeLeft3" - "op_Dereference" - "op_ComposeRight" - "op_ComposeLeft" - "op_TypedQuotationUnicode" - "op_ChevronsBar" - "op_Quotation" - "op_QuotationUntyped" - "op_AdditionAssignment" - "op_SubtractionAssignment" - "op_MultiplyAssignment" - "op_DivisionAssignment" - "op_Range" - "op_RangeStep" - "op_Dynamic" - "op_DynamicAssignment" - "op_ArrayLookup" - "op_ArrayAssign" ] |> Set.ofList + Set.ofList + [ + "op_Nil" + "op_ColonColon" + "op_Addition" + "op_Splice" + "op_SpliceUntyped" + "op_Increment" + "op_Decrement" + "op_Subtraction" + "op_Multiply" + "op_Exponentiation" + "op_Division" + "op_Append" + "op_Concatenate" + "op_Modulus" + "op_BitwiseAnd" + "op_BitwiseOr" + "op_ExclusiveOr" + "op_LeftShift" + "op_LogicalNot" + "op_RightShift" + "op_UnaryPlus" + "op_UnaryNegation" + "op_AddressOf" + "op_IntegerAddressOf" + "op_BooleanAnd" + "op_BooleanOr" + "op_LessThanOrEqual" + "op_Equality" + "op_Inequality" + "op_GreaterThanOrEqual" + "op_LessThan" + "op_GreaterThan" + "op_PipeRight" + "op_PipeRight2" + "op_PipeRight3" + "op_PipeLeft" + "op_PipeLeft2" + "op_PipeLeft3" + "op_Dereference" + "op_ComposeRight" + "op_ComposeLeft" + "op_TypedQuotationUnicode" + "op_ChevronsBar" + "op_Quotation" + "op_QuotationUntyped" + "op_AdditionAssignment" + "op_SubtractionAssignment" + "op_MultiplyAssignment" + "op_DivisionAssignment" + "op_Range" + "op_RangeStep" + "op_Dynamic" + "op_DynamicAssignment" + "op_ArrayLookup" + "op_ArrayAssign" + ] /// Operator identifiers can be made up of "op_" followed by a sequence of operators from this list. let operators = @@ -112,11 +115,12 @@ module AstInfo = "LBrack" "RBrack" ] + [] let rec isSequenceOfOperators (str:string) = if Seq.isEmpty str then true else - let operator = operators |> List.tryFind (fun op -> str.StartsWith(op)) + let operator = List.tryFind (fun (op: string) -> str.StartsWith(op)) operators match operator with | Some(operator) -> str.Substring(operator.Length) |> isSequenceOfOperators @@ -124,7 +128,7 @@ module AstInfo = /// Is an identifier an operator overload? let isOperator (identifier:string) = - if operatorIdentifiers |> Set.contains identifier then + if Set.contains identifier operatorIdentifiers then true else if identifier.StartsWith("op_") && identifier.Length > 3 then diff --git a/src/FSharpLint.Core/Framework/HintParser.fs b/src/FSharpLint.Core/Framework/HintParser.fs index 210b03e69..e7e8b068d 100644 --- a/src/FSharpLint.Core/Framework/HintParser.fs +++ b/src/FSharpLint.Core/Framework/HintParser.fs @@ -1,399 +1,20 @@ namespace FSharpLint.Framework +open System open FParsec open FSharp.Compiler.Tokenization +open HintParserTypes module HintParser = - type Constant = - | Byte of byte - | Bytes of byte[] - | Char of char - | Decimal of decimal - | Double of double - | Int16 of int16 - | Int32 of int32 - | Int64 of int64 - | IntPtr of nativeint - | SByte of sbyte - | Single of single - | UInt16 of uint16 - | UInt32 of uint32 - | UInt64 of uint64 - | UIntPtr of unativeint - | UserNum of bigint * char - | String of string - | Unit - | Bool of bool - - [] - type Pattern = - | Cons of Pattern * Pattern - | Or of Pattern * Pattern - | Wildcard - | Variable of char - | Identifier of string list - | Constant of Constant - | Parentheses of Pattern - | Tuple of Pattern list - | List of Pattern list - | Array of Pattern list - | Null - - [] - type Expression = - | FunctionApplication of Expression list - | InfixOperator of operatorIdentifier:Expression * Expression * Expression - | PrefixOperator of operatorIdentifier:Expression * Expression - | AddressOf of singleAmpersand:bool * Expression - | Wildcard - | Variable of char - | Identifier of string list - | Constant of Constant - | Parentheses of Expression - | Lambda of LambdaArg list * LambdaBody - | LambdaBody of Expression - | LambdaArg of Expression - | Tuple of Expression list - | List of Expression list - | Array of Expression list - | If of cond:Expression * body:Expression * ``else``:Expression option - | Else of Expression - | Null - and LambdaArg = LambdaArg of Expression - and LambdaBody = LambdaBody of Expression - - type HintNode = - | HintPat of Pattern - | HintExpr of Expression - - type Suggestion = - | Expr of Expression - | Message of string - - type Hint = - { MatchedNode:HintNode - Suggestion:Suggestion } - - /// Provides a way of creating a single list from any number of hint ASTs. - /// Means we can simply iterate over a single list for each node in the F# tree - /// when matching hints rather than check each hint AST for each node. - module MergeSyntaxTrees = - - open System.Collections.Generic - - type SyntaxHintNode = - | Identifier = 1uy - | Null = 2uy - | Expression = 3uy - | FuncApp = 4uy - | Unit = 5uy - | AddressOf = 6uy - - | If = 10uy - | Else = 11uy - - | Lambda = 20uy - | LambdaArg = 21uy - | LambdaBody = 22uy - - | ArrayOrList = 30uy - | Tuple = 31uy - - | Variable = 40uy - | Wildcard = 41uy - - | ConstantBool = 51uy - | ConstantByte = 52uy - | ConstantChar = 53uy - | ConstantDecimal = 54uy - | ConstantDouble = 55uy - | ConstantInt16 = 56uy - | ConstantInt32 = 57uy - | ConstantInt64 = 58uy - | ConstantIntPtr = 59uy - | ConstantSByte = 60uy - | ConstantSingle = 61uy - | ConstantString = 62uy - | ConstantUInt16 = 63uy - | ConstantUInt32 = 64uy - | ConstantUInt64 = 65uy - | ConstantUIntPtr = 66uy - | ConstantBytes = 67uy - | ConstantUserNum = 68uy - - | Cons = 101uy - | And = 102uy - | Or = 103uy - - [] - type Node = - { Edges:Edges - MatchedHint:Hint list } - and [] Edges = - { Lookup:Dictionary - AnyMatch:(char option * Node) list } - - override lhs.Equals(other) = - match other with - | :? Edges as rhs -> - let getList dict = Seq.toList dict |> List.map (fun (x:KeyValuePair<_, _>) -> (x.Key, x.Value)) - - lhs.AnyMatch = rhs.AnyMatch && - lhs.Lookup.Count = rhs.Lookup.Count && - getList lhs.Lookup = getList rhs.Lookup - | _ -> false - - override this.GetHashCode() = hash (this.AnyMatch, hash this.Lookup) - - static member Empty = { Lookup = Dictionary<_, _>(); AnyMatch = [] } - - let private getConstKey = function - | Constant.Unit -> SyntaxHintNode.Unit - | Constant.Bool(_) -> SyntaxHintNode.ConstantBool - | Constant.Byte(_) -> SyntaxHintNode.ConstantByte - | Constant.Bytes(_) -> SyntaxHintNode.ConstantBytes - | Constant.Char(_) -> SyntaxHintNode.ConstantChar - | Constant.Decimal(_) -> SyntaxHintNode.ConstantDecimal - | Constant.Double(_) -> SyntaxHintNode.ConstantDouble - | Constant.Int16(_) -> SyntaxHintNode.ConstantInt16 - | Constant.Int32(_) -> SyntaxHintNode.ConstantInt32 - | Constant.Int64(_) -> SyntaxHintNode.ConstantInt64 - | Constant.IntPtr(_) -> SyntaxHintNode.ConstantIntPtr - | Constant.SByte(_) -> SyntaxHintNode.ConstantSByte - | Constant.Single(_) -> SyntaxHintNode.ConstantSingle - | Constant.String(_) -> SyntaxHintNode.ConstantString - | Constant.UInt16(_) -> SyntaxHintNode.ConstantUInt16 - | Constant.UInt32(_) -> SyntaxHintNode.ConstantUInt32 - | Constant.UInt64(_) -> SyntaxHintNode.ConstantUInt64 - | Constant.UIntPtr(_) -> SyntaxHintNode.ConstantUIntPtr - | Constant.UserNum(_) -> SyntaxHintNode.ConstantUserNum - - let rec private getExprKey = function - | Expression.FunctionApplication(_) - | Expression.InfixOperator(_) - | Expression.PrefixOperator(_) -> SyntaxHintNode.FuncApp - | Expression.AddressOf(_) -> SyntaxHintNode.AddressOf - | Expression.Parentheses(expr) -> getExprKey expr - | Expression.Lambda(_) -> SyntaxHintNode.Lambda - | Expression.LambdaArg(_) -> SyntaxHintNode.LambdaArg - | Expression.LambdaBody(_) -> SyntaxHintNode.LambdaBody - | Expression.Tuple(_) -> SyntaxHintNode.Tuple - | Expression.Constant(constant) -> getConstKey constant - | Expression.List(_) - | Expression.Array(_) -> SyntaxHintNode.ArrayOrList - | Expression.If(_) -> SyntaxHintNode.If - | Expression.Else(_) -> SyntaxHintNode.Else - | Expression.Identifier(_) -> SyntaxHintNode.Identifier - | Expression.Null -> SyntaxHintNode.Null - | Expression.Wildcard -> SyntaxHintNode.Wildcard - | Expression.Variable(_) -> SyntaxHintNode.Variable - - let rec private getPatternKey = function - | Pattern.Cons(_) -> SyntaxHintNode.Cons - | Pattern.Or(_) -> SyntaxHintNode.Or - | Pattern.Wildcard -> SyntaxHintNode.Wildcard - | Pattern.Variable(_) -> SyntaxHintNode.Variable - | Pattern.Identifier(_) -> SyntaxHintNode.Identifier - | Pattern.Constant(constant) -> getConstKey constant - | Pattern.Parentheses(pattern) -> getPatternKey pattern - | Pattern.Tuple(_) -> SyntaxHintNode.Tuple - | Pattern.List(_) - | Pattern.Array(_) -> SyntaxHintNode.ArrayOrList - | Pattern.Null -> SyntaxHintNode.Null - - let rec private getKey = function - | HintExpr(expr) -> getExprKey expr - | HintPat(pattern) -> getPatternKey pattern - - let rec private getChildren = function - | HintExpr(Expression.Parentheses(expr)) -> getChildren <| HintExpr expr - | HintExpr(Expression.Lambda(args, LambdaBody(body))) -> - [ for LambdaArg(arg) in args -> HintExpr arg - yield HintExpr body ] - | HintExpr(Expression.LambdaArg(arg)) -> - [HintExpr arg] - | HintExpr(Expression.LambdaBody(body)) -> - [HintExpr body] - | HintExpr(Expression.InfixOperator(Expression.Identifier(["::"]) as ident, lhs, rhs)) -> - [HintExpr ident; HintExpr (Expression.Tuple([lhs; rhs]))] - | HintExpr(Expression.InfixOperator(ident, lhs, rhs)) -> - [HintExpr ident; HintExpr lhs; HintExpr rhs] - | HintExpr(Expression.PrefixOperator(ident, expr)) -> - [HintExpr ident; HintExpr expr] - | HintExpr(Expression.AddressOf(_, expr)) -> [HintExpr expr] - | HintExpr(Expression.FunctionApplication(exprs)) - | HintExpr(Expression.Tuple(exprs)) - | HintExpr(Expression.List(exprs)) - | HintExpr(Expression.Array(exprs)) -> exprs |> List.map HintExpr - | HintExpr(Expression.If(ifCond, bodyExpr, Some(elseExpr))) -> - [HintExpr ifCond; HintExpr bodyExpr; HintExpr elseExpr] - | HintExpr(Expression.If(ifCond, bodyExpr, None)) -> - [HintExpr ifCond; HintExpr bodyExpr] - | HintExpr(Expression.Else(x)) -> [HintExpr x] - | HintExpr(Expression.Identifier(_)) - | HintExpr(Expression.Constant(_)) - | HintExpr(Expression.Null) - | HintExpr(Expression.Wildcard) - | HintExpr(Expression.Variable(_)) -> [] - | HintPat(Pattern.Cons(lhs, rhs)) - | HintPat(Pattern.Or(lhs, rhs)) -> [HintPat lhs; HintPat rhs] - | HintPat(Pattern.Array(patterns)) - | HintPat(Pattern.List(patterns)) - | HintPat(Pattern.Tuple(patterns)) -> patterns |> List.map HintPat - | HintPat(Pattern.Parentheses(pattern)) -> [HintPat pattern] - | HintPat(Pattern.Variable(_)) - | HintPat(Pattern.Identifier(_)) - | HintPat(Pattern.Constant(_)) - | HintPat(Pattern.Wildcard) - | HintPat(Pattern.Null) -> [] - - let private getConstantHashCode = function - | Constant.Bool(x) -> hash x - | Constant.Byte(x) -> hash x - | Constant.Bytes(x) -> hash x - | Constant.Char(x) -> hash x - | Constant.Decimal(x) -> hash x - | Constant.Double(x) -> hash x - | Constant.Int16(x) -> hash x - | Constant.Int32(x) -> hash x - | Constant.Int64(x) -> hash x - | Constant.IntPtr(x) -> hash x - | Constant.SByte(x) -> hash x - | Constant.Single(x) -> hash x - | Constant.String(x) -> hash x - | Constant.UInt16(x) -> hash x - | Constant.UInt32(x) -> hash x - | Constant.UInt64(x) -> hash x - | Constant.UIntPtr(x) -> hash x - | Constant.UserNum(x, y) -> hash (x, y) - | _ -> 0 - - let private getIdentifierHashCode = function - | identifier when (List.isEmpty >> not) identifier -> - identifier - |> Seq.last - |> ExpressionUtilities.identAsCompiledOpName - |> hash - | _ -> 0 - - let rec private getHashCode node = - match node with - | HintExpr(Expression.Identifier(identifier)) - | HintPat(Pattern.Identifier(identifier)) -> getIdentifierHashCode identifier - | HintExpr(Expression.Constant(constant)) - | HintPat(Pattern.Constant(constant)) -> getConstantHashCode constant - | HintExpr(Expression.Parentheses(expr)) -> getHashCode <| HintExpr expr - | HintPat(Pattern.Parentheses(expr)) -> getHashCode <| HintPat expr - | _ -> 0 - - let private hintToList (hint:Hint) = - let nodes = Queue<_>() - - let rec depthFirstTraversal expr depth = - let children = getChildren expr - - nodes.Enqueue(expr, depth) - - for child in children do - depthFirstTraversal child (depth + 1) - - depthFirstTraversal hint.MatchedNode 0 - - (nodes |> Seq.toList, hint) - - type private HintList = (HintNode * int) list * Hint - - type private TransposedNode = - | HintNode of key:HintNode * depth:int * rest:HintList - | EndOfHint of Hint - - /// Gets the head of each given list - let private transposeHead hintLists = - let rec transposeHead builtList = function - | (((key, depth)::tail), hint)::rest -> - let restOfHintList = (tail, hint) - let next = HintNode(key, depth, restOfHintList)::builtList - transposeHead next rest - | ([], hint)::rest -> - let next = EndOfHint(hint)::builtList - transposeHead next rest - | [] -> builtList - - transposeHead [] hintLists - - let isAnyMatch = function - | ((SyntaxHintNode.Wildcard | SyntaxHintNode.Variable), _, _, _) -> true - | _ -> false - - let getHints items = items |> Seq.map (fun (_, _, _, hint) -> hint) |> Seq.toList - - let mergeHints hints = - let rec getEdges transposed = - let map = Dictionary<_, _>() - - transposed - |> List.choose (function - | HintNode(expr, depth, rest) -> Some(getKey expr, expr, depth, rest) - | EndOfHint(_) -> None) - |> List.filter (isAnyMatch >> not) - |> Seq.groupBy (fun (key, expr, _, _) -> Utilities.hash2 key (getHashCode expr)) - |> Seq.iter (fun (hashcode, items) -> map.Add(hashcode, mergeHints (getHints items))) - - let anyMatches = - transposed - |> List.choose (function - | HintNode(expr, depth, rest) -> - match (getKey expr, expr) with - | (SyntaxHintNode.Wildcard as key), HintExpr(Expression.Wildcard) - | (SyntaxHintNode.Wildcard as key), HintPat(Pattern.Wildcard) - | (SyntaxHintNode.Variable as key), HintExpr(Expression.Variable(_)) - | (SyntaxHintNode.Variable as key), HintPat(Pattern.Variable(_)) -> - Some(key, expr, depth, rest) - | _ -> None - | EndOfHint(_) -> None) - |> Seq.groupBy (fun (_, expr, _, _) -> expr) - |> Seq.choose - (fun (expr, items) -> - match expr with - | HintPat(Pattern.Wildcard) - | HintExpr(Expression.Wildcard) -> Some(None, mergeHints (getHints items)) - | HintPat(Pattern.Variable(var)) - | HintExpr(Expression.Variable(var)) -> Some(Some(var), mergeHints (getHints items)) - | _ -> None) - |> Seq.toList - - { Lookup = map - AnyMatch = anyMatches } - - and mergeHints hints = - let transposed = transposeHead hints - - let edges = getEdges transposed - - let matchedHints = - transposed - |> Seq.choose (function - | HintNode(_) -> None - | EndOfHint(hint) -> Some(hint)) - |> Seq.toList - - { Edges = edges - MatchedHint = matchedHints } - - let transposed = - hints |> List.map hintToList |> transposeHead - - getEdges transposed - let charListToString charList = - Seq.fold (fun x y -> x + y.ToString()) "" charList + Seq.fold (fun concatenatedString charElement -> concatenatedString + charElement.ToString()) String.Empty charList - let pischar chars : Parser = - satisfy (fun x -> List.exists ((=) x) chars) + let pischar chars : Parser = + satisfy (fun character -> List.exists ((=) character) chars) - let pnotchar chars : Parser = - satisfy (fun x -> not <| List.exists ((=) x) chars) + let pnotchar chars : Parser = + satisfy (fun character -> not <| List.exists ((=) character) chars) module Operators = let pfirstopchar: Parser = @@ -424,7 +45,7 @@ module HintParser = pidentstartchar .>>. many pidentchar |>> fun (start, rest) -> start::rest >>= fun ident -> - let identStr = System.String.Join("", ident) + let identStr = System.String.Join(String.Empty, ident) let isKeyword = List.exists ((=) identStr) FSharpKeywords.KeywordNames @@ -446,7 +67,7 @@ module HintParser = let private plongident: (CharStream -> Reply) = choice [ attempt (sepBy1 pident (skipChar '.')) - pident |>> fun x -> [x] ] + pident |>> fun identChars -> [identChars] ] let private pidentorop: (CharStream -> Reply) = choice @@ -467,7 +88,7 @@ module HintParser = match operator with | Some(operator) -> identifiers@[operator] | None -> identifiers) - attempt (pidentorop |>> fun x -> [x]) + attempt (pidentorop |>> fun identOrOpChars -> [identOrOpChars]) plongident ] |>> List.map charListToString @@ -479,21 +100,24 @@ module HintParser = char(System.Convert.ToInt32(dec, 10)) let private escapeMap = - [ ('"', '\"') - ('\\', '\\') - ('\'', '\'') - ('n', '\n') - ('t', '\t') - ('b', '\b') - ('r', '\r') - ('a', '\a') - ('f', '\f') - ('v', '\v') ] |> Map.ofList + Map.ofList + [ + ('"', '\"') + ('\\', '\\') + ('\'', '\'') + ('n', '\n') + ('t', '\t') + ('b', '\b') + ('r', '\r') + ('a', '\a') + ('f', '\f') + ('v', '\v') + ] let private pescapechar: Parser = skipChar '\\' >>. pischar ['"';'\\';'\'';'n';'t';'b';'r';'a';'f';'v'] - |>> fun x -> Map.find x escapeMap + |>> fun escapeChar -> Map.find escapeChar escapeMap let private pnonescapechars: Parser = skipChar '\\' @@ -508,29 +132,29 @@ module HintParser = let private punicodegraphshort: Parser = skipString "\\u" >>. many1 hex - >>= fun x -> - if x.Length <> 4 then + >>= fun unicodegraphChars -> + if unicodegraphChars.Length <> 4 then fail "Unicode graph short must be 4 hex characters long" else - preturn (x |> charListToString |> hexToCharacter) + preturn (unicodegraphChars |> charListToString |> hexToCharacter) let private punicodegraphlong: Parser = skipString "\\U" >>. many1 hex - >>= fun x -> - if x.Length <> 8 then + >>= fun unicodegraphChars -> + if unicodegraphChars.Length <> 8 then fail "Unicode graph long must be 8 hex characters long" else - preturn (x |> charListToString |> hexToCharacter) + preturn (unicodegraphChars |> charListToString |> hexToCharacter) let private ptrigraph: Parser = skipChar '\\' >>. many1 digit - >>= fun x -> - if x.Length <> 3 then + >>= fun trigraphChars -> + if trigraphChars.Length <> 3 then fail "Trigraph must be 3 characters long" else - preturn (x |> charListToString |> decimalToCharacter) + preturn (trigraphChars |> charListToString |> decimalToCharacter) let private pnewline: Parser = pchar '\n' <|> (skipChar '\r' >>. skipChar '\n' >>% '\n') @@ -628,19 +252,19 @@ module HintParser = skipChar '0' >>. (skipChar 'x' <|> skipChar 'X') >>. many1 hex - |>> fun x -> '0'::'x'::x + |>> fun hexChars -> '0'::'x'::hexChars let private poctalint: Parser = skipChar '0' >>. (skipChar 'o' <|> skipChar 'O') >>. many1 octal - |>> fun x -> '0'::'o'::x + |>> fun octalChars -> '0'::'o'::octalChars let private pbinaryint: Parser = skipChar '0' >>. (skipChar 'b' <|> skipChar 'B') >>. many1 (pchar '0' <|> pchar '1') - |>> fun x -> '0'::'b'::x + |>> fun binaryChars -> '0'::'b'::binaryChars let private pint: (CharStream -> Reply) = choice @@ -783,7 +407,7 @@ module HintParser = satisfy isLetter .>> notFollowedBy (satisfy isLetter) - let ptuple (pparser:Parser<'T, unit>) : Parser<'T list, unit> = + let ptuple (pparser:Parser<'Element, unit>) : Parser<'Element list, unit> = skipChar '(' >>. pparser .>> skipChar ',' @@ -791,14 +415,14 @@ module HintParser = .>> skipChar ')' |>> fun (func, rest) -> (func::rest) - let plist (pparser:Parser<'T, unit>): Parser<'T list, unit> = + let plist (pparser:Parser<'Element, unit>): Parser<'Element list, unit> = skipChar '[' >>. spaces >>. sepEndBy pparser (skipChar ';') .>> spaces .>> skipChar ']' - let parray (pparser:Parser<'T, unit>): Parser<'T list, unit> = + let parray (pparser:Parser<'Element, unit>): Parser<'Element list, unit> = skipString "[|" >>. spaces >>. sepEndBy pparser (skipChar ';') @@ -827,7 +451,7 @@ module HintParser = .>> skipString "then" .>>. pexpression .>>. opt (skipString "else" >>. pexpression) - |>> fun ((condition, expr), elseExpr) -> Expression.If(condition, expr, elseExpr |> Option.map Expression.Else) + |>> fun ((condition, expr), elseExpr) -> Expression.If(condition, expr, Option.map Expression.Else elseExpr) let plambda: Parser = let plambdastart: Parser = @@ -846,7 +470,7 @@ module HintParser = let! body = plambdaend return Expression.Lambda - (arguments |> List.map (Expression.LambdaArg >> LambdaArg), + (List.map (Expression.LambdaArg >> LambdaArg) arguments, LambdaBody(Expression.LambdaBody(body))) } @@ -902,9 +526,9 @@ module HintParser = let addInfixOperator prefix precedence associativity = let remainingOpChars = if prefix = "=" then - notFollowedBy (pstring "==>") |>> fun _ -> "" + notFollowedBy (pstring "==>") |>> fun _ -> String.Empty else if prefix = "|" then - notFollowedBy (pstring "]") |>> fun _ -> "" + notFollowedBy (pstring "]") |>> fun _ -> String.Empty else manySatisfy (isAnyOf Operators.opchars) @@ -922,20 +546,22 @@ module HintParser = else if prefix = "~" then manySatisfy ((=) '~') else - preturn "" + preturn String.Empty + + let checkPrefix remOpChars expr = + if prefix = "&" then Expression.AddressOf(true, expr) + else if prefix = "&&" then Expression.AddressOf(false, expr) + else if prefix = "!" || prefix = "~" then + let opIdent = Expression.Identifier [prefix + remOpChars] + Expression.PrefixOperator(opIdent, expr) + else + let opIdent = Expression.Identifier ["~" + prefix + remOpChars] + Expression.PrefixOperator(opIdent, expr) let prefixOp = PrefixOperator( prefix, remainingOpChars, precedence, true, (), - fun remOpChars expr -> - if prefix = "&" then Expression.AddressOf(true, expr) - else if prefix = "&&" then Expression.AddressOf(false, expr) - else if prefix = "!" || prefix = "~" then - let opIdent = Expression.Identifier [prefix + remOpChars] - Expression.PrefixOperator(opIdent, expr) - else - let opIdent = Expression.Identifier ["~" + prefix + remOpChars] - Expression.PrefixOperator(opIdent, expr)) + checkPrefix) opp.AddOperator(prefixOp) @@ -1024,7 +650,7 @@ module HintParser = let addInfixOperator operator precedence associativity = let remainingOpChars = if operator = "|" then - notFollowedBy (pstring "]") |>> fun _ -> "" + notFollowedBy (pstring "]") |>> fun _ -> String.Empty else manySatisfy (isAnyOf Operators.opchars) @@ -1062,24 +688,24 @@ module HintParser = let pexpressionbasedhint = parse { - let! m = Expressions.pexpression + let! expression = Expressions.pexpression do! phintcenter - let! s = psuggestion + let! suggestion = psuggestion - return { MatchedNode = HintExpr m; Suggestion = s } + return { MatchedNode = HintExpr expression; Suggestion = suggestion } } let ppatternbasedhint = parse { - let! m = Patterns.ppattern + let! pattern = Patterns.ppattern do! phintcenter - let! s = psuggestion + let! suggestion = psuggestion - return { MatchedNode = HintPat m; Suggestion = s } + return { MatchedNode = HintPat pattern; Suggestion = suggestion } } let phint: Parser = diff --git a/src/FSharpLint.Core/Framework/HintParserTypes.fs b/src/FSharpLint.Core/Framework/HintParserTypes.fs new file mode 100644 index 000000000..dee79cee3 --- /dev/null +++ b/src/FSharpLint.Core/Framework/HintParserTypes.fs @@ -0,0 +1,73 @@ +namespace FSharpLint.Framework + +module HintParserTypes = + + type Constant = + | Byte of byte + | Bytes of byte[] + | Char of char + | Decimal of decimal + | Double of double + | Int16 of int16 + | Int32 of int32 + | Int64 of int64 + | IntPtr of nativeint + | SByte of sbyte + | Single of single + | UInt16 of uint16 + | UInt32 of uint32 + | UInt64 of uint64 + | UIntPtr of unativeint + | UserNum of bigint * char + | String of string + | Unit + | Bool of bool + + [] + type Pattern = + | Cons of Pattern * Pattern + | Or of Pattern * Pattern + | Wildcard + | Variable of char + | Identifier of string list + | Constant of Constant + | Parentheses of Pattern + | Tuple of Pattern list + | List of Pattern list + | Array of Pattern list + | Null + + [] + type Expression = + | FunctionApplication of Expression list + | InfixOperator of operatorIdentifier:Expression * Expression * Expression + | PrefixOperator of operatorIdentifier:Expression * Expression + | AddressOf of singleAmpersand:bool * Expression + | Wildcard + | Variable of char + | Identifier of string list + | Constant of Constant + | Parentheses of Expression + | Lambda of LambdaArg list * LambdaBody + | LambdaBody of Expression + | LambdaArg of Expression + | Tuple of Expression list + | List of Expression list + | Array of Expression list + | If of cond:Expression * body:Expression * ``else``:Expression option + | Else of Expression + | Null + and LambdaArg = LambdaArg of Expression + and LambdaBody = LambdaBody of Expression + + type HintNode = + | HintPat of Pattern + | HintExpr of Expression + + type Suggestion = + | Expr of Expression + | Message of string + + type Hint = + { MatchedNode:HintNode + Suggestion:Suggestion } diff --git a/src/FSharpLint.Core/Framework/HintParserUtilities.fs b/src/FSharpLint.Core/Framework/HintParserUtilities.fs new file mode 100644 index 000000000..7749eed7b --- /dev/null +++ b/src/FSharpLint.Core/Framework/HintParserUtilities.fs @@ -0,0 +1,325 @@ +namespace FSharpLint.Framework + +open HintParserTypes + +/// Provides a way of creating a single list from any number of hint ASTs. +/// Means we can simply iterate over a single list for each node in the F# tree +/// when matching hints rather than check each hint AST for each node. +module MergeSyntaxTrees = + + open System.Collections.Generic + + type SyntaxHintNode = + | Identifier = 1uy + | Null = 2uy + | Expression = 3uy + | FuncApp = 4uy + | Unit = 5uy + | AddressOf = 6uy + + | If = 10uy + | Else = 11uy + + | Lambda = 20uy + | LambdaArg = 21uy + | LambdaBody = 22uy + + | ArrayOrList = 30uy + | Tuple = 31uy + + | Variable = 40uy + | Wildcard = 41uy + + | ConstantBool = 51uy + | ConstantByte = 52uy + | ConstantChar = 53uy + | ConstantDecimal = 54uy + | ConstantDouble = 55uy + | ConstantInt16 = 56uy + | ConstantInt32 = 57uy + | ConstantInt64 = 58uy + | ConstantIntPtr = 59uy + | ConstantSByte = 60uy + | ConstantSingle = 61uy + | ConstantString = 62uy + | ConstantUInt16 = 63uy + | ConstantUInt32 = 64uy + | ConstantUInt64 = 65uy + | ConstantUIntPtr = 66uy + | ConstantBytes = 67uy + | ConstantUserNum = 68uy + + | Cons = 101uy + | And = 102uy + | Or = 103uy + + [] + type Node = + { Edges: Edges; MatchedHint: Hint list } + + and [] Edges = + { + Lookup: Dictionary + AnyMatch: (char option * Node) list + } + + override this.Equals(other) = + match other with + | :? Edges as rhs -> + let getList dict = + Seq.toList dict + |> List.map (fun (dictItems: KeyValuePair<_, _>) -> (dictItems.Key, dictItems.Value)) + + this.AnyMatch = rhs.AnyMatch + && this.Lookup.Count = rhs.Lookup.Count + && getList this.Lookup = getList rhs.Lookup + | _ -> false + + override this.GetHashCode() = hash (this.AnyMatch, hash this.Lookup) + + static member Empty = + { + Lookup = Dictionary<_, _>() + AnyMatch = List.Empty + } + + let private getConstKey = + function + | Constant.Unit -> SyntaxHintNode.Unit + | Constant.Bool(_) -> SyntaxHintNode.ConstantBool + | Constant.Byte(_) -> SyntaxHintNode.ConstantByte + | Constant.Bytes(_) -> SyntaxHintNode.ConstantBytes + | Constant.Char(_) -> SyntaxHintNode.ConstantChar + | Constant.Decimal(_) -> SyntaxHintNode.ConstantDecimal + | Constant.Double(_) -> SyntaxHintNode.ConstantDouble + | Constant.Int16(_) -> SyntaxHintNode.ConstantInt16 + | Constant.Int32(_) -> SyntaxHintNode.ConstantInt32 + | Constant.Int64(_) -> SyntaxHintNode.ConstantInt64 + | Constant.IntPtr(_) -> SyntaxHintNode.ConstantIntPtr + | Constant.SByte(_) -> SyntaxHintNode.ConstantSByte + | Constant.Single(_) -> SyntaxHintNode.ConstantSingle + | Constant.String(_) -> SyntaxHintNode.ConstantString + | Constant.UInt16(_) -> SyntaxHintNode.ConstantUInt16 + | Constant.UInt32(_) -> SyntaxHintNode.ConstantUInt32 + | Constant.UInt64(_) -> SyntaxHintNode.ConstantUInt64 + | Constant.UIntPtr(_) -> SyntaxHintNode.ConstantUIntPtr + | Constant.UserNum(_) -> SyntaxHintNode.ConstantUserNum + + let rec private getExprKey = + function + | Expression.FunctionApplication(_) + | Expression.InfixOperator(_) + | Expression.PrefixOperator(_) -> SyntaxHintNode.FuncApp + | Expression.AddressOf(_) -> SyntaxHintNode.AddressOf + | Expression.Parentheses(expr) -> getExprKey expr + | Expression.Lambda(_) -> SyntaxHintNode.Lambda + | Expression.LambdaArg(_) -> SyntaxHintNode.LambdaArg + | Expression.LambdaBody(_) -> SyntaxHintNode.LambdaBody + | Expression.Tuple(_) -> SyntaxHintNode.Tuple + | Expression.Constant(constant) -> getConstKey constant + | Expression.List(_) + | Expression.Array(_) -> SyntaxHintNode.ArrayOrList + | Expression.If(_) -> SyntaxHintNode.If + | Expression.Else(_) -> SyntaxHintNode.Else + | Expression.Identifier(_) -> SyntaxHintNode.Identifier + | Expression.Null -> SyntaxHintNode.Null + | Expression.Wildcard -> SyntaxHintNode.Wildcard + | Expression.Variable(_) -> SyntaxHintNode.Variable + + let rec private getPatternKey = + function + | Pattern.Cons(_) -> SyntaxHintNode.Cons + | Pattern.Or(_) -> SyntaxHintNode.Or + | Pattern.Wildcard -> SyntaxHintNode.Wildcard + | Pattern.Variable(_) -> SyntaxHintNode.Variable + | Pattern.Identifier(_) -> SyntaxHintNode.Identifier + | Pattern.Constant(constant) -> getConstKey constant + | Pattern.Parentheses(pattern) -> getPatternKey pattern + | Pattern.Tuple(_) -> SyntaxHintNode.Tuple + | Pattern.List(_) + | Pattern.Array(_) -> SyntaxHintNode.ArrayOrList + | Pattern.Null -> SyntaxHintNode.Null + + let rec private getKey = + function + | HintExpr(expr) -> getExprKey expr + | HintPat(pattern) -> getPatternKey pattern + + let rec private getChildren = + function + | HintExpr(Expression.Parentheses(expr)) -> getChildren <| HintExpr expr + | HintExpr(Expression.Lambda(args, LambdaBody(body))) -> + [ for LambdaArg(arg) in args -> HintExpr arg + yield HintExpr body ] + | HintExpr(Expression.LambdaArg(arg)) -> [ HintExpr arg ] + | HintExpr(Expression.LambdaBody(body)) -> [ HintExpr body ] + | HintExpr(Expression.InfixOperator(Expression.Identifier([ "::" ]) as ident, lhs, rhs)) -> + [ HintExpr ident; HintExpr(Expression.Tuple([ lhs; rhs ])) ] + | HintExpr(Expression.InfixOperator(ident, lhs, rhs)) -> [ HintExpr ident; HintExpr lhs; HintExpr rhs ] + | HintExpr(Expression.PrefixOperator(ident, expr)) -> [ HintExpr ident; HintExpr expr ] + | HintExpr(Expression.AddressOf(_, expr)) -> [ HintExpr expr ] + | HintExpr(Expression.FunctionApplication(exprs)) + | HintExpr(Expression.Tuple(exprs)) + | HintExpr(Expression.List(exprs)) + | HintExpr(Expression.Array(exprs)) -> List.map HintExpr exprs + | HintExpr(Expression.If(ifCond, bodyExpr, Some(elseExpr))) -> + [ HintExpr ifCond; HintExpr bodyExpr; HintExpr elseExpr ] + | HintExpr(Expression.If(ifCond, bodyExpr, None)) -> [ HintExpr ifCond; HintExpr bodyExpr ] + | HintExpr(Expression.Else(expression)) -> [ HintExpr expression ] + | HintExpr(Expression.Identifier(_)) + | HintExpr(Expression.Constant(_)) + | HintExpr(Expression.Null) + | HintExpr(Expression.Wildcard) + | HintExpr(Expression.Variable(_)) -> List.Empty + | HintPat(Pattern.Cons(lhs, rhs)) + | HintPat(Pattern.Or(lhs, rhs)) -> [ HintPat lhs; HintPat rhs ] + | HintPat(Pattern.Array(patterns)) + | HintPat(Pattern.List(patterns)) + | HintPat(Pattern.Tuple(patterns)) -> List.map HintPat patterns + | HintPat(Pattern.Parentheses(pattern)) -> [ HintPat pattern ] + | HintPat(Pattern.Variable(_)) + | HintPat(Pattern.Identifier(_)) + | HintPat(Pattern.Constant(_)) + | HintPat(Pattern.Wildcard) + | HintPat(Pattern.Null) -> List.Empty + + let private getConstantHashCode = + function + | Constant.Bool value -> hash value + | Constant.Byte value -> hash value + | Constant.Bytes value -> hash value + | Constant.Char value -> hash value + | Constant.Decimal value -> hash value + | Constant.Double value -> hash value + | Constant.Int16 value -> hash value + | Constant.Int32 value -> hash value + | Constant.Int64 value -> hash value + | Constant.IntPtr value -> hash value + | Constant.SByte value -> hash value + | Constant.Single value -> hash value + | Constant.String value -> hash value + | Constant.UInt16 value -> hash value + | Constant.UInt32 value -> hash value + | Constant.UInt64 value -> hash value + | Constant.UIntPtr value -> hash value + | Constant.UserNum(intValue, charValue) -> hash (intValue, charValue) + | _ -> 0 + + let private getIdentifierHashCode = + function + | identifier when (List.isEmpty >> not) identifier -> + match (Seq.tryLast identifier) with + | Some value -> value |> ExpressionUtilities.identAsCompiledOpName |> hash + | None -> failwith "There's no last element in identifier." + | _ -> 0 + + [] + let rec private getHashCode node = + match node with + | HintExpr(Expression.Identifier(identifier)) + | HintPat(Pattern.Identifier(identifier)) -> getIdentifierHashCode identifier + | HintExpr(Expression.Constant(constant)) + | HintPat(Pattern.Constant(constant)) -> getConstantHashCode constant + | HintExpr(Expression.Parentheses(expr)) -> getHashCode <| HintExpr expr + | HintPat(Pattern.Parentheses(expr)) -> getHashCode <| HintPat expr + | _ -> 0 + + let private hintToList (hint: Hint) = + let nodes = Queue<_>() + + let rec depthFirstTraversal expr depth = + let children = getChildren expr + + nodes.Enqueue(expr, depth) + + for child in children do + depthFirstTraversal child (depth + 1) + + depthFirstTraversal hint.MatchedNode 0 + + (Seq.toList nodes, hint) + + type private HintList = (HintNode * int) list * Hint + + type private TransposedNode = + | HintNode of key: HintNode * depth: int * rest: HintList + | EndOfHint of Hint + + /// Gets the head of each given list + let private transposeHead hintLists = + let rec transposeHead builtList = + function + | (((key, depth) :: tail), hint) :: rest -> + let restOfHintList = (tail, hint) + let next = HintNode(key, depth, restOfHintList) :: builtList + transposeHead next rest + | ([], hint) :: rest -> + let next = EndOfHint(hint) :: builtList + transposeHead next rest + | [] -> builtList + + transposeHead List.Empty hintLists + + let isAnyMatch = + function + | ((SyntaxHintNode.Wildcard | SyntaxHintNode.Variable), _, _, _) -> true + | _ -> false + + let getHints items = + items |> Seq.map (fun (_, _, _, hint) -> hint) |> Seq.toList + + let mergeHints hints = + let rec getEdges transposed = + let map = Dictionary<_, _>() + + transposed + |> List.choose (function + | HintNode(expr, depth, rest) -> Some(getKey expr, expr, depth, rest) + | EndOfHint(_) -> None) + |> List.filter (isAnyMatch >> not) + |> Seq.groupBy (fun (key, expr, _, _) -> Utilities.hash2 key (getHashCode expr)) + |> Seq.iter (fun (hashcode, items) -> map.Add(hashcode, mergeHints (getHints items))) + + let anyMatches = + transposed + |> List.choose (function + | HintNode(expr, depth, rest) -> + match (getKey expr, expr) with + | (SyntaxHintNode.Wildcard as key), HintExpr(Expression.Wildcard) + | (SyntaxHintNode.Wildcard as key), HintPat(Pattern.Wildcard) + | (SyntaxHintNode.Variable as key), HintExpr(Expression.Variable(_)) + | (SyntaxHintNode.Variable as key), HintPat(Pattern.Variable(_)) -> Some(key, expr, depth, rest) + | _ -> None + | EndOfHint(_) -> None) + |> Seq.groupBy (fun (_, expr, _, _) -> expr) + |> Seq.choose (fun (expr, items) -> + match expr with + | HintPat(Pattern.Wildcard) + | HintExpr(Expression.Wildcard) -> Some(None, mergeHints (getHints items)) + | HintPat(Pattern.Variable(var)) + | HintExpr(Expression.Variable(var)) -> Some(Some(var), mergeHints (getHints items)) + | _ -> None) + |> Seq.toList + + { Lookup = map; AnyMatch = anyMatches } + + and mergeHints hints = + let transposed = transposeHead hints + + let edges = getEdges transposed + + let matchedHints = + transposed + |> Seq.choose (function + | HintNode(_) -> None + | EndOfHint(hint) -> Some(hint)) + |> Seq.toList + + { + Edges = edges + MatchedHint = matchedHints + } + + let transposed = hints |> List.map hintToList |> transposeHead + + getEdges transposed diff --git a/src/FSharpLint.Core/Framework/ParseFile.fs b/src/FSharpLint.Core/Framework/ParseFile.fs index d2183c0ae..ac4208a36 100644 --- a/src/FSharpLint.Core/Framework/ParseFile.fs +++ b/src/FSharpLint.Core/Framework/ParseFile.fs @@ -33,9 +33,9 @@ module ParseFile = | AbortedTypeCheck [] - type ParseFileResult<'T> = + type ParseFileResult<'Content> = | Failed of ParseFileFailure - | Success of 'T + | Success of 'Content let private parse file source (checker:FSharpChecker, options) = let sourceText = SourceText.ofString source @@ -45,10 +45,13 @@ module ParseFile = match checkFileAnswer with | FSharpCheckFileAnswer.Succeeded(typeCheckResults) -> - { Text = source - Ast = parseResults.ParseTree - TypeCheckResults = Some(typeCheckResults) - File = file } |> Success + Success + { + Text = source + Ast = parseResults.ParseTree + TypeCheckResults = Some(typeCheckResults) + File = file + } | FSharpCheckFileAnswer.Aborted -> Failed(AbortedTypeCheck) let getProjectOptionsFromScript (checker:FSharpChecker) file (source:string) = diff --git a/src/FSharpLint.Core/Framework/Resources.fs b/src/FSharpLint.Core/Framework/Resources.fs index 1ca40f977..a25ec81dc 100644 --- a/src/FSharpLint.Core/Framework/Resources.fs +++ b/src/FSharpLint.Core/Framework/Resources.fs @@ -7,7 +7,7 @@ open System.Resources /// Used to retrieve multi-lingual strings inside of the app. type Resources() = static let resourceName = Assembly.GetExecutingAssembly().GetManifestResourceNames() - |> Seq.find (fun n -> n.EndsWith("Text.resources", System.StringComparison.Ordinal)) + |> Seq.find (fun resource -> resource.EndsWith("Text.resources", System.StringComparison.Ordinal)) static let resourceManager = ResourceManager(resourceName.Replace(".resources", System.String.Empty), typeof.GetTypeInfo().Assembly) diff --git a/src/FSharpLint.Core/Framework/Suppression.fs b/src/FSharpLint.Core/Framework/Suppression.fs index af48797e1..22fe49dfa 100644 --- a/src/FSharpLint.Core/Framework/Suppression.fs +++ b/src/FSharpLint.Core/Framework/Suppression.fs @@ -29,12 +29,9 @@ let private extractRules (rules:Set) (str:string) = /// Parses a given file to find lines containing rule suppressions. let parseSuppressionInfo (rules:Set) (lines:string list) = - let rules = rules |> Set.map (fun rule -> rule.ToLowerInvariant()) + let rules = Set.map (fun (rule: String) -> rule.ToLowerInvariant()) rules - lines - |> List.mapi (fun lineNum line -> (lineNum + 1, line)) - |> List.filter (fun (_, line) -> line.Contains("fsharplint:")) - |> List.choose (fun (lineNum, line) -> + let choose lineNum line = let matched = Regex.Match (line, ".*fsharplint:([a-z\-]+)\s*(.*)$") if matched.Success then let suppressionTarget = @@ -48,7 +45,12 @@ let parseSuppressionInfo (rules:Set) (lines:string list) = | "disable-line" -> Some (lineNum, DisableLine suppressionTarget) | "disable-next-line" -> Some (lineNum + 1, DisableLine suppressionTarget) | _ -> None - else None) + else None + + lines + |> List.mapi (fun lineNum line -> (lineNum + 1, line)) + |> List.filter (fun (_, line) -> line.Contains("fsharplint:")) + |> List.choose (fun (lineNum, line) -> choose lineNum line) |> List.groupBy (fun (line, _) -> line) |> List.map (fun (line, suppressions) -> { Line = line @@ -61,22 +63,25 @@ let isSuppressed (rule:String) (line:int) (lineSuppressions:LineSuppression list false else let rule = rule.ToLowerInvariant() + + + let fold (disabledRules:Set) (lineSuppression:LineSuppression) = + let innerFold (disabledRules:Set) suppression = + match suppression with + | Enable(rules) -> + Set.difference disabledRules rules + | Disable(rules) -> + Set.union disabledRules rules + | DisableLine(rules) -> + if line = lineSuppression.Line then + Set.union disabledRules rules + else + disabledRules + List.fold innerFold disabledRules lineSuppression.Suppressions let disabledRules = lineSuppressions |> List.takeWhile (fun lineSupression -> lineSupression.Line <= line) - |> List.fold (fun (disabledRules:Set) (lineSuppression:LineSuppression) -> - lineSuppression.Suppressions |> List.fold (fun (disabledRules:Set) suppression -> - match suppression with - | Enable(rules) -> - Set.difference disabledRules rules - | Disable(rules) -> - Set.union disabledRules rules - | DisableLine(rules) -> - if line = lineSuppression.Line then - Set.union disabledRules rules - else - disabledRules - ) disabledRules) Set.empty + |> List.fold fold Set.empty disabledRules.Contains(rule) diff --git a/src/FSharpLint.Core/Framework/Utilities.fs b/src/FSharpLint.Core/Framework/Utilities.fs index 4ba2e6a5b..e79cc4f76 100644 --- a/src/FSharpLint.Core/Framework/Utilities.fs +++ b/src/FSharpLint.Core/Framework/Utilities.fs @@ -9,7 +9,9 @@ module Utilities = current <- current * 31 + hash one current * 31 + hash two - let () x y = System.IO.Path.Combine(x, y) + let () path1 path2 = System.IO.Path.Combine(path1, path2) + + let returnEmptyArray () = Array.empty module Dictionary = @@ -17,7 +19,7 @@ module Dictionary = let addOrUpdate key value (dict:Dictionary<'Key,'Value>) = if dict.ContainsKey(key) then - dict.Remove(key) |> ignore + dict.Remove(key) |> ignore dict.Add(key, value) @@ -37,12 +39,12 @@ module ExpressionUtilities = let getSymbolFromIdent (checkFile:FSharpCheckFileResults option) expr = match (checkFile, expr) with | Some(checkFile), Identifier(ident, range) -> - let identNames = ident |> List.map (fun x -> x.idText) + let identNames = List.map (fun (identifier: Ident) -> identifier.idText) ident checkFile.GetSymbolUseAtLocation( range.StartLine, range.EndColumn, - "", + String.Empty, identNames) | _ -> None @@ -60,8 +62,8 @@ module ExpressionUtilities = /// Extracts an expression from parentheses e.g. ((x + 4)) -> x + 4 let rec removeParens = function - | SynExpr.Paren(x, _, _, _) -> removeParens x - | x -> x + | SynExpr.Paren(expr, _, _, _) -> removeParens expr + | expression -> expression /// Finds index of a given (line number, column) position in a string. let findPos (pos:pos) (str:string) = @@ -73,7 +75,7 @@ module ExpressionUtilities = else None findLineStart pos.Line 1 0 - |> Option.map (fun x -> x + pos.Column) + |> Option.map (fun lineStart -> lineStart + pos.Column) /// Converts a LongIdent to a String. let longIdentToString (lid:LongIdent) = @@ -81,7 +83,7 @@ module ExpressionUtilities = /// Converts a LongIdentWithDots to a String. let longIdentWithDotsToString (lidwd: SynLongIdent) = - lidwd.LongIdent |> longIdentToString + longIdentToString lidwd.LongIdent /// Tries to find the source code within a given range. let tryFindTextOfRange (range:Range) (text:string) = @@ -94,7 +96,7 @@ module ExpressionUtilities = | _ -> None let getLeadingSpaces (range:Range) (text:string) = - let range = Range.mkRange "" (Position.mkPos range.StartLine 0) range.End + let range = Range.mkRange String.Empty (Position.mkPos range.StartLine 0) range.End tryFindTextOfRange range text |> Option.map (fun text -> text.ToCharArray() @@ -106,30 +108,32 @@ module ExpressionUtilities = let synTypeToString (text:string) = function | SynType.Tuple _ as synType -> tryFindTextOfRange synType.Range text - |> Option.map (fun x -> $"({x})") + |> Option.map (fun extractedText -> $"({extractedText})") | other -> tryFindTextOfRange other.Range text /// Converts a list of type args to its string representation. let typeArgsToString (text:string) (typeArgs:SynType list) = - let typeStrings = typeArgs |> List.choose (synTypeToString text) + let typeStrings = List.choose (synTypeToString text) typeArgs if typeStrings.Length = typeArgs.Length then typeStrings |> String.concat "," |> Some else None /// Counts the number of comment lines preceding the given range of text. let countPrecedingCommentLines (text:string) (startPos:pos) (endPos:pos) = - let range = Range.mkRange "" startPos endPos + let range = Range.mkRange String.Empty startPos endPos - tryFindTextOfRange range text - |> Option.map (fun precedingText -> + let map (precedingText: string) = let lines = precedingText.Split '\n' |> Array.rev |> Array.tail lines |> Array.takeWhile (fun line -> line.TrimStart().StartsWith("//")) - |> Array.length) + |> Array.length + + tryFindTextOfRange range text + |> Option.map map |> Option.defaultValue 0 let rangeContainsOtherRange (containingRange:Range) (range:Range) = @@ -148,15 +152,15 @@ module String = | null -> None | line -> Some line - let rec iterateLines currentLine i = + let rec iterateLines currentLine index = match currentLine with | Some line -> let nextLine = readLine () let isLastLine = Option.isNone nextLine - lines.Add(line, i, isLastLine) + lines.Add(line, index, isLastLine) - iterateLines nextLine (i + 1) + iterateLines nextLine (index + 1) | None -> () iterateLines (readLine ()) 0 diff --git a/src/FSharpLint.Core/Prelude.fs b/src/FSharpLint.Core/Prelude.fs index 5ff3b0c20..98ba09480 100644 --- a/src/FSharpLint.Core/Prelude.fs +++ b/src/FSharpLint.Core/Prelude.fs @@ -4,11 +4,11 @@ namespace FSharpLint.Core module Prelude = module Async = - let combine f x y = async { - let! x = x - let! y = y - return f x y } + let combine operation job1 job2 = async { + let! firstOperation = job1 + let! secondOperation = job2 + return operation firstOperation secondOperation } - let map f xAsync = async { - let! x = xAsync - return f x } \ No newline at end of file + let map operation job = async { + let! jobResult = job + return operation jobResult } diff --git a/src/FSharpLint.Core/Rules/Conventions/AsyncExceptionWithoutReturn.fs b/src/FSharpLint.Core/Rules/Conventions/AsyncExceptionWithoutReturn.fs index 521515201..978ed38ef 100644 --- a/src/FSharpLint.Core/Rules/Conventions/AsyncExceptionWithoutReturn.fs +++ b/src/FSharpLint.Core/Rules/Conventions/AsyncExceptionWithoutReturn.fs @@ -6,68 +6,88 @@ open FSharpLint.Framework open FSharpLint.Framework.Suggestion open FSharpLint.Framework.Ast open FSharpLint.Framework.Rules +open FSharpLint.Framework.Utilities -let rec checkExpression (expression: SynExpr) (range: range) = - match expression with - | SynExpr.Sequential (_, _, firstExpression, secondExpression, _, _) -> - let result = checkExpression firstExpression range - Array.append result (checkExpression secondExpression secondExpression.Range) - | SynExpr.Paren (innerExpression, _, _, range) -> checkExpression innerExpression range - | SynExpr.While (_, _, innerExpression, range) -> checkExpression innerExpression range - | SynExpr.For (_, _, _, _, _, _, _, innerExpression, range) -> checkExpression innerExpression range - | SynExpr.ForEach (_, _, _, _, _, _, innerExpression, range) -> checkExpression innerExpression range - | SynExpr.Match (_, _, clauses, range, _) -> - clauses - |> List.map (fun (SynMatchClause (_, _, clause, range, _, _)) -> checkExpression clause range) - |> List.toArray - |> Array.concat - | SynExpr.Do (innerExpression, range) -> checkExpression innerExpression range - | SynExpr.TryWith (tryExpression, withCases, tryRange, _, _, _) -> - withCases - |> List.map (fun (SynMatchClause (_, _, withCase, withRange, _, _)) -> checkExpression withCase withRange) - |> List.toArray - |> Array.concat - |> Array.append (checkExpression tryExpression tryRange) - | SynExpr.TryFinally (tryExpression, finallyExpr, range, _, _, _) -> - checkExpression finallyExpr range - |> Array.append (checkExpression tryExpression range) - | SynExpr.IfThenElse (_, thenExpr, elseExpr, _, _, range, _) -> - let checkThen = checkExpression thenExpr range - - match elseExpr with - | Some elseExpression -> - checkThen - |> Array.append (checkExpression elseExpression range) - | None -> checkThen - | SynExpr.App (_, _, SynExpr.Ident failwithId, _, _) when - failwithId.idText = "failwith" - || failwithId.idText = "failwithf" - || failwithId.idText = "raise" - -> - { Range = range - Message = Resources.GetString "RulesAsyncExceptionWithoutReturn" - SuggestedFix = None - TypeChecks = List.Empty } - |> Array.singleton - | SynExpr.App (_, _, funcExpr, _, range) -> - checkExpression funcExpr range - | SynExpr.LetOrUse (_, _, _, body, range, _) -> - checkExpression body range - | _ -> Array.empty - +[] +let rec checkExpression (expression: SynExpr) (range: range) (continuation: unit -> array) = + Array.append + (match expression with + | SynExpr.Sequential (_, _, firstExpression, secondExpression, _, _) -> + checkExpression + 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)) + checkMultipleExpressions subExpressions returnEmptyArray + | SynExpr.Do (innerExpression, range) -> checkExpression innerExpression range 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, _) -> + checkExpression + thenExpr + range + (fun () -> + match elseExpr with + | Some elseExpression -> + checkExpression elseExpression range returnEmptyArray + | None -> Array.empty) + + | SynExpr.App (_, _, SynExpr.Ident failwithId, _, _) when + failwithId.idText = "failwith" + || failwithId.idText = "failwithf" + || failwithId.idText = "raise" + -> + Array.singleton + { + Range = range + Message = Resources.GetString "RulesAsyncExceptionWithoutReturn" + SuggestedFix = None + TypeChecks = List.Empty + } + | SynExpr.App (_, _, funcExpr, _, range) -> + checkExpression funcExpr range returnEmptyArray + | SynExpr.LetOrUse (_, _, _, body, range, _) -> + checkExpression body range returnEmptyArray + | _ -> Array.empty) + (continuation ()) +and [] checkMultipleExpressions (expressions: list) (continuation: unit -> array) = + match expressions with + | (expression, range) :: tail -> + checkExpression + expression + range + (fun () -> + checkMultipleExpressions + tail + continuation) + | [] -> + continuation () let runner args = match args.AstNode with | AstNode.Expression ( SynExpr.App (_, _, (SynExpr.Ident compExprName), (SynExpr.ComputationExpr (_, innerExpression, _)), range) - ) when compExprName.idText = "async" -> checkExpression innerExpression range + ) when compExprName.idText = "async" -> checkExpression innerExpression range returnEmptyArray | _ -> Array.empty let rule = - { Name = "AsyncExceptionWithoutReturn" - Identifier = Identifiers.AsyncExceptionWithoutReturn - RuleConfig = - { AstNodeRuleConfig.Runner = runner - Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "AsyncExceptionWithoutReturn" + Identifier = Identifiers.AsyncExceptionWithoutReturn + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/AvoidSinglePipeOperator.fs b/src/FSharpLint.Core/Rules/Conventions/AvoidSinglePipeOperator.fs index 66ffaf440..956722d78 100644 --- a/src/FSharpLint.Core/Rules/Conventions/AvoidSinglePipeOperator.fs +++ b/src/FSharpLint.Core/Rules/Conventions/AvoidSinglePipeOperator.fs @@ -10,18 +10,19 @@ open FSharpLint.Framework.Rules let runner (args: AstNodeRuleParams) = let errors range suggestedFix = - { - Range = range - Message = String.Format(Resources.GetString ("RulesAvoidSinglePipeOperator")) - SuggestedFix = suggestedFix - TypeChecks = List.Empty - } |> Array.singleton + Array.singleton + { + Range = range + Message = String.Format(Resources.GetString ("RulesAvoidSinglePipeOperator")) + SuggestedFix = suggestedFix + TypeChecks = List.Empty + } let rec checkExpr (expr: SynExpr) (outerArgExpr: SynExpr) (range: FSharp.Compiler.Text.range) (parentList: AstNode list): WarningDetails array = let checkParentPiped (expr: AstNode) = match expr with | AstNode.Expression(SynExpr.App(_exprAtomicFlag, _isInfix, funcExpr, _argExpr, _range)) -> - checkExpr funcExpr outerArgExpr range [] |> Seq.isEmpty + checkExpr funcExpr outerArgExpr range List.Empty |> Seq.isEmpty | _ -> false match expr with @@ -45,7 +46,7 @@ let runner (args: AstNodeRuleParams) = let suggestedFix = lazy( let maybeFuncText = ExpressionUtilities.tryFindTextOfRange outerArgExpr.Range args.FileContent let maybeArgText = ExpressionUtilities.tryFindTextOfRange argExpr.Range args.FileContent - match maybeFuncText, maybeArgText with + match (maybeFuncText, maybeArgText) with | Some(funcText), Some(argText) -> let replacementText = sprintf "%s %s" funcText argText Some { FromText=args.FileContent; FromRange=range; ToText=replacementText } @@ -74,7 +75,13 @@ let runner (args: AstNodeRuleParams) = let rule = - { Name = "AvoidSinglePipeOperator" - Identifier = Identifiers.AvoidSinglePipeOperator - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "AvoidSinglePipeOperator" + Identifier = Identifiers.AvoidSinglePipeOperator + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/AvoidTooShortNames.fs b/src/FSharpLint.Core/Rules/Conventions/AvoidTooShortNames.fs index b54fb0ad5..5c7f04171 100644 --- a/src/FSharpLint.Core/Rules/Conventions/AvoidTooShortNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/AvoidTooShortNames.fs @@ -59,7 +59,7 @@ let private getIdentifiers (args:AstNodeRuleParams) = | AstNode.Expression(SynExpr.Lambda(_, _, lambdaArgs, _, _, _, _)) -> let lambdaIdent = FunctionReimplementation.getLambdaParamIdent lambdaArgs match lambdaIdent with - | Some ident -> (ident, ident.idText, None) |> Array.singleton + | Some ident -> Array.singleton (ident, ident.idText, None) | None -> Array.empty | AstNode.Match(SynMatchClause(namePattern, _, _, _, _, _)) -> getParameterWithBelowMinimumLength [namePattern] @@ -75,10 +75,10 @@ let private getIdentifiers (args:AstNodeRuleParams) = result | _ -> Array.empty | SynPat.Named(SynIdent(identifier, _), _, _, _) when isIdentifierTooShort identifier.idText -> - (identifier, identifier.idText, None) |> Array.singleton + Array.singleton (identifier, identifier.idText, None) | _ -> Array.empty | AstNode.Field(SynField(_, _, Some identifier, _, _, _, _, _, _)) when isIdentifierTooShort identifier.idText -> - (identifier, identifier.idText, None) |> Array.singleton + Array.singleton (identifier, identifier.idText, None) | AstNode.TypeDefinition(SynTypeDefn(componentInfo, _typeDef, _, _, _, _)) -> let checkTypes types = seq { @@ -93,19 +93,23 @@ let private getIdentifiers (args:AstNodeRuleParams) = | Some types -> checkTypes types.TyparDecls |> Array.ofSeq | None -> Array.empty | AstNode.Type(SynType.Var(SynTypar(id, _, _), _)) when isIdentifierTooShort id.idText -> - (id, id.idText, None) |> Array.singleton + Array.singleton (id, id.idText, None) | _ -> Array.empty let runner (args:AstNodeRuleParams) = getIdentifiers args |> Array.collect (fun (identifier, idText, typeCheck) -> let suggestions = checkIdentifier identifier idText - suggestions |> Array.map (fun suggestion -> { suggestion with TypeChecks = Option.toList typeCheck })) + Array.map (fun suggestion -> { suggestion with TypeChecks = Option.toList typeCheck }) suggestions) let rule = - { Name = "AvoidTooShortNames" - Identifier = Identifiers.AvoidTooShortNames - RuleConfig = - { AstNodeRuleConfig.Runner = runner - Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "AvoidTooShortNames" + Identifier = Identifiers.AvoidTooShortNames + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/BindingHelper.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/BindingHelper.fs index 77125bb3f..dd9ec7ced 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/BindingHelper.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/BindingHelper.fs @@ -4,9 +4,9 @@ open FSharpLint.Framework open FSharp.Compiler.Syntax open FSharpLint.Framework.Ast -let isLetBinding i (syntaxArray:AbstractSyntaxArray.Node []) = - if i > 0 then - match syntaxArray.[syntaxArray.[i].ParentIndex].Actual with +let isLetBinding index (syntaxArray:AbstractSyntaxArray.Node []) = + if index > 0 then + match syntaxArray.[syntaxArray.[index].ParentIndex].Actual with | AstNode.ModuleDeclaration(SynModuleDecl.Let(_)) | AstNode.Expression(SynExpr.LetOrUse(_, false, _, _, _, _)) -> true | _ -> false diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/FavourAsKeyword.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/FavourAsKeyword.fs index 1d3d3cdbc..81e78db32 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/FavourAsKeyword.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/FavourAsKeyword.fs @@ -21,7 +21,7 @@ let private checkForNamedPatternEqualsConstant (args:AstNodeRuleParams) pattern | SynExpr.App(_, _, ExpressionUtilities.Identifier([opIdent], _), SynExpr.Ident(ident), _) when opIdent.idText = "op_Equality" && Option.contains ident.idText patternIdent -> - let fromRange = Range.mkRange "" range.Start constRange.End + let fromRange = Range.mkRange String.Empty range.Start constRange.End let suggestedFix = ExpressionUtilities.tryFindTextOfRange fromRange args.FileContent @@ -29,17 +29,15 @@ let private checkForNamedPatternEqualsConstant (args:AstNodeRuleParams) pattern ExpressionUtilities.tryFindTextOfRange constRange args.FileContent |> Option.bind (fun constText -> - - lazy (Some { FromText = text; FromRange = fromRange; ToText = $"{constText} as {ident.idText}"}) - |> Some + Some (lazy (Some { FromText = text; FromRange = fromRange; ToText = $"{constText} as {ident.idText}"})) ) - ) - { Range = fromRange - Message = Resources.GetString("RulesFavourAsKeyword") - SuggestedFix = suggestedFix - TypeChecks = [] } |> Array.singleton + Array.singleton + { Range = fromRange + Message = Resources.GetString("RulesFavourAsKeyword") + SuggestedFix = suggestedFix + TypeChecks = List.Empty } | _ -> Array.empty | _ -> Array.empty @@ -52,7 +50,7 @@ let private runner (args:AstNodeRuleParams) = | _ -> Array.empty let rule = - { Name = "FavourAsKeyword" - Identifier = Identifiers.FavourAsKeyword - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { Name = "FavourAsKeyword" + Identifier = Identifiers.FavourAsKeyword + RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/FavourIgnoreOverLetWild.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/FavourIgnoreOverLetWild.fs index eccb524cc..4a40f86fa 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/FavourIgnoreOverLetWild.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/FavourIgnoreOverLetWild.fs @@ -16,12 +16,13 @@ let private checkForBindingToAWildcard pattern range fileContent (expr: SynExpr) match ExpressionUtilities.tryFindTextOfRange expr.Range fileContent with | Some exprText -> if findWildAndIgnoreParens pattern then - { Range = range - Message = Resources.GetString("RulesFavourIgnoreOverLetWildError") - SuggestedFix = Some (lazy (Some({ FromRange = letBindingRange - FromText = fileContent - ToText = sprintf "(%s) |> ignore" exprText }))) - TypeChecks = [] } |> Array.singleton + Array.singleton + { Range = range + Message = Resources.GetString("RulesFavourIgnoreOverLetWildError") + SuggestedFix = Some (lazy (Some({ FromRange = letBindingRange + FromText = fileContent + ToText = sprintf "(%s) |> ignore" exprText }))) + TypeChecks = List.Empty } else Array.empty | None -> Array.empty @@ -44,7 +45,13 @@ let private runner (args:AstNodeRuleParams) = /// Checks if any code uses 'let _ = ...' and suggests to use the ignore function. let rule = - { Name = "FavourIgnoreOverLetWild" - Identifier = Identifiers.FavourIgnoreOverLetWild - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "FavourIgnoreOverLetWild" + Identifier = Identifiers.FavourIgnoreOverLetWild + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/FavourTypedIgnore.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/FavourTypedIgnore.fs index c7706ad5a..ce5bec3cf 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/FavourTypedIgnore.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/FavourTypedIgnore.fs @@ -19,10 +19,12 @@ let private runner (args: AstNodeRuleParams) = FromRange = range ToText = identifier })) - { Range = range - Message = String.Format(Resources.GetString "RulesFavourTypedIgnore", identifier) - SuggestedFix = Some suggestedFix - TypeChecks = [] } + { + Range = range + Message = String.Format(Resources.GetString "RulesFavourTypedIgnore", identifier) + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty + } let isTyped expression identifier range text = match expression with @@ -50,9 +52,13 @@ let private runner (args: AstNodeRuleParams) = /// Checks if any code uses untyped ignore let rule = - { Name = "FavourTypedIgnore" - Identifier = Identifiers.FavourTypedIgnore - RuleConfig = - { AstNodeRuleConfig.Runner = runner - Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "FavourTypedIgnore" + Identifier = Identifiers.FavourTypedIgnore + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/TupleOfWildcards.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/TupleOfWildcards.fs index d3a136dbb..87821f70f 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/TupleOfWildcards.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/TupleOfWildcards.fs @@ -14,7 +14,7 @@ let private checkTupleOfWildcards fileContents pattern identifier identifierRang | _ -> false let constructorString numberOfWildcards = - let constructorName = identifier |> String.concat "." + let constructorName = String.concat "." identifier let arguments = Array.create numberOfWildcards "_" |> String.concat ", " if numberOfWildcards = 1 then $"%s{constructorName} _" @@ -29,7 +29,13 @@ let private checkTupleOfWildcards fileContents pattern identifier identifierRang let error = String.Format(errorFormat, refactorFrom, refactorTo) let suggestedFix = lazy( Some { SuggestedFix.FromRange = identifierRange; FromText = fileContents; ToText = refactorTo }) - { Range = range; Message = error; SuggestedFix = Some suggestedFix; TypeChecks = [] } |> Array.singleton + Array.singleton + { + Range = range + Message = error + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty + } | _ -> Array.empty let private isTupleMemberArgs breadcrumbs tupleRange = @@ -52,14 +58,20 @@ let private runner (args:AstNodeRuleParams) = | AstNode.Pattern(SynPat.LongIdent(identifier, _, _, SynArgPats.Pats([SynPat.Paren(SynPat.Tuple(_, _, _, range) as pattern, _)]), _, identRange)) -> let breadcrumbs = args.GetParents 2 if (not << isTupleMemberArgs breadcrumbs) range then - let identifier = identifier.LongIdent |> List.map (fun x -> x.idText) + let identifier = identifier.LongIdent |> List.map (fun ident -> ident.idText) checkTupleOfWildcards args.FileContent pattern identifier identRange else Array.empty | _ -> Array.empty let rule = - { Name = "TupleOfWildcards" - Identifier = Identifiers.TupleOfWildcards - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "TupleOfWildcards" + Identifier = Identifiers.TupleOfWildcards + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/UselessBinding.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/UselessBinding.fs index 408de053c..380457eb2 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/UselessBinding.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/UselessBinding.fs @@ -19,13 +19,13 @@ let private checkForUselessBinding (checkInfo:FSharpCheckFileResults option) pat let isNotMutable (symbol:FSharpSymbolUse) = match symbol.Symbol with - | :? FSharpMemberOrFunctionOrValue as v -> not v.IsMutable + | :? FSharpMemberOrFunctionOrValue as fsharpElement -> not fsharpElement.IsMutable | _ -> true let checkNotMutable (ident:Ident) = fun () -> let symbol = checkInfo.GetSymbolUseAtLocation( - ident.idRange.StartLine, ident.idRange.EndColumn, "", [ident.idText]) + ident.idRange.StartLine, ident.idRange.EndColumn, String.Empty, [ident.idText]) match symbol with | Some(symbol) -> isNotMutable symbol @@ -51,9 +51,9 @@ let private runner (args:AstNodeRuleParams) = let maybeSuggestedFix = match args.GetParents(args.NodeIndex) with | AstNode.ModuleDeclaration(SynModuleDecl.Let(_, _, range)) :: _ -> - Some({ FromRange = range; FromText = "let"; ToText = "" }) + Some({ FromRange = range; FromText = "let"; ToText = String.Empty }) | AstNode.Expression(SynExpr.LetOrUse(_, false, _, _, range, _)) :: _ -> - Some({ FromRange = range; FromText = "use"; ToText = "" }) + Some({ FromRange = range; FromText = "use"; ToText = String.Empty }) | _ -> None match args.AstNode with | AstNode.Binding(SynBinding(_, _, _, isMutable, _, _, _, pattern, _, expr, range, _, _)) @@ -63,8 +63,13 @@ let private runner (args:AstNodeRuleParams) = Array.empty let rule = - { Name = "UselessBinding" - Identifier = Identifiers.UselessBinding - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule - + AstNodeRule + { + Name = "UselessBinding" + Identifier = Identifiers.UselessBinding + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/WildcardNamedWithAsPattern.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/WildcardNamedWithAsPattern.fs index c811a4f28..db0a4b854 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/WildcardNamedWithAsPattern.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/WildcardNamedWithAsPattern.fs @@ -14,10 +14,11 @@ let private checkForWildcardNamedWithAsPattern fileContents pattern = let suggestedFix = lazy( Some { FromRange = range; FromText = fileContents; ToText = identifier.idText }) - { Range = range - Message = Resources.GetString("RulesWildcardNamedWithAsPattern") - SuggestedFix = Some suggestedFix - TypeChecks = [] } |> Array.singleton + Array.singleton + { Range = range + Message = Resources.GetString("RulesWildcardNamedWithAsPattern") + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty } | _ -> Array.empty let private runner (args:AstNodeRuleParams) = @@ -27,8 +28,13 @@ let private runner (args:AstNodeRuleParams) = | _ -> Array.empty let rule = - { Name = "WildcardNamedWithAsPattern" - Identifier = Identifiers.WildcardNamedWithAsPattern - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule - + AstNodeRule + { + Name = "WildcardNamedWithAsPattern" + Identifier = Identifiers.WildcardNamedWithAsPattern + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/CyclomaticComplexity.fs b/src/FSharpLint.Core/Rules/Conventions/CyclomaticComplexity.fs index 99c307280..917797e79 100644 --- a/src/FSharpLint.Core/Rules/Conventions/CyclomaticComplexity.fs +++ b/src/FSharpLint.Core/Rules/Conventions/CyclomaticComplexity.fs @@ -32,13 +32,13 @@ type private BindingScopeComparer() = member this.Compare(left, right) = let leftStart = left.Binding.RangeOfBindingWithoutRhs.Start let rightStart = right.Binding.RangeOfBindingWithoutRhs.Start - let leftTuple : ValueTuple = leftStart.Line, leftStart.Column, left.Complexity - let rightTuple : ValueTuple = rightStart.Line, rightStart.Column, right.Complexity + let leftTuple : ValueTuple = (leftStart.Line, leftStart.Column, left.Complexity) + let rightTuple : ValueTuple = (rightStart.Line, rightStart.Column, right.Complexity) leftTuple.CompareTo rightTuple /// A two-tiered stack-like structure for containing BindingScopes. type private BindingStack(maxComplexity: int) = - let mutable tier1 = [] + let mutable tier1 = List.Empty let mutable tier2 = SortedSet(BindingScopeComparer()) member this.Push (args:AstNodeRuleParams) (bs: BindingScope) = @@ -46,20 +46,20 @@ type private BindingStack(maxComplexity: int) = let isChildOfCurrent = if List.isEmpty tier1 then false else - args.GetParents args.NodeIndex |> List.tryFind (fun x -> Object.ReferenceEquals(tier1.Head.Node, x)) |> Option.isSome + args.GetParents args.NodeIndex |> List.tryFind (fun astNode -> Object.ReferenceEquals(tier1.Head.Node, astNode)) |> Option.isSome // if the node is not a child and the stack isn't empty, we're finished with the current head of tier1, so move it from tier1 to tier2 if not isChildOfCurrent && not (List.isEmpty tier1) then let popped = tier1.Head tier1 <- tier1.Tail if popped.Complexity > maxComplexity then - tier2.Add popped |> ignore + tier2.Add popped |> ignore // finally, push the item on to the stack tier1 <- bs::tier1 member this.IncrComplexityOfCurrentScope incr = - let h = tier1.Head - let complexity = h.Complexity + incr - tier1 <- {h with Complexity = complexity}::tier1.Tail + let head = tier1.Head + let complexity = head.Complexity + incr + tier1 <- {head with Complexity = complexity}::tier1.Tail interface IEnumerable with member this.GetEnumerator() = @@ -76,7 +76,7 @@ type private BindingStack(maxComplexity: int) = /// Clears the stack. member this.Clear() = - tier1 <- [] + tier1 <- List.Empty tier2.Clear() /// A stack to track the current cyclomatic complexity of a binding scope. @@ -131,13 +131,12 @@ let private countBooleanOperators expression = | SynExpr.MatchBang(_, _, clauses, _, _) | SynExpr.MatchLambda(_, _, clauses, _, _) | SynExpr.Match(_, _, clauses, _, _) -> - clauses |> List.sumBy (fun c -> - match c with - | SynMatchClause(_, whenExprOpt, _, _, _, _) -> - match whenExprOpt with - | Some whenExpr -> - countOperators 0 whenExpr - | None -> 0) + List.sumBy (fun matchClause -> + match matchClause with + | SynMatchClause(_, whenExprOpt, _, _, _, _) -> + match whenExprOpt with + | Some whenExpr -> countOperators 0 whenExpr + | None -> 0) clauses | _ -> count // kick off the calculation @@ -181,7 +180,7 @@ let runner (config:Config) (args:AstNodeRuleParams) : WarningDetails[] = | SynExpr.MatchBang(_, _, clauses, _, _) | SynExpr.MatchLambda(_, _, clauses, _, _) | SynExpr.Match(_, _, clauses, _, _) -> - let numCases = clauses |> List.sumBy countCasesInMatchClause // determine the number of cases in the match expression + let numCases = List.sumBy countCasesInMatchClause clauses // determine the number of cases in the match expression bindingStack.IncrComplexityOfCurrentScope (numCases + countBooleanOperators expression) // include the number of boolean operators in any when expressions, if applicable | _ -> () | _ -> () @@ -191,15 +190,15 @@ let runner (config:Config) (args:AstNodeRuleParams) : WarningDetails[] = let fromStack = bindingStack |> Seq.sortBy (fun scope -> // sort by order of start position, for reporting let pos = scope.Binding.RangeOfBindingWithRhs.Start - pos.Column, pos.Line) + (pos.Column, pos.Line)) |> Seq.map (fun scope -> // transform into WarningDetails let errMsg = String.Format(Resources.GetString("RulesCyclomaticComplexityError"), scope.Complexity, config.MaxComplexity) - { Range = scope.Binding.RangeOfBindingWithRhs; Message = errMsg; SuggestedFix = None; TypeChecks = [] }) + { Range = scope.Binding.RangeOfBindingWithRhs; Message = errMsg; SuggestedFix = None; TypeChecks = List.Empty }) |> Seq.toList let ret = match warningDetails with - | Some x -> x::fromStack + | Some warning -> warning::fromStack | None -> fromStack - ret |> List.toArray + List.toArray ret else Array.empty @@ -211,8 +210,13 @@ let cleanup () = /// Generator function for a rule instance. let rule config = - { Name = "CyclomaticComplexity" - Identifier = Identifiers.CyclomaticComplexity - RuleConfig = { AstNodeRuleConfig.Runner = runner config - Cleanup = cleanup } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "CyclomaticComplexity" + Identifier = Identifiers.CyclomaticComplexity + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = cleanup + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/EnsureTailCallDiagnosticsInRecursiveFunctions.fs b/src/FSharpLint.Core/Rules/Conventions/EnsureTailCallDiagnosticsInRecursiveFunctions.fs index 4b6e832d1..e6c25343a 100644 --- a/src/FSharpLint.Core/Rules/Conventions/EnsureTailCallDiagnosticsInRecursiveFunctions.fs +++ b/src/FSharpLint.Core/Rules/Conventions/EnsureTailCallDiagnosticsInRecursiveFunctions.fs @@ -19,33 +19,32 @@ let private emitWarning (func: UnneededRecKeyword.RecursiveFunctionInfo) = TypeChecks = list.Empty } let runner (args: AstNodeRuleParams) = - match args.AstNode, args.CheckInfo with + match (args.AstNode, args.CheckInfo) with | UnneededRecKeyword.RecursiveFunctions(funcs), Some checkInfo -> - funcs - |> List.choose - (fun functionInfo -> - if UnneededRecKeyword.functionIsCalledInOneOf checkInfo functionInfo funcs then - let hasTailCallAttribute = - functionInfo.Attributes - |> List.collect (fun attrs -> attrs.Attributes) - |> List.exists - (fun attr -> - match attr.TypeName with - | SynLongIdent([ident], _, _) -> - ident.idText = "TailCall" || ident.idText = "TailCallAttribute" - | _ -> false) - if hasTailCallAttribute then - None - else - emitWarning functionInfo |> Some - else + let processFunction functionInfo = + if UnneededRecKeyword.functionIsCalledInOneOf checkInfo functionInfo funcs then + let hasTailCallAttribute = + functionInfo.Attributes + |> List.collect (fun attrs -> attrs.Attributes) + |> List.exists + (fun attr -> + match attr.TypeName with + | SynLongIdent([ident], _, _) -> + ident.idText = "TailCall" || ident.idText = "TailCallAttribute" + | _ -> false) + if hasTailCallAttribute then None - ) + else + emitWarning functionInfo |> Some + else + None + funcs + |> List.choose processFunction |> List.toArray | _ -> Array.empty let rule = - { Name = "EnsureTailCallDiagnosticsInRecursiveFunctions" - Identifier = Identifiers.EnsureTailCallDiagnosticsInRecursiveFunctions - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { Name = "EnsureTailCallDiagnosticsInRecursiveFunctions" + Identifier = Identifiers.EnsureTailCallDiagnosticsInRecursiveFunctions + RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } diff --git a/src/FSharpLint.Core/Rules/Conventions/FavourConsistentThis.fs b/src/FSharpLint.Core/Rules/Conventions/FavourConsistentThis.fs index 8dfaf0f3a..4a4d800a1 100644 --- a/src/FSharpLint.Core/Rules/Conventions/FavourConsistentThis.fs +++ b/src/FSharpLint.Core/Rules/Conventions/FavourConsistentThis.fs @@ -26,11 +26,11 @@ let runner (config: Config) args = | head::_ when isNotConsistent head.idText symbol -> let suggestedFix = lazy(Some({ FromRange = head.idRange; FromText = head.idText; ToText = symbol })) let error = - { Range = range - Message = String.Format(Resources.GetString "RulesFavourConsistentThis", config.Symbol) - SuggestedFix = Some suggestedFix - TypeChecks = List.Empty } - |> Array.singleton + Array.singleton + { Range = range + Message = String.Format(Resources.GetString "RulesFavourConsistentThis", config.Symbol) + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty } error | _ -> Array.empty else @@ -39,7 +39,13 @@ let runner (config: Config) args = | _ -> Array.empty let rule config = - { Name = "FavourConsistentThis" - Identifier = Identifiers.FavourConsistentThis - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "FavourConsistentThis" + Identifier = Identifiers.FavourConsistentThis + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/FavourNonMutablePropertyInitialization.fs b/src/FSharpLint.Core/Rules/Conventions/FavourNonMutablePropertyInitialization.fs index 81e67ea84..cf1986321 100644 --- a/src/FSharpLint.Core/Rules/Conventions/FavourNonMutablePropertyInitialization.fs +++ b/src/FSharpLint.Core/Rules/Conventions/FavourNonMutablePropertyInitialization.fs @@ -5,6 +5,7 @@ open FSharpLint.Framework.Suggestion open FSharp.Compiler.Syntax open FSharpLint.Framework.Ast open FSharpLint.Framework.Rules +open FSharpLint.Framework.Utilities open System let private getWarningDetails (ident: Ident) = @@ -32,61 +33,72 @@ let private extraInstanceMethod (app:SynExpr) (instanceMethodCalls: List | _ -> instanceMethodCalls | _ -> instanceMethodCalls +[] let rec private extraFromBindings (bindings: List) (classInstances: List) = match bindings with | SynBinding(_, _, _, _, _, _, _, SynPat.Named(SynIdent(ident, _), _, _, _), _, _expression, _, _, _)::rest -> extraFromBindings rest (ident.idText::classInstances) | _ -> classInstances -let rec private processLetBinding (instanceNames: Set) (body: SynExpr) : array = - match body with - | SynExpr.LongIdentSet(SynLongIdent(identifiers, _, _), _, _) -> - match identifiers with - | [instanceIdent; propertyIdent] when Set.contains instanceIdent.idText instanceNames -> - getWarningDetails propertyIdent - | _ -> Array.empty - | SynExpr.Sequential(_, _, expr1, expr2, _, _) -> - let instanceNames = - Set.difference +[] +let rec private processLetBinding (instanceNames: Set) (body: SynExpr) (continuation: unit -> array) : array = + Array.append + (match body with + | SynExpr.LongIdentSet(SynLongIdent(identifiers, _, _), _, _) -> + match identifiers with + | [instanceIdent; propertyIdent] when Set.contains instanceIdent.idText instanceNames -> + getWarningDetails propertyIdent + | _ -> Array.empty + | SynExpr.Sequential(_, _, expr1, expr2, _, _) -> + let instanceNames = + Set.difference + instanceNames + (extraInstanceMethod expr1 List.empty |> Set.ofList) + processLetBinding instanceNames - (extraInstanceMethod expr1 List.empty |> Set.ofList) - Array.append - (processLetBinding instanceNames expr1) - (processLetBinding instanceNames expr2) - | _ -> [||] + expr1 + (fun () -> processLetBinding instanceNames expr2 returnEmptyArray) + | _ -> Array.empty) + (continuation()) -and processExpression (expression: SynExpr) : array = - match expression with - | SynExpr.LetOrUse(_, _, bindings, body, _, _) -> - let instanceNames = extraFromBindings bindings [] |> Set.ofList - processLetBinding instanceNames body - | SynExpr.Sequential(_, _, expr1, expr2, _, _) -> - Array.append - (processExpression expr1) - (processExpression expr2) - | _ -> Array.empty +and [] processExpression (expression: SynExpr) (continuation: unit -> array) : array = + Array.append + (match expression with + | SynExpr.LetOrUse(_, _, bindings, body, _, _) -> + let instanceNames = extraFromBindings bindings List.Empty |> Set.ofList + processLetBinding instanceNames body returnEmptyArray + | SynExpr.Sequential(_, _, expr1, expr2, _, _) -> + processExpression expr1 (fun () -> processExpression expr2 returnEmptyArray) + | _ -> Array.empty) + (continuation()) let runner args = match args.AstNode with | Binding(SynBinding(_, _, _, _, _, _, _, _, _, SynExpr.LetOrUse(_, _, bindings, body, _, _), _, _, _)) -> - let instanceNames = extraFromBindings bindings [] |> Set.ofList - processLetBinding instanceNames body + let instanceNames = extraFromBindings bindings List.Empty |> Set.ofList + processLetBinding instanceNames body returnEmptyArray | Match(SynMatchClause(_, _, expr, _, _, _)) -> - processExpression expr + processExpression expr returnEmptyArray | Lambda(lambda, _) -> - processExpression lambda.Body + processExpression lambda.Body returnEmptyArray | Expression(SynExpr.TryWith(tryExpr, _, _, _, _, _)) -> - processExpression tryExpr + processExpression tryExpr returnEmptyArray | Expression(SynExpr.TryFinally(tryExpr, finallyExpr, _, _, _, _)) -> Array.append - (processExpression tryExpr) - (processExpression finallyExpr) + (processExpression tryExpr returnEmptyArray) + (processExpression finallyExpr returnEmptyArray) | Expression(SynExpr.ComputationExpr(_, expr, _)) -> - processExpression expr + processExpression expr returnEmptyArray | _ -> Array.empty let rule = - { Name = "FavourNonMutablePropertyInitialization" - Identifier = Identifiers.FavourNonMutablePropertyInitialization - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "FavourNonMutablePropertyInitialization" + Identifier = Identifiers.FavourNonMutablePropertyInitialization + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/FavourReRaise.fs b/src/FSharpLint.Core/Rules/Conventions/FavourReRaise.fs index b0274c300..b1dfc2d2d 100644 --- a/src/FSharpLint.Core/Rules/Conventions/FavourReRaise.fs +++ b/src/FSharpLint.Core/Rules/Conventions/FavourReRaise.fs @@ -9,11 +9,11 @@ open FSharpLint.Framework.Rules let private runner (args: AstNodeRuleParams) = let generateError suggestedFix range = - { Range = range - Message = Resources.GetString "RulesFavourReRaise" - SuggestedFix = Some suggestedFix - TypeChecks = List.empty } - |> Array.singleton + Array.singleton + { Range = range + Message = Resources.GetString "RulesFavourReRaise" + SuggestedFix = Some suggestedFix + TypeChecks = List.empty } let rec checkExpr (expr) maybeIdent = match expr with @@ -44,9 +44,13 @@ let private runner (args: AstNodeRuleParams) = | _ -> Array.empty let rule = - { Name = "FavourReRaise" - Identifier = Identifiers.FavourReRaise - RuleConfig = - { AstNodeRuleConfig.Runner = runner - Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "FavourReRaise" + Identifier = Identifiers.FavourReRaise + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/FavourStaticEmptyFields.fs b/src/FSharpLint.Core/Rules/Conventions/FavourStaticEmptyFields.fs index aeb3c20dc..0ba6790fa 100644 --- a/src/FSharpLint.Core/Rules/Conventions/FavourStaticEmptyFields.fs +++ b/src/FSharpLint.Core/Rules/Conventions/FavourStaticEmptyFields.fs @@ -22,7 +22,7 @@ let private getStaticEmptyErrorMessage (range:FSharp.Compiler.Text.Range) (empt let formatError errorName = Resources.GetString errorName - errorMessageKey |> formatError + formatError errorMessageKey let private generateError (fileContents: string) (range:FSharp.Compiler.Text.Range) (emptyLiteralType: EmptyLiteralType) = let suggestedFix = lazy( @@ -32,11 +32,11 @@ let private generateError (fileContents: string) (range:FSharp.Compiler.Text.Ran | EmptyListLiteral -> "List.Empty" | EmptyArrayLiteral -> "Array.empty" Some({ FromRange = range; FromText = fileContents; ToText = replacementText })) - { Range = range - Message = getStaticEmptyErrorMessage range emptyLiteralType - SuggestedFix = Some suggestedFix - TypeChecks = List.Empty } - |> Array.singleton + Array.singleton + { Range = range + Message = getStaticEmptyErrorMessage range emptyLiteralType + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty } let private runner (args: AstNodeRuleParams) = match args.AstNode with @@ -47,9 +47,8 @@ let private runner (args: AstNodeRuleParams) = if isArray then EmptyArrayLiteral else EmptyListLiteral generateError args.FileContent range emptyLiteralType | AstNode.Expression(SynExpr.Record(_, _, synExprRecordField, _)) -> - synExprRecordField - |> List.map (fun field -> - match field with + let mapping = + function | SynExprRecordField(_, _, expr, _) -> match expr with | Some(SynExpr.ArrayOrList(isArray, [], range)) -> @@ -59,15 +58,21 @@ let private runner (args: AstNodeRuleParams) = generateError args.FileContent range EmptyStringLiteral | Some(SynExpr.App(_, _, _, SynExpr.Const (SynConst.String ("", _, range), _), _)) -> generateError args.FileContent range EmptyStringLiteral - | _ -> Array.empty) + | _ -> Array.empty + synExprRecordField + |> List.map mapping |> Array.concat | _ -> Array.empty let rule = - { Name = "FavourStaticEmptyFields" - Identifier = Identifiers.FavourStaticEmptyFields - RuleConfig = - { AstNodeRuleConfig.Runner = runner - Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "FavourStaticEmptyFields" + Identifier = Identifiers.FavourStaticEmptyFields + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/FunctionReimplementation/CanBeReplacedWithComposition.fs b/src/FSharpLint.Core/Rules/Conventions/FunctionReimplementation/CanBeReplacedWithComposition.fs index 2b110ea66..3be3ec9bb 100644 --- a/src/FSharpLint.Core/Rules/Conventions/FunctionReimplementation/CanBeReplacedWithComposition.fs +++ b/src/FSharpLint.Core/Rules/Conventions/FunctionReimplementation/CanBeReplacedWithComposition.fs @@ -39,21 +39,21 @@ let private validateLambdaCannotBeReplacedWithComposition fileContents _ lambda if lastArgument.idText = lambdaArgument.idText then funcString :: calledFunctionIdents else - [] + List.Empty | SynExpr.App(_, false, _, _, _) as nextFunction -> lambdaArgumentIsLastApplicationInFunctionCalls nextFunction lambdaArgument (funcString :: calledFunctionIdents) - | _ -> [] - | _ -> [] - | _ -> [] + | _ -> List.Empty + | _ -> List.Empty + | _ -> List.Empty match lambda.Arguments with | [singleParameter] -> match Helper.FunctionReimplementation.getLambdaParamIdent singleParameter with | Some paramIdent -> - match lambdaArgumentIsLastApplicationInFunctionCalls expression paramIdent [] with + match lambdaArgumentIsLastApplicationInFunctionCalls expression paramIdent List.Empty with | [] -> None | funcStrings -> Some funcStrings | None -> None @@ -65,16 +65,23 @@ let private validateLambdaCannotBeReplacedWithComposition fileContents _ lambda let suggestedFix = lazy( Some { FromRange = range; FromText = fileContents; ToText = String.Join(" >> ", funcStrings) }) - { Range = range - Message = Resources.GetString("RulesCanBeReplacedWithComposition") - SuggestedFix = Some suggestedFix - TypeChecks = [] } |> Array.singleton + Array.singleton + { Range = range + Message = Resources.GetString("RulesCanBeReplacedWithComposition") + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty } let runner (args:AstNodeRuleParams) = Helper.FunctionReimplementation.checkLambda args (validateLambdaCannotBeReplacedWithComposition args.FileContent) let rule = - { Name = "CanBeReplacedWithComposition" - Identifier = Identifiers.CanBeReplacedWithComposition - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "CanBeReplacedWithComposition" + Identifier = Identifiers.CanBeReplacedWithComposition + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/FunctionReimplementation/ReimplementsFunction.fs b/src/FSharpLint.Core/Rules/Conventions/FunctionReimplementation/ReimplementsFunction.fs index e204d209a..60bb0c259 100644 --- a/src/FSharpLint.Core/Rules/Conventions/FunctionReimplementation/ReimplementsFunction.fs +++ b/src/FSharpLint.Core/Rules/Conventions/FunctionReimplementation/ReimplementsFunction.fs @@ -24,21 +24,23 @@ let private validateLambdaIsNotPointless (text:string) lambda range = let generateError (identifier:LongIdent) = let identifier = identifier - |> List.map (fun x -> - if PrettyNaming.IsLogicalOpName x.idText then - PrettyNaming.ConvertValLogicalNameToDisplayNameCore x.idText |> sprintf "( %s )" + |> List.map (fun ident -> + if PrettyNaming.IsLogicalOpName ident.idText then + PrettyNaming.ConvertValLogicalNameToDisplayNameCore ident.idText |> sprintf "( %s )" else - x.idText) + ident.idText) |> String.concat "." let suggestedFix = lazy( ExpressionUtilities.tryFindTextOfRange range text |> Option.map (fun fromText -> { FromText = fromText; FromRange = range; ToText = identifier })) - { Range = range - Message = String.Format(Resources.GetString("RulesReimplementsFunction"), identifier) - SuggestedFix = Some suggestedFix - TypeChecks = [] } + { + Range = range + Message = String.Format(Resources.GetString("RulesReimplementsFunction"), identifier) + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty + } let argumentsAsIdentifiers = lambda.Arguments @@ -53,7 +55,13 @@ let runner (args:AstNodeRuleParams) = Helper.FunctionReimplementation.checkLambda args validateLambdaIsNotPointless let rule = - { Name = "ReimplementsFunction" - Identifier = Identifiers.ReimplementsFunction - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "ReimplementsFunction" + Identifier = Identifiers.ReimplementsFunction + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/ActivePatternNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/ActivePatternNames.fs index c53bde0c7..703b07647 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/ActivePatternNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/ActivePatternNames.fs @@ -11,13 +11,13 @@ let private getValueOrFunctionIdents _ pattern = | SynPat.LongIdent(longIdent, _, _, _, _, _) -> match List.tryLast longIdent.LongIdent with | Some ident when isActivePattern ident -> - ident |> Array.singleton + Array.singleton ident | _ -> Array.empty | SynPat.Named(SynIdent(ident, _), _, _, _) | SynPat.OptionalVal(ident, _) -> if isActivePattern ident then - ident |> Array.singleton + Array.singleton ident else Array.empty | _ -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/EnumCasesNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/EnumCasesNames.fs index b0d5c260a..ab043c5d0 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/EnumCasesNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/EnumCasesNames.fs @@ -8,7 +8,7 @@ open FSharpLint.Rules.Helper.Naming let private getIdentifiers (args:AstNodeRuleParams) = match args.AstNode with | AstNode.EnumCase(SynEnumCase(_, SynIdent(identifier, _), _, _, _, _)) -> - (identifier, identifier.idText, None) |> Array.singleton + Array.singleton (identifier, identifier.idText, None) | _ -> Array.empty let rule config = diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/ExceptionNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/ExceptionNames.fs index 3c7531ef0..8c892152c 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/ExceptionNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/ExceptionNames.fs @@ -10,7 +10,7 @@ let private getIdentifiers (args:AstNodeRuleParams) = | AstNode.ExceptionRepresentation(SynExceptionDefnRepr.SynExceptionDefnRepr(_, unionCase, _, _, _, _)) -> match unionCase with | SynUnionCase(_, SynIdent(identifier, _), _, _, _, _, _) -> - (identifier, identifier.idText, None) |> Array.singleton + Array.singleton (identifier, identifier.idText, None) | _ -> Array.empty let rule config = diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/GenericTypesNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/GenericTypesNames.fs index 12c6fa5ab..1ee9e79d3 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/GenericTypesNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/GenericTypesNames.fs @@ -20,7 +20,7 @@ let private getIdentifiers (args: AstNodeRuleParams) = | Some types -> checkTypes types.TyparDecls |> Array.ofSeq | None -> Array.empty | AstNode.Type(SynType.Var(SynTypar(id, _, _), _)) -> - (id, id.idText, None) |> Array.singleton + Array.singleton (id, id.idText, None) | _ -> Array.empty let rule config = diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/InterfaceNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/InterfaceNames.fs index 831b4eff2..c1c8c60d8 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/InterfaceNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/InterfaceNames.fs @@ -19,7 +19,7 @@ let private getIdentifiers (args:AstNodeRuleParams) = match List.tryLast identifier with | Some typeIdentifier -> if not (isMeasureType attrs) && isInterface typeDef then - (typeIdentifier, typeIdentifier.idText, None) |> Array.singleton + Array.singleton (typeIdentifier, typeIdentifier.idText, None) else Array.empty | _ -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/InternalValuesNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/InternalValuesNames.fs index 7fd9872f1..eeeaa46e5 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/InternalValuesNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/InternalValuesNames.fs @@ -21,8 +21,7 @@ let private getValueOrFunctionIdents typeChecker accessControlLevel pattern = | Some ident when not (isActivePattern ident) && singleIdentifier -> let checkNotUnionCase = checkNotUnionCase ident if accessControlLevel = AccessControlLevel.Internal then - (ident, ident.idText, Some checkNotUnionCase) - |> Array.singleton + Array.singleton (ident, ident.idText, Some checkNotUnionCase) else Array.empty | None | Some _ -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/LiteralNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/LiteralNames.fs index 73e6da84a..2d8df210c 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/LiteralNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/LiteralNames.fs @@ -11,8 +11,8 @@ let private getIdentifiers (args:AstNodeRuleParams) = if isLiteral attributes then let rec getLiteralIdents = function | SynPat.Named(SynIdent(identifier, _), _, _, _) -> - (identifier, identifier.idText, None) |> Array.singleton - | SynPat.Paren(p, _) -> getLiteralIdents p + Array.singleton (identifier, identifier.idText, None) + | SynPat.Paren(pat, _) -> getLiteralIdents pat | _ -> Array.empty getLiteralIdents pattern diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/MeasureTypeNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/MeasureTypeNames.fs index f4dd87ac7..a743f4dab 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/MeasureTypeNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/MeasureTypeNames.fs @@ -19,7 +19,7 @@ let private getIdentifiers (args:AstNodeRuleParams) = match List.tryLast identifier with | Some typeIdentifier -> if isMeasureType attrs then - (typeIdentifier, typeIdentifier.idText, None) |> Array.singleton + Array.singleton (typeIdentifier, typeIdentifier.idText, None) else Array.empty | _ -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/MemberNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/MemberNames.fs index 2e29ee179..be36e192b 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/MemberNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/MemberNames.fs @@ -13,14 +13,13 @@ let private getMemberIdents _ = function // Ignore members prefixed with op_, they are a special case used for operator overloading. Array.empty | None -> Array.empty - | Some ident -> (ident, ident.idText, None) |> Array.singleton + | Some ident -> Array.singleton (ident, ident.idText, None) | _ -> Array.empty let private isImplementingInterface parents = - parents - |> List.exists (function + List.exists (function | AstNode.MemberDefinition (SynMemberDefn.Interface _) -> true - | _ -> false) + | _ -> false) parents let private getIdentifiers (args:AstNodeRuleParams) = match args.AstNode with @@ -36,7 +35,7 @@ let private getIdentifiers (args:AstNodeRuleParams) = | AstNode.MemberDefinition(memberDef) -> match memberDef with | SynMemberDefn.AbstractSlot(SynValSig(_, SynIdent(identifier, _), _, _, _, _, _, _, _, _, _, _), _, _, _) -> - (identifier, identifier.idText, None) |> Array.singleton + Array.singleton (identifier, identifier.idText, None) | _ -> Array.empty | _ -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs index d2738940a..fe8c2fc06 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs @@ -8,19 +8,20 @@ open FSharpLint.Framework.Ast open FSharpLint.Framework.AstInfo open FSharpLint.Framework.Rules open FSharpLint.Framework.Suggestion +open FSharpLint.Framework.Utilities open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols module QuickFixes = let removeAllUnderscores (ident:Ident) = lazy( - let toText = ident.idText.Replace("_", "") + let toText = ident.idText.Replace("_", String.Empty) Some { FromText = ident.idText; FromRange = ident.idRange; ToText = toText }) let removeNonPrefixingUnderscores (ident:Ident) = lazy( let prefixingUnderscores = - ident.idText |> Seq.takeWhile (fun x -> x = '_') |> String.Concat + ident.idText |> Seq.takeWhile (fun char -> char = '_') |> String.Concat - let toText = prefixingUnderscores + ident.idText.Replace("_", "") + let toText = prefixingUnderscores + ident.idText.Replace("_", String.Empty) Some { FromText = ident.idText; FromRange = ident.idRange; ToText = toText }) let addPrefix prefix (ident:Ident) = lazy( @@ -31,20 +32,20 @@ module QuickFixes = let private mapFirstChar map (str:string) = let prefix = - str |> Seq.takeWhile (fun x -> x = '_') |> String.Concat + str |> Seq.takeWhile (fun char -> char = '_') |> String.Concat let withoutPrefix = str.Substring prefix.Length if withoutPrefix.Length > 0 then let firstChar = map withoutPrefix.[0] |> string let rest = withoutPrefix.Substring 1 prefix + firstChar + rest - else "" + else String.Empty let toPascalCase (ident:Ident) = lazy( - let pascalCaseIdent = ident.idText |> mapFirstChar Char.ToUpper + let pascalCaseIdent = mapFirstChar Char.ToUpper ident.idText Some { FromText = ident.idText; FromRange = ident.idRange; ToText = pascalCaseIdent }) let toCamelCase (ident:Ident) = lazy( - let camelCaseIdent = ident.idText |> mapFirstChar Char.ToLower + let camelCaseIdent = mapFirstChar Char.ToLower ident.idText Some { FromText = ident.idText; FromRange = ident.idRange; ToText = camelCaseIdent }) [] @@ -55,7 +56,7 @@ let private NumberOfExpectedBackticks = 4 /// the information as to whether the identifier was backticked doesn't appear to be in the AST. let private isNotDoubleBackTickedIdent = let isDoubleBackTickedIdent (identifier:Ident) = - let diffOfRangeAgainstIdent (r:Range) = (r.EndColumn - r.StartColumn) - identifier.idText.Length + let diffOfRangeAgainstIdent (range:Range) = (range.EndColumn - range.StartColumn) - identifier.idText.Length let range = identifier.idRange not range.IsSynthetic && diffOfRangeAgainstIdent range = NumberOfExpectedBackticks @@ -128,32 +129,33 @@ let private checkIdentifierPart (config:NamingConfig) (identifier:Ident) (idText | _ -> None let prefixError = - config.Prefix - |> Option.bind (fun prefix -> + Option.bind (fun prefix -> prefixRule prefix idText - |> Option.map (formatError2 prefix >> tryAddFix (QuickFixes.addPrefix prefix))) + |> Option.map (formatError2 prefix >> tryAddFix (QuickFixes.addPrefix prefix))) config.Prefix let suffixError = - config.Suffix - |> Option.bind (fun suffix -> + Option.bind (fun suffix -> suffixRule suffix idText - |> Option.map (formatError2 suffix >> tryAddFix (QuickFixes.addSuffix suffix))) + |> Option.map (formatError2 suffix >> tryAddFix (QuickFixes.addSuffix suffix))) config.Suffix - [| - casingError - underscoresError - prefixError - suffixError - |] |> Array.choose id + Array.choose id + [| + casingError + underscoresError + prefixError + suffixError + |] let private checkIdentifier (namingConfig:NamingConfig) (identifier:Ident) (idText:string) = if notOperator idText && isNotDoubleBackTickedIdent identifier then checkIdentifierPart namingConfig identifier idText |> Array.map (fun (message, suggestedFix) -> - { Range = identifier.idRange - Message = message - SuggestedFix = Some suggestedFix - TypeChecks = [] }) + { + Range = identifier.idRange + Message = message + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty + }) else Array.empty @@ -162,7 +164,7 @@ let toAstNodeRule (namingRule:RuleMetadata) = namingRule.RuleConfig.GetIdentifiersToCheck args |> Array.collect (fun (identifier, idText, typeCheck) -> let suggestions = checkIdentifier namingRule.RuleConfig.Config identifier idText - suggestions |> Array.map (fun suggestion -> { suggestion with TypeChecks = Option.toList typeCheck })) + Array.map (fun suggestion -> { suggestion with TypeChecks = Option.toList typeCheck }) suggestions) { RuleMetadata.Name = namingRule.Name @@ -176,7 +178,7 @@ let isActivePattern (identifier:Ident) = let activePatternIdentifiers (identifier:Ident) = identifier.idText.Split('|') |> Seq.toArray - |> Array.filter (fun x -> not <| String.IsNullOrEmpty(x) && x.Trim() <> "_") + |> Array.filter (fun identifierSegment -> not <| String.IsNullOrEmpty(identifierSegment) && identifierSegment.Trim() <> "_") /// Specifies access control level as described in @@ -189,16 +191,16 @@ type AccessControlLevel = | Private | Internal -let getAccessControlLevel (syntaxArray:AbstractSyntaxArray.Node []) i = +let getAccessControlLevel (syntaxArray:AbstractSyntaxArray.Node []) index = let resolveAccessControlLevel = function | Some(SynAccess.Public _) | None -> AccessControlLevel.Public | Some(SynAccess.Private _) -> AccessControlLevel.Private | Some(SynAccess.Internal _) -> AccessControlLevel.Internal - let rec getAccessibility state isPrivateWhenReachedBinding i = - if i = 0 then state + let rec getAccessibility state isPrivateWhenReachedBinding index = + if index = 0 then state else - let node = syntaxArray.[i] + let node = syntaxArray.[index] match node.Actual with | TypeSimpleRepresentation(SynTypeDefnSimpleRepr.Record(access, _, _)) | TypeSimpleRepresentation(SynTypeDefnSimpleRepr.Union(access, _, _)) @@ -236,7 +238,7 @@ let getAccessControlLevel (syntaxArray:AbstractSyntaxArray.Node []) i = | LambdaBody(_) | Expression(_) -> getAccessibility state true node.ParentIndex - getAccessibility AccessControlLevel.Public false i + getAccessibility AccessControlLevel.Public false index /// Is an attribute with a given name? @@ -261,7 +263,7 @@ let isMeasureType = isAttribute "Measure" let isNotUnionCase (checkFile:FSharpCheckFileResults) (ident:Ident) = let symbol = checkFile.GetSymbolUseAtLocation( - ident.idRange.StartLine, ident.idRange.EndColumn, "", [ident.idText]) + ident.idRange.StartLine, ident.idRange.EndColumn, String.Empty, [ident.idText]) match symbol with | Some(symbol) when (symbol.Symbol :? FSharpUnionCase) -> false @@ -299,19 +301,24 @@ let isModule (moduleKind:SynModuleOrNamespaceKind) = /// Is module name implicitly created from file name? let isImplicitModule (SynModuleOrNamespace.SynModuleOrNamespace(longIdent, _, moduleKind, _, _, _, _, range, _)) = - let zeroLengthRange (r:Range) = - (r.EndColumn - r.StartColumn) = 0 && r.StartLine = r.EndLine + let zeroLengthRange (range:Range) = + (range.EndColumn - range.StartColumn) = 0 && range.StartLine = range.EndLine // Check the identifiers in the module name have no length. // Not ideal but there's no attribute in the AST indicating the module is implicit from the file name. // TODO: does SynModuleOrNamespaceKind.AnonModule replace this check? - isModule moduleKind && longIdent |> List.forall (fun x -> zeroLengthRange x.idRange) - -type GetIdents<'T> = AccessControlLevel -> SynPat -> 'T [] - -/// Recursively get all identifiers from pattern using provided getIdents function and collect them into array. -/// accessibility parameter is passed to getIdents, and can be narrowed down along the way (see checkAccessibility). -let rec getPatternIdents<'T> (accessibility:AccessControlLevel) (getIdents:GetIdents<'T>) argsAreParameters (pattern:SynPat) = + isModule moduleKind && longIdent |> List.forall (fun ident -> zeroLengthRange ident.idRange) + +type GetIdents<'Item> = AccessControlLevel -> SynPat -> 'Item [] + +[] +let rec private innerGetPatternIdents<'Item> (accessibility:AccessControlLevel) + (getIdents:GetIdents<'Item>) + argsAreParameters + (pattern:SynPat) + (continuation: unit -> array<'Item>) = + (continuation()) + |> Array.append <| match pattern with | SynPat.LongIdent(_, _, _, args, access, _) -> let identAccessibility = checkAccessibility accessibility access @@ -321,37 +328,31 @@ let rec getPatternIdents<'T> (accessibility:AccessControlLevel) (getIdents:GetId | SynArgPats.NamePatPairs(pats, _, _) -> pats.IsEmpty | SynArgPats.Pats(pats) -> pats.IsEmpty - let argSuggestions = - match args with + let idents = + // Only check if expecting args as parameters e.g. function - otherwise is a DU pattern. + if hasNoArgs || argsAreParameters then + getIdents identAccessibility pattern + else + Array.empty + + Array.append + idents + (match args with | SynArgPats.NamePatPairs(pats, _, _) -> - pats - |> List.toArray - |> Array.collect (fun(_, _, synPat) -> getPatternIdents AccessControlLevel.Private getIdents false synPat) + innerGetAllPatternIdents AccessControlLevel.Private getIdents (pats |> List.map (fun(_, _, synPat) -> synPat)) | SynArgPats.Pats(pats) -> - pats - |> List.toArray - |> Array.collect (getPatternIdents AccessControlLevel.Private getIdents false) - - // Only check if expecting args as parameters e.g. function - otherwise is a DU pattern. - if hasNoArgs || argsAreParameters then - getIdents identAccessibility pattern - |> Array.append argSuggestions - else - argSuggestions + innerGetAllPatternIdents AccessControlLevel.Private getIdents pats) | SynPat.Named(_, _, access, _) -> let accessibility = checkAccessibility accessibility access getIdents accessibility pattern | SynPat.Or(p1, p2, _, _) -> - [|p1; p2|] - |> Array.collect (getPatternIdents accessibility getIdents false) - | SynPat.Paren(p, _) -> - getPatternIdents accessibility getIdents false p + innerGetAllPatternIdents accessibility getIdents [p1; p2] + | SynPat.Paren(pat, _) -> + innerGetPatternIdents accessibility getIdents false pat returnEmptyArray | SynPat.Ands(pats, _) | SynPat.Tuple(_, pats, _, _) | SynPat.ArrayOrList(_, pats, _) -> - pats - |> List.toArray - |> Array.collect (getPatternIdents accessibility getIdents false) + innerGetAllPatternIdents accessibility getIdents pats | SynPat.Record(_) | SynPat.IsInst(_) | SynPat.QuoteExpr(_) @@ -364,13 +365,24 @@ let rec getPatternIdents<'T> (accessibility:AccessControlLevel) (getIdents:GetId | SynPat.InstanceMember(_) | SynPat.FromParseError(_) -> Array.empty | SynPat.As(lhsPat, rhsPat, _) -> - Array.append - (getPatternIdents accessibility getIdents false lhsPat) - (getPatternIdents accessibility getIdents false rhsPat) + innerGetPatternIdents accessibility getIdents false lhsPat + (fun () -> innerGetPatternIdents accessibility getIdents false rhsPat returnEmptyArray) | SynPat.ListCons(lhs, rhs, _, _) -> - Array.append - (getPatternIdents accessibility getIdents false lhs) - (getPatternIdents accessibility getIdents false rhs) + innerGetPatternIdents accessibility getIdents false lhs + (fun () -> innerGetPatternIdents accessibility getIdents false rhs returnEmptyArray) + +and [] innerGetAllPatternIdents (accessibility: AccessControlLevel) + (getIdents: GetIdents<'Item>) + (patterns: list): array<'Item> = + match patterns with + | head::tail -> + innerGetPatternIdents accessibility getIdents false head (fun () -> innerGetAllPatternIdents accessibility getIdents tail) + | [] -> Array.empty + +/// Recursively get all identifiers from pattern using provided getIdents function and collect them into array. +/// accessibility parameter is passed to getIdents, and can be narrowed down along the way (see checkAccessibility). +let getPatternIdents<'Item> (accessibility:AccessControlLevel) (getIdents:GetIdents<'Item>) argsAreParameters (pattern:SynPat) = + innerGetPatternIdents accessibility getIdents argsAreParameters pattern returnEmptyArray let isNested args nodeIndex = let parent = args.SyntaxArray.[nodeIndex].ParentIndex @@ -384,6 +396,6 @@ let getFunctionIdents (pattern:SynPat) = match pattern with | SynPat.LongIdent (longIdent, _, _, SynArgPats.Pats _, _, _) -> match List.tryLast longIdent.LongIdent with - | Some ident -> (ident, ident.idText, None) |> Array.singleton + | Some ident -> Array.singleton (ident, ident.idText, None) | None -> Array.empty | _ -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/ParameterNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/ParameterNames.fs index e42eebb81..e34a105ef 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/ParameterNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/ParameterNames.fs @@ -9,7 +9,7 @@ open FSharpLint.Rules.Helper.Naming let private getMemberIdents _ = function | SynPat.Named(SynIdent(ident, _), _, _, _) | SynPat.OptionalVal(ident, _) -> - (ident, ident.idText, None) |> Array.singleton + Array.singleton (ident, ident.idText, None) | _ -> Array.empty let private getValueOrFunctionIdents typeChecker _accessibility pattern = @@ -22,11 +22,11 @@ let private getValueOrFunctionIdents typeChecker _accessibility pattern = | SynPat.Named(SynIdent(ident, _), _, _, _) | SynPat.OptionalVal(ident, _) when not (isActivePattern ident) -> let checkNotUnionCase = checkNotUnionCase ident - (ident, ident.idText, Some checkNotUnionCase) |> Array.singleton + Array.singleton (ident, ident.idText, Some checkNotUnionCase) | SynPat.LongIdent(SynLongIdent([ident], _, _), _, _, SynArgPats.Pats([]), _, _) when not (isActivePattern ident) -> // Handle constructor parameters that are represented as LongIdent (e.g., PascalCase parameters) let checkNotUnionCase = checkNotUnionCase ident - (ident, ident.idText, Some checkNotUnionCase) |> Array.singleton + Array.singleton (ident, ident.idText, Some checkNotUnionCase) | _ -> Array.empty let private getIdentifiers (args:AstNodeRuleParams) = diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/PrivateValuesNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/PrivateValuesNames.fs index 7dc808e5a..a871265ea 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/PrivateValuesNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/PrivateValuesNames.fs @@ -21,8 +21,7 @@ let private getValueOrFunctionIdents typeChecker accessibility pattern = | Some ident when not (isActivePattern ident) && singleIdentifier -> let checkNotUnionCase = checkNotUnionCase ident if accessibility = AccessControlLevel.Private then - (ident, ident.idText, Some checkNotUnionCase) - |> Array.singleton + Array.singleton (ident, ident.idText, Some checkNotUnionCase) else Array.empty | None | Some _ -> Array.empty @@ -42,11 +41,11 @@ let private getIdentifiers (args:AstNodeRuleParams) = else Array.empty | AstNode.Expression(SynExpr.For(_, _, identifier, _, _, _, _, _, _)) -> - (identifier, identifier.idText, None) |> Array.singleton + Array.singleton (identifier, identifier.idText, None) | AstNode.Match(SynMatchClause(pattern, _, _, _, _, _)) -> match pattern with | SynPat.Named(SynIdent(identifier, _), isThis, _, _) when not isThis -> - (identifier, identifier.idText, None) |> Array.singleton + Array.singleton (identifier, identifier.idText, None) | SynPat.As(_lshPat, rhsPat, _) -> getPatternIdents AccessControlLevel.Private (getValueOrFunctionIdents args.CheckInfo) false rhsPat | _ -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/PublicValuesNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/PublicValuesNames.fs index 64166e1b1..774dc3651 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/PublicValuesNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/PublicValuesNames.fs @@ -25,8 +25,7 @@ let private getValueOrFunctionIdents typeChecker accessControlLevel pattern = | Some ident when singleIdentifier -> let checkNotUnionCase = checkNotUnionCase ident if accessControlLevel = AccessControlLevel.Public && isNotActivePattern ident then - (ident, ident.idText, Some checkNotUnionCase) - |> Array.singleton + Array.singleton (ident, ident.idText, Some checkNotUnionCase) else Array.empty | None | Some _ -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/RecordFieldNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/RecordFieldNames.fs index 797542383..18fe5876a 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/RecordFieldNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/RecordFieldNames.fs @@ -10,8 +10,7 @@ let private getIdentifiers (args:AstNodeRuleParams) = | AstNode.TypeSimpleRepresentation (SynTypeDefnSimpleRepr.Record (recordFields=recordFields)) -> recordFields |> List.choose (fun (SynField (idOpt=idOpt)) -> - idOpt - |> Option.map (fun identifier -> (identifier, identifier.idText, None))) + Option.map (fun (identifier: Ident) -> (identifier, identifier.idText, None)) idOpt) |> List.toArray | _ -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/UnionCasesNames.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/UnionCasesNames.fs index a34e3e92b..15e0328d6 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/UnionCasesNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/UnionCasesNames.fs @@ -8,7 +8,7 @@ open FSharpLint.Rules.Helper.Naming let private getIdentifiers (args:AstNodeRuleParams) = match args.AstNode with | AstNode.UnionCase(SynUnionCase(_, SynIdent(identifier, _), _, _, _, _, _)) -> - (identifier, identifier.idText, None) |> Array.singleton + Array.singleton (identifier, identifier.idText, None) | _ -> Array.empty let rule config = diff --git a/src/FSharpLint.Core/Rules/Conventions/NestedStatements.fs b/src/FSharpLint.Core/Rules/Conventions/NestedStatements.fs index a1c0ba89b..48a791b7e 100644 --- a/src/FSharpLint.Core/Rules/Conventions/NestedStatements.fs +++ b/src/FSharpLint.Core/Rules/Conventions/NestedStatements.fs @@ -44,35 +44,35 @@ let private getRange node = | AstNode.Binding(node) -> Some node.RangeOfBindingWithRhs | _ -> None -let private distanceToCommonParent (syntaxArray:AbstractSyntaxArray.Node []) i j = - let mutable i = i - let mutable j = j +let private distanceToCommonParent (syntaxArray:AbstractSyntaxArray.Node []) iIndex jIndex = + let mutable iIndex = iIndex + let mutable jIndex = jIndex let mutable distance = 0 - while i <> j do - if i > j then - i <- syntaxArray.[i].ParentIndex + while iIndex <> jIndex do + if iIndex > jIndex then + iIndex <- syntaxArray.[iIndex].ParentIndex - if i <> j && areChildrenNested syntaxArray.[i].Actual then + if iIndex <> jIndex && areChildrenNested syntaxArray.[iIndex].Actual then distance <- distance + 1 else - j <- syntaxArray.[j].ParentIndex + jIndex <- syntaxArray.[jIndex].ParentIndex distance /// Is node a duplicate of a node in the AST containing ExtraSyntaxInfo /// e.g. lambda arg being a duplicate of the lambda. -let isMetaData args node i = - let parentIndex = args.SyntaxArray.[i].ParentIndex - if parentIndex = i then false +let isMetaData args node index = + let parentIndex = args.SyntaxArray.[index].ParentIndex + if parentIndex = index then false else Object.ReferenceEquals(node, args.SyntaxArray.[parentIndex].Actual) -let isElseIf args node i = +let isElseIf args node index = match node with | AstNode.Expression(SynExpr.IfThenElse(_)) -> - let parentIndex = args.SyntaxArray.[i].ParentIndex - if parentIndex = i then false + let parentIndex = args.SyntaxArray.[index].ParentIndex + if parentIndex = index then false else match args.SyntaxArray.[parentIndex].Actual with | AstNode.Expression(SynExpr.IfThenElse(_, _, Some(_), _, _, _, _)) -> true @@ -81,13 +81,13 @@ let isElseIf args node i = let mutable depth = 0 -let decrementDepthToCommonParent args i j = - if j < args.SyntaxArray.Length then +let decrementDepthToCommonParent args iIndex jIndex = + if jIndex < args.SyntaxArray.Length then // If next node in array is not a sibling or child of the current node. - let parent = args.SyntaxArray.[j].ParentIndex - if parent <> i && parent <> args.SyntaxArray.[i].ParentIndex then + let parent = args.SyntaxArray.[jIndex].ParentIndex + if parent <> iIndex && parent <> args.SyntaxArray.[iIndex].ParentIndex then // Decrement depth until we reach a common parent. - depth <- depth - (distanceToCommonParent args.SyntaxArray i j) + depth <- depth - (distanceToCommonParent args.SyntaxArray iIndex jIndex) let mutable skipToIndex = None @@ -103,20 +103,25 @@ let runner (config:Config) (args:AstNodeRuleParams) = true if not skip then - let i = args.NodeIndex + let index = args.NodeIndex let node = args.AstNode - decrementDepthToCommonParent args i (i + 1) + decrementDepthToCommonParent args index (index + 1) - if areChildrenNested node && not <| isMetaData args node i && not <| isElseIf args node i then + if areChildrenNested node && not <| isMetaData args node index && not <| isElseIf args node index then if depth >= config.Depth then // Skip children as we've had an error containing them. - let skipChildren = i + args.SyntaxArray.[i].NumberOfChildren + 1 - decrementDepthToCommonParent args i skipChildren + let skipChildren = index + args.SyntaxArray.[index].NumberOfChildren + 1 + decrementDepthToCommonParent args index skipChildren skipToIndex <- Some skipChildren getRange node |> Option.map (fun range -> - { Range = range; Message = error config.Depth; SuggestedFix = None; TypeChecks = [] }) + { + Range = range + Message = error config.Depth + SuggestedFix = None + TypeChecks = List.Empty + }) |> Option.toArray else depth <- depth + 1 @@ -131,8 +136,13 @@ let cleanup () = skipToIndex <- None let rule config = - { Name = "NestedStatements" - Identifier = Identifiers.NestedStatements - RuleConfig = { AstNodeRuleConfig.Runner = runner config - Cleanup = cleanup } } - |> AstNodeRule + AstNodeRule + { + Name = "NestedStatements" + Identifier = Identifiers.NestedStatements + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = cleanup + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/NoPartialFunctions.fs b/src/FSharpLint.Core/Rules/Conventions/NoPartialFunctions.fs index 502db2f95..999a90340 100644 --- a/src/FSharpLint.Core/Rules/Conventions/NoPartialFunctions.fs +++ b/src/FSharpLint.Core/Rules/Conventions/NoPartialFunctions.fs @@ -22,54 +22,55 @@ type private Replacement = | Function of functionName:string let private partialFunctionIdentifiers = - [ - // Option - ("Option.get", PatternMatch) + Map.ofList + [ + // Option + ("Option.get", PatternMatch) - // Map - ("Map.find", Function "Map.tryFind") - ("Map.findKey", Function "Map.tryFindKey") + // Map + ("Map.find", Function "Map.tryFind") + ("Map.findKey", Function "Map.tryFindKey") - // Array - ("Array.exactlyOne", Function "Array.tryExactlyOne") - ("Array.get", Function "Array.tryItem") - ("Array.item", Function "Array.tryItem") - ("Array.find", Function "Array.tryFind") - ("Array.findIndex", Function "Array.tryFindIndex") - ("Array.findBack", Function "Array.tryFindBack") - ("Array.head", Function "Array.tryHead") - ("Array.last", Function "Array.tryLast") - ("Array.tail", Function "FSharpx.Collections.Seq.tryHeadTail") - ("Array.reduce", Function "Array.fold") - ("Array.reduceBack", Function "Array.foldBack") - ("Array.pick", Function "Array.tryPick") + // Array + ("Array.exactlyOne", Function "Array.tryExactlyOne") + ("Array.get", Function "Array.tryItem") + ("Array.item", Function "Array.tryItem") + ("Array.find", Function "Array.tryFind") + ("Array.findIndex", Function "Array.tryFindIndex") + ("Array.findBack", Function "Array.tryFindBack") + ("Array.head", Function "Array.tryHead") + ("Array.last", Function "Array.tryLast") + ("Array.tail", Function "FSharpx.Collections.Seq.tryHeadTail") + ("Array.reduce", Function "Array.fold") + ("Array.reduceBack", Function "Array.foldBack") + ("Array.pick", Function "Array.tryPick") - // Seq - ("Seq.exactlyOne", Function "Seq.tryExactlyOne") - ("Seq.item", Function "Seq.tryItem") - ("Seq.find", Function "Seq.tryFind") - ("Seq.findIndex", Function "Seq.tryFindIndex") - ("Seq.findBack", Function "Seq.tryFindBack") - ("Seq.head", Function "Seq.tryHead") - ("Seq.last", Function "Seq.tryLast") - ("Seq.tail", Function "FSharpx.Collections.Seq.tryHeadTail") - ("Seq.reduce", Function "Seq.fold") - ("Seq.reduceBack", Function "Seq.foldBack") - ("Seq.pick", Function "Seq.tryPick") + // Seq + ("Seq.exactlyOne", Function "Seq.tryExactlyOne") + ("Seq.item", Function "Seq.tryItem") + ("Seq.find", Function "Seq.tryFind") + ("Seq.findIndex", Function "Seq.tryFindIndex") + ("Seq.findBack", Function "Seq.tryFindBack") + ("Seq.head", Function "Seq.tryHead") + ("Seq.last", Function "Seq.tryLast") + ("Seq.tail", Function "FSharpx.Collections.Seq.tryHeadTail") + ("Seq.reduce", Function "Seq.fold") + ("Seq.reduceBack", Function "Seq.foldBack") + ("Seq.pick", Function "Seq.tryPick") - // List - ("List.exactlyOne", Function "List.tryExactlyOne") - ("List.item", Function "List.tryItem") - ("List.find", Function "List.tryFind") - ("List.findIndex", Function "List.tryFindIndex") - ("List.findBack", Function "List.tryFindBack") - ("List.head", Function "List.tryHead") - ("List.last", Function "List.tryLast") - ("List.tail", Function "FSharpx.Collections.Seq.tryHeadTail") - ("List.reduce", Function "List.fold") - ("List.reduceBack", Function "List.foldBack") - ("List.pick", Function "List.tryPick") - ] |> Map.ofList + // List + ("List.exactlyOne", Function "List.tryExactlyOne") + ("List.item", Function "List.tryItem") + ("List.find", Function "List.tryFind") + ("List.findIndex", Function "List.tryFindIndex") + ("List.findBack", Function "List.tryFindBack") + ("List.head", Function "List.tryHead") + ("List.last", Function "List.tryLast") + ("List.tail", Function "FSharpx.Collections.Seq.tryHeadTail") + ("List.reduce", Function "List.fold") + ("List.reduceBack", Function "List.foldBack") + ("List.pick", Function "List.tryPick") + ] /// List of tuples (fully qualified instance member name, namespace, argument compiled type name, replacement strategy) let private partialInstanceMemberIdentifiers = @@ -92,7 +93,7 @@ let private checkIfPartialIdentifier (config:Config) (identifier:string) (range: Range = range Message = String.Format(Resources.GetString ("RulesConventionsNoPartialFunctionsAdditionalError"), identifier) SuggestedFix = None - TypeChecks = [] + TypeChecks = List.Empty } else Map.tryFind identifier partialFunctionIdentifiers @@ -103,16 +104,18 @@ let private checkIfPartialIdentifier (config:Config) (identifier:string) (range: Range = range Message = String.Format(Resources.GetString ("RulesConventionsNoPartialFunctionsPatternMatchError"), identifier) SuggestedFix = None - TypeChecks = [] + TypeChecks = List.Empty } | Function replacementFunction -> { Range = range Message = String.Format(Resources.GetString "RulesConventionsNoPartialFunctionsReplacementError", replacementFunction, identifier) SuggestedFix = Some (lazy ( Some { FromText = identifier; FromRange = range; ToText = replacementFunction })) - TypeChecks = [] + TypeChecks = List.Empty }) +// not a tail-recursive function +// fsharplint:disable EnsureTailCallDiagnosticsInRecursiveFunctions let rec private tryFindTypedExpression (range: Range) (expression: FSharpExpr) = let tryFindFirst exprs = exprs |> Seq.choose (tryFindTypedExpression range) |> Seq.tryHead @@ -125,13 +128,13 @@ let rec private tryFindTypedExpression (range: Range) (expression: FSharpExpr) = | FSharpExprPatterns.AddressSet(lvalueExpr, rvalueExpr) -> tryFindTypedExpression range lvalueExpr |> Option.orElse (tryFindTypedExpression range rvalueExpr) | FSharpExprPatterns.Application(funcExpr, _typeArgs, argExprs) -> - (funcExpr :: argExprs) |> tryFindFirst + tryFindFirst (funcExpr :: argExprs) | FSharpExprPatterns.Call(objExprOpt, _memberOrFunc, _typeArgs1, _typeArgs2, argExprs) -> - (List.append (Option.toList objExprOpt) argExprs) |> tryFindFirst + tryFindFirst (List.append (Option.toList objExprOpt) argExprs) | FSharpExprPatterns.Coerce(_targetType, inpExpr) -> tryFindTypedExpression range inpExpr | FSharpExprPatterns.FastIntegerForLoop(startExpr, limitExpr, consumeExpr, _isUp, _, _) -> - [ startExpr; limitExpr; consumeExpr ] |> tryFindFirst + tryFindFirst [ startExpr; limitExpr; consumeExpr ] | FSharpExprPatterns.ILAsm(_asmCode, _typeArgs, argExprs) -> tryFindFirst argExprs | FSharpExprPatterns.ILFieldGet (objExprOpt, _fieldType, _fieldName) -> @@ -139,7 +142,7 @@ let rec private tryFindTypedExpression (range: Range) (expression: FSharpExpr) = | FSharpExprPatterns.ILFieldSet (objExprOpt, _fieldType, _fieldName, valueExpr) -> objExprOpt |> Option.bind (tryFindTypedExpression range) |> Option.orElse (tryFindTypedExpression range valueExpr) | FSharpExprPatterns.IfThenElse (guardExpr, thenExpr, elseExpr) -> - [ guardExpr; thenExpr; elseExpr ] |> tryFindFirst + tryFindFirst [ guardExpr; thenExpr; elseExpr ] | FSharpExprPatterns.Lambda(_lambdaVar, bodyExpr) -> tryFindTypedExpression range bodyExpr | FSharpExprPatterns.Let((_bindingVar, bindingExpr, _), bodyExpr) -> @@ -207,6 +210,7 @@ let rec private tryFindTypedExpression (range: Range) (expression: FSharpExpr) = | FSharpExprPatterns.WhileLoop(guardExpr, bodyExpr, _) -> tryFindTypedExpression range guardExpr |> Option.orElse (tryFindTypedExpression range bodyExpr) | _ -> None +// fsharplint:enable EnsureTailCallDiagnosticsInRecursiveFunctions let private getTypedExpressionForRange (checkFile:FSharpCheckFileResults) (range: Range) = let expressions = @@ -232,21 +236,20 @@ let private getTypedExpressionForRange (checkFile:FSharpCheckFileResults) (range let private matchesBuiltinFSharpType (typeName: string) (fsharpType: FSharpType) : Option = let matchingPartialInstanceMember = - partialInstanceMemberIdentifiers - |> List.tryFind (fun (memberName, _, _, _) -> memberName.Split('.').[0] = typeName) + List.tryFind (fun (memberName: string, _, _, _) -> memberName.Split('.').[0] = typeName) partialInstanceMemberIdentifiers match matchingPartialInstanceMember with | Some(_, typeNamespace, compiledTypeName, _) -> - (fsharpType.HasTypeDefinition - && fsharpType.TypeDefinition.Namespace = typeNamespace - && fsharpType.TypeDefinition.CompiledName = compiledTypeName) - |> Some + Some( + fsharpType.HasTypeDefinition + && fsharpType.TypeDefinition.Namespace = typeNamespace + && fsharpType.TypeDefinition.CompiledName = compiledTypeName + ) | None -> None let private isNonStaticInstanceMemberCall (checkFile:FSharpCheckFileResults) names lineText (range: Range) :(Option) = let typeChecks = - (partialInstanceMemberIdentifiers - |> List.map (fun replacement -> + let map (replacement: string * option * string * Replacement) = match replacement with | (fullyQualifiedInstanceMember, _, _, replacementStrategy) -> if not (fullyQualifiedInstanceMember.Contains ".") then @@ -289,20 +292,47 @@ let private isNonStaticInstanceMemberCall (checkFile:FSharpCheckFileResults) nam if typeMatches then match replacementStrategy with - | PatternMatch -> - Some { Range = range - Message = String.Format(Resources.GetString "RulesConventionsNoPartialFunctionsPatternMatchError", fullyQualifiedInstanceMember) - SuggestedFix = None - TypeChecks = (fun () -> typeMatches) |> List.singleton } - | Function replacementFunctionName -> - Some { Range = range - Message = String.Format(Resources.GetString "RulesConventionsNoPartialFunctionsReplacementError", replacementFunctionName, fullyQualifiedInstanceMember) - SuggestedFix = Some (lazy ( Some { FromText = (String.concat "." names) ; FromRange = range; ToText = replacementFunctionName })) - TypeChecks = (fun () -> typeMatches) |> List.singleton } + | PatternMatch -> + Some + { + Range = range + Message = + String.Format( + Resources.GetString + "RulesConventionsNoPartialFunctionsPatternMatchError", + fullyQualifiedInstanceMember + ) + SuggestedFix = None + TypeChecks = (fun () -> typeMatches) |> List.singleton + } + | Function replacementFunctionName -> + Some + { + Range = range + Message = + String.Format( + Resources.GetString "RulesConventionsNoPartialFunctionsReplacementError", + replacementFunctionName, + fullyQualifiedInstanceMember + ) + SuggestedFix = + Some( + lazy + (Some + { + FromText = (String.concat "." names) + FromRange = range + ToText = replacementFunctionName + }) + ) + TypeChecks = (fun () -> typeMatches) |> List.singleton + } else None | _ -> None - | _ -> None)) + | _ -> None + + List.map map partialInstanceMemberIdentifiers match List.tryFind(fun (typeCheck:Option) -> typeCheck.IsSome) typeChecks with | None -> None | Some instanceMember -> instanceMember @@ -314,8 +344,7 @@ let private checkMemberCallOnExpression (originalRange: Range): array = match getTypedExpressionForRange checkFile range with | Some expression -> - partialInstanceMemberIdentifiers - |> List.choose (fun (fullyQualifiedInstanceMember, _, _, replacementStrategy) -> + let choose (fullyQualifiedInstanceMember: string) (replacementStrategy: Replacement) = let typeName = fullyQualifiedInstanceMember.Split(".").[0] let fsharpType = expression.Type @@ -329,17 +358,47 @@ let private checkMemberCallOnExpression if matchesType then match replacementStrategy with | PatternMatch -> - Some { Range = originalRange - Message = String.Format(Resources.GetString "RulesConventionsNoPartialFunctionsPatternMatchError", fullyQualifiedInstanceMember) - SuggestedFix = None - TypeChecks = (fun () -> true) |> List.singleton } + Some + { + Range = originalRange + Message = + String.Format( + Resources.GetString "RulesConventionsNoPartialFunctionsPatternMatchError", + fullyQualifiedInstanceMember + ) + SuggestedFix = None + TypeChecks = (fun () -> true) |> List.singleton + } | Function replacementFunctionName -> - Some { Range = originalRange - Message = String.Format(Resources.GetString "RulesConventionsNoPartialFunctionsReplacementError", replacementFunctionName, fullyQualifiedInstanceMember) - SuggestedFix = Some (lazy ( Some { FromText = (ExpressionUtilities.tryFindTextOfRange originalRange flieContent).Value ; FromRange = originalRange; ToText = replacementFunctionName })) - TypeChecks = (fun () -> true) |> List.singleton } + Some + { + Range = originalRange + Message = + String.Format( + Resources.GetString "RulesConventionsNoPartialFunctionsReplacementError", + replacementFunctionName, + fullyQualifiedInstanceMember + ) + SuggestedFix = + Some( + lazy + (Some + { + FromText = + (ExpressionUtilities.tryFindTextOfRange originalRange flieContent) + .Value + FromRange = originalRange + ToText = replacementFunctionName + }) + ) + TypeChecks = (fun () -> true) |> List.singleton + } else - None) + None + + partialInstanceMemberIdentifiers + |> List.choose (fun (fullyQualifiedInstanceMember, _, _, replacementStrategy) -> + choose fullyQualifiedInstanceMember replacementStrategy) |> List.toArray | None -> Array.empty @@ -351,13 +410,13 @@ let private runner (config:Config) (args:AstNodeRuleParams) = match checkPartialIdentifier with | Some partialIdent -> - partialIdent |> Array.singleton + Array.singleton partialIdent | _ -> let lineText = args.Lines.[range.EndLine - 1] let nonStaticInstanceMemberTypeCheckResult = isNonStaticInstanceMemberCall checkInfo identifier lineText range match nonStaticInstanceMemberTypeCheckResult with | Some warningDetails -> - warningDetails |> Array.singleton + Array.singleton warningDetails | _ -> Array.Empty() | (Ast.Expression(SynExpr.DotGet(expr, _, SynLongIdent(_identifiers, _, _), _range)), Some checkInfo) -> let originalRange = expr.Range @@ -367,8 +426,13 @@ let private runner (config:Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "NoPartialFunctions" - Identifier = Identifiers.NoPartialFunctions - RuleConfig = { AstNodeRuleConfig.Runner = runner config - Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "NoPartialFunctions" + Identifier = Identifiers.NoPartialFunctions + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfBooleanOperatorsInCondition.fs b/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfBooleanOperatorsInCondition.fs index 7b45cd4d0..fba0c3c51 100644 --- a/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfBooleanOperatorsInCondition.fs +++ b/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfBooleanOperatorsInCondition.fs @@ -29,7 +29,7 @@ let private validateCondition (maxBooleanOperators:int) condition = total + 1 else total - | x -> + | _ -> total let ruleName = "MaxNumberOfBooleanOperatorsInCondition" @@ -39,7 +39,13 @@ let private validateCondition (maxBooleanOperators:int) condition = if numberOfBooleanOperators > maxBooleanOperators then let errorFormatString = Resources.GetString("RulesNumberOfItemsBooleanConditionsError") let error = String.Format(errorFormatString, maxBooleanOperators) - { Range = condition.Range; Message = error; SuggestedFix = None; TypeChecks = [] } |> Array.singleton + Array.singleton + { + Range = condition.Range + Message = error + SuggestedFix = None + TypeChecks = List.Empty + } else Array.empty @@ -57,7 +63,13 @@ let private runner (config:Helper.NumberOfItems.Config) (args:AstNodeRuleParams) | _ -> Array.empty let rule config = - { Name = "MaxNumberOfBooleanOperatorsInCondition" - Identifier = Identifiers.MaxNumberOfBooleanOperatorsInCondition - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "MaxNumberOfBooleanOperatorsInCondition" + Identifier = Identifiers.MaxNumberOfBooleanOperatorsInCondition + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfFunctionParameters.fs b/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfFunctionParameters.fs index 2b76f7d61..36d792651 100644 --- a/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfFunctionParameters.fs +++ b/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfFunctionParameters.fs @@ -13,7 +13,13 @@ let private validateFunction (maxParameters:int) (constructorArguments:SynArgPat when List.length parameters > maxParameters -> let errorFormatString = Resources.GetString("RulesNumberOfItemsFunctionError") let error = String.Format(errorFormatString, maxParameters) - { Range = parameters.[maxParameters].Range; Message = error; SuggestedFix = None; TypeChecks = [] } |> Array.singleton + Array.singleton + { + Range = parameters.[maxParameters].Range + Message = error + SuggestedFix = None + TypeChecks = List.Empty + } | _ -> Array.empty let private runner (config:Helper.NumberOfItems.Config) (args:AstNodeRuleParams) = @@ -23,7 +29,13 @@ let private runner (config:Helper.NumberOfItems.Config) (args:AstNodeRuleParams) | _ -> Array.empty let rule config = - { Name = "MaxNumberOfFunctionParameters" - Identifier = Identifiers.MaxNumberOfFunctionParameters - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "MaxNumberOfFunctionParameters" + Identifier = Identifiers.MaxNumberOfFunctionParameters + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfItemsInTuple.fs b/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfItemsInTuple.fs index a3da5258d..39208d1fc 100644 --- a/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfItemsInTuple.fs +++ b/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfItemsInTuple.fs @@ -7,24 +7,30 @@ open FSharp.Compiler.Syntax open FSharpLint.Framework.Ast open FSharpLint.Framework.Rules -let private isInApplication (syntaxArray:AbstractSyntaxArray.Node[]) i = - let rec isApplicationNode i = - if i <= 0 then false +let private isInApplication (syntaxArray:AbstractSyntaxArray.Node[]) index = + let rec isApplicationNode nodeIndex = + if nodeIndex <= 0 then false else - let node = syntaxArray.[i] + let node = syntaxArray.[nodeIndex] match node.Actual with | AstNode.Expression(SynExpr.Paren(_)) -> isApplicationNode node.ParentIndex | AstNode.Expression(SynExpr.App(_) | SynExpr.New(_)) -> true | _ -> false - if i <= 0 then false - else isApplicationNode syntaxArray.[i].ParentIndex + if index <= 0 then false + else isApplicationNode syntaxArray.[index].ParentIndex let private validateTuple (maxItems:int) (items:SynExpr list) = if List.length items > maxItems then let errorFormatString = Resources.GetString("RulesNumberOfItemsTupleError") let error = String.Format(errorFormatString, maxItems) - { Range = items.[maxItems].Range; Message = error; SuggestedFix = None; TypeChecks = [] } |> Array.singleton + Array.singleton + { + Range = items.[maxItems].Range + Message = error + SuggestedFix = None + TypeChecks = List.Empty + } else Array.empty @@ -39,7 +45,13 @@ let runner (config:Helper.NumberOfItems.Config) (args:AstNodeRuleParams) = Array.empty let rule config = - { Name = "MaxNumberOfItemsInTuple" - Identifier = Identifiers.MaxNumberOfItemsInTuple - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "MaxNumberOfItemsInTuple" + Identifier = Identifiers.MaxNumberOfItemsInTuple + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfMembers.fs b/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfMembers.fs index e0a53120e..8f855e53d 100644 --- a/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfMembers.fs +++ b/src/FSharpLint.Core/Rules/Conventions/NumberOfItems/MaxNumberOfMembers.fs @@ -19,20 +19,24 @@ let private getMembers (members:SynMemberDefn list) = | SynMemberDefn.AutoProperty(_, _, _, _, _, _, _, _, SynValSigAccess.GetSet (access, _, _), _, _, _) -> isPublic access | _ -> false - members - |> List.filter isPublicMember + List.filter isPublicMember members let private validateType (maxMembers:int) members typeRepresentation = let members = match typeRepresentation with | SynTypeDefnRepr.Simple(_) | SynTypeDefnRepr.Exception(_) -> members - | SynTypeDefnRepr.ObjectModel(_, members, _) -> members - |> getMembers + | SynTypeDefnRepr.ObjectModel(_, members, _) -> getMembers members if List.length members > maxMembers then let errorFormatString = Resources.GetString("RulesNumberOfItemsClassMembersError") let error = String.Format(errorFormatString, maxMembers) - { Range = members.[maxMembers].Range; Message = error; SuggestedFix = None; TypeChecks = [] } |> Array.singleton + Array.singleton + { + Range = members.[maxMembers].Range + Message = error + SuggestedFix = None + TypeChecks = List.Empty + } else Array.empty @@ -43,7 +47,13 @@ let private runner (config:Helper.NumberOfItems.Config) (args:AstNodeRuleParams) | _ -> Array.empty let rule config = - { Name = "MaxNumberOfMembers" - Identifier = Identifiers.MaxNumberOfMembers - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "MaxNumberOfMembers" + Identifier = Identifiers.MaxNumberOfMembers + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithBadUsage.fs b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithBadUsage.fs index c2bab841a..619a6cd4a 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithBadUsage.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithBadUsage.fs @@ -52,11 +52,13 @@ let private runner (args: AstNodeRuleParams) = | NullMessage -> "consider using a non-null error messages as parameter" let error = - { Range = range - Message = String.Format(Resources.GetString "RulesFailwithBadUsage", message) - SuggestedFix = suggestedFix - TypeChecks = List.Empty } - |> Array.singleton + Array.singleton + { + Range = range + Message = String.Format(Resources.GetString "RulesFailwithBadUsage", message) + SuggestedFix = suggestedFix + TypeChecks = List.Empty + } error @@ -70,7 +72,7 @@ let private runner (args: AstNodeRuleParams) = || failwithId.idText = "failwithf" -> match expression with - | SynExpr.Const (SynConst.String (id, _, _), _) when id = "" -> + | SynExpr.Const (SynConst.String (id, _, _), _) when id = String.Empty -> generateError failwithId.idText id range BadUsageType.EmptyMessage maybeIdentifier | SynExpr.Const (SynConst.String (id, _, _), _) when id <> fakeExternDeclErrorMsg -> let isDuplicate = @@ -127,9 +129,13 @@ let private runner (args: AstNodeRuleParams) = let cleanup () = failwithMessages <- Set.empty let rule = - { Name = "FailwithBadUsage" - Identifier = Identifiers.FailwithBadUsage - RuleConfig = - { AstNodeRuleConfig.Runner = runner - Cleanup = cleanup } } - |> AstNodeRule + AstNodeRule + { + Name = "FailwithBadUsage" + Identifier = Identifiers.FailwithBadUsage + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = cleanup + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithWithSingleArgument.fs b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithWithSingleArgument.fs index 0290bc040..3fd44fdaf 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithWithSingleArgument.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithWithSingleArgument.fs @@ -5,7 +5,13 @@ open FSharpLint.Framework.Rules let runner = Helper.RaiseWithTooManyArguments.checkRaiseWithTooManyArgs "failwith" 1 "FailwithWithSingleArgument" let rule = - { Name = "FailwithWithSingleArgument" - Identifier = Identifiers.FailwithWithSingleArgument - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "FailwithWithSingleArgument" + Identifier = Identifiers.FailwithWithSingleArgument + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithfWithArgumentsMatchingFormatString.fs b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithfWithArgumentsMatchingFormatString.fs index b67d3f7ed..33a104cd1 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithfWithArgumentsMatchingFormatString.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/FailwithfWithArgumentsMatchingFormatString.fs @@ -1,5 +1,7 @@ module FSharpLint.Rules.FailwithfWithArgumentsMatchingFormatString +open System + open FSharpLint.Framework open FSharpLint.Framework.Suggestion open FSharp.Compiler.Syntax @@ -13,20 +15,27 @@ let private runner (args:AstNodeRuleParams) = | FuncApp(expressions, range) -> match expressions with | SynExpr.Ident(ident)::SynExpr.Const(SynConst.String(formatString, _, _), _)::arguments - when ident.idText = "failwithf" && List.length arguments = formatString.Replace("%%", "").Split('%').Length -> - { - Range = range - Message = Resources.GetString "FailwithfWithArgumentsMatchingFormatString" - SuggestedFix = None - TypeChecks = [] - } |> Array.singleton + when ident.idText = "failwithf" && List.length arguments = formatString.Replace("%%", String.Empty).Split('%').Length -> + Array.singleton + { + Range = range + Message = Resources.GetString "FailwithfWithArgumentsMatchingFormatString" + SuggestedFix = None + TypeChecks = List.Empty + } | _ -> Array.empty | _ -> Array.empty | _ -> Array.empty let rule = - { Name = "FailwithfWithArgumentsMatchingFormatString" - Identifier = Identifiers.FailwithfWithArgumentsMatchingFormattingString - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "FailwithfWithArgumentsMatchingFormatString" + Identifier = Identifiers.FailwithfWithArgumentsMatchingFormattingString + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/InvalidArgWithTwoArguments.fs b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/InvalidArgWithTwoArguments.fs index 1803e0629..db0f7a835 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/InvalidArgWithTwoArguments.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/InvalidArgWithTwoArguments.fs @@ -5,7 +5,13 @@ open FSharpLint.Framework.Rules let runner = Helper.RaiseWithTooManyArguments.checkRaiseWithTooManyArgs "invalidArg" 2 "InvalidArgWithTwoArguments" let rule = - { Name = "InvalidArgWithTwoArguments" - Identifier = Identifiers.InvalidOpWithSingleArgument - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "InvalidArgWithTwoArguments" + Identifier = Identifiers.InvalidOpWithSingleArgument + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/InvalidOpWithSingleArgument.fs b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/InvalidOpWithSingleArgument.fs index 9c19ebdc3..9f0d05f87 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/InvalidOpWithSingleArgument.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/InvalidOpWithSingleArgument.fs @@ -5,7 +5,13 @@ open FSharpLint.Framework.Rules let runner = Helper.RaiseWithTooManyArguments.checkRaiseWithTooManyArgs "invalidOp" 1 "RulesInvalidOpWithSingleArgument" let rule = - { Name = "InvalidOpWithSingleArgument" - Identifier = Identifiers.InvalidOpWithSingleArgument - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "InvalidOpWithSingleArgument" + Identifier = Identifiers.InvalidOpWithSingleArgument + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/NullArgWithSingleArgument.fs b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/NullArgWithSingleArgument.fs index 84b172b83..91565e58c 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/NullArgWithSingleArgument.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/NullArgWithSingleArgument.fs @@ -5,7 +5,13 @@ open FSharpLint.Framework.Rules let runner = Helper.RaiseWithTooManyArguments.checkRaiseWithTooManyArgs "nullArg" 1 "RulesNullArgWithSingleArgument" let rule = - { Name = "NullArgWithSingleArgument" - Identifier = Identifiers.NullArgWithSingleArgument - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "NullArgWithSingleArgument" + Identifier = Identifiers.NullArgWithSingleArgument + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/RaiseWithSingleArgument.fs b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/RaiseWithSingleArgument.fs index 8a2894c8d..e20fef9c9 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/RaiseWithSingleArgument.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/RaiseWithSingleArgument.fs @@ -5,7 +5,13 @@ open FSharpLint.Framework.Rules let runner = Helper.RaiseWithTooManyArguments.checkRaiseWithTooManyArgs "raise" 1 "RulesRaiseWithSingleArgument" let rule = - { Name = "RaiseWithSingleArgument" - Identifier = Identifiers.RaiseWithSingleArgument - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "RaiseWithSingleArgument" + Identifier = Identifiers.RaiseWithSingleArgument + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/RaiseWithTooManyArgumentsHelper.fs b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/RaiseWithTooManyArgumentsHelper.fs index a5a46177d..668de639e 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/RaiseWithTooManyArgumentsHelper.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RaiseWithTooManyArguments/RaiseWithTooManyArgumentsHelper.fs @@ -19,12 +19,13 @@ let checkRaiseWithTooManyArgs (raiseType:string) (count:int) (ruleName:string) ( | FuncApp(expressions, range) -> match expressions with | RaiseWithTooManyArgs raiseType count -> - { - Range = range - Message = Resources.GetString ruleName - SuggestedFix = None - TypeChecks = [] - } |> Array.singleton + Array.singleton + { + Range = range + Message = Resources.GetString ruleName + SuggestedFix = None + TypeChecks = List.Empty + } | _ -> Array.empty | _ -> Array.empty | _ -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/RecursiveAsyncFunction.fs b/src/FSharpLint.Core/Rules/Conventions/RecursiveAsyncFunction.fs index 923e441e0..834ba4ac0 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RecursiveAsyncFunction.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RecursiveAsyncFunction.fs @@ -31,6 +31,26 @@ let private getFunctionNameFromAsyncCompExprBinding = function let checkRecursiveAsyncFunction (args:AstNodeRuleParams) (range:Range) (doBangExpr:SynExpr) breadcrumbs = let doTokenRange = Range.mkRange "do!" (Position.mkPos range.StartLine range.StartColumn) (Position.mkPos range.StartLine (range.StartColumn + 3)) + + let suggestFix () = + let suggestedFix = + lazy + (ExpressionUtilities.tryFindTextOfRange doTokenRange args.FileContent + |> Option.map (fun fromText -> + { + FromText = fromText + FromRange = doTokenRange + ToText = "return!" + })) + + Some + { + Range = range + Message = Resources.GetString("RulesConventionsRecursiveAsyncFunctionError") + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty + } + match doBangExpr with | SynExpr.App (funcExpr=(SynExpr.Ident callerIdent)) -> breadcrumbs @@ -40,21 +60,10 @@ let checkRecursiveAsyncFunction (args:AstNodeRuleParams) (range:Range) (doBangEx bindings | AstNode.Expression (SynExpr.LetOrUse (true, false, bindings, _, _, _)) -> bindings - | _ -> []) + | _ -> List.Empty) |> List.choose getFunctionNameFromAsyncCompExprBinding |> List.filter ((=) callerIdent.idText) - |> List.choose (fun _ -> - let suggestedFix = lazy( - ExpressionUtilities.tryFindTextOfRange doTokenRange args.FileContent - |> Option.map (fun fromText -> - { FromText = fromText - FromRange = doTokenRange - ToText = "return!" })) - - { Range = range - Message = Resources.GetString("RulesConventionsRecursiveAsyncFunctionError") - SuggestedFix = Some suggestedFix - TypeChecks = [] } |> Some) + |> List.choose (fun _ -> suggestFix ()) |> List.toArray | _ -> Array.empty @@ -66,7 +75,13 @@ let runner args = | _ -> Array.empty let rule = - { Name = "RecursiveAsyncFunction" - Identifier = Identifiers.RecursiveAsyncFunction - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "RecursiveAsyncFunction" + Identifier = Identifiers.RecursiveAsyncFunction + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/RedundantNewKeyword.fs b/src/FSharpLint.Core/Rules/Conventions/RedundantNewKeyword.fs index d95cc67c2..4ab45e080 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RedundantNewKeyword.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RedundantNewKeyword.fs @@ -1,5 +1,7 @@ module FSharpLint.Rules.RedundantNewKeyword +open System + open FSharpLint.Framework open FSharpLint.Framework.Suggestion open FSharp.Compiler.Symbols @@ -16,17 +18,17 @@ let private implementsIDisposable (fsharpType:FSharpType) = else false -let private doesNotImplementIDisposable (checkFile:FSharpCheckFileResults) (ident: SynLongIdent) = fun () -> - let names = ident.LongIdent |> List.map (fun x -> x.idText) - let symbol = checkFile.GetSymbolUseAtLocation(ident.Range.StartLine, ident.Range.EndColumn, "", names) +let private doesNotImplementIDisposable (checkFile:FSharpCheckFileResults) (ident: SynLongIdent) = + let names = List.map (fun (identifier: Ident) -> identifier.idText) ident.LongIdent + let symbol = checkFile.GetSymbolUseAtLocation(ident.Range.StartLine, ident.Range.EndColumn, String.Empty, names) match symbol with | Some(symbol) when (symbol.Symbol :? FSharpMemberOrFunctionOrValue) -> let ctor = symbol.Symbol :?> FSharpMemberOrFunctionOrValue - ctor.DeclaringEntity - |> Option.exists (fun ctorForType -> - Seq.forall (implementsIDisposable >> not) ctorForType.AllInterfaces) + Option.exists + (fun (ctorForType: FSharpEntity) -> Seq.forall (implementsIDisposable >> not) ctorForType.AllInterfaces) + ctor.DeclaringEntity | Some symbol when (symbol.Symbol :? FSharpEntity) -> let ctor = symbol.Symbol :?> FSharpEntity Seq.forall (implementsIDisposable >> not) ctor.AllInterfaces @@ -42,17 +44,29 @@ let private generateFix (text:string) range = lazy( let runner args = - match args.AstNode, args.CheckInfo with + match (args.AstNode, args.CheckInfo) with | AstNode.Expression(SynExpr.New(_, SynType.LongIdent(identifier), _, range)), Some checkInfo | AstNode.Expression(SynExpr.New(_, SynType.App(SynType.LongIdent(identifier), _, _, _, _, _, _), _, range)), Some checkInfo -> - { Range = range - Message = Resources.GetString("RulesRedundantNewKeyword") - SuggestedFix = Some (generateFix args.FileContent range) - TypeChecks = [ doesNotImplementIDisposable checkInfo identifier ] } |> Array.singleton + Array.singleton + { + Range = range + Message = Resources.GetString("RulesRedundantNewKeyword") + SuggestedFix = Some(generateFix args.FileContent range) + TypeChecks = + [ + fun () -> doesNotImplementIDisposable checkInfo identifier + ] + } | _ -> Array.empty let rule = - { Name = "RedundantNewKeyword" - Identifier = Identifiers.RedundantNewKeyword - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "RedundantNewKeyword" + Identifier = Identifiers.RedundantNewKeyword + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInClass.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInClass.fs index 0c7e92bb6..9e2dedadd 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInClass.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInClass.fs @@ -14,7 +14,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInClass" - Identifier = Identifiers.MaxLinesInClass - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInClass" + Identifier = Identifiers.MaxLinesInClass + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInConstructor.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInConstructor.fs index 9f7fad7ea..5113d487c 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInConstructor.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInConstructor.fs @@ -14,7 +14,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInConstructor" - Identifier = Identifiers.MaxLinesInConstructor - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInConstructor" + Identifier = Identifiers.MaxLinesInConstructor + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInEnum.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInEnum.fs index dd2b692f1..1c3ed64ec 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInEnum.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInEnum.fs @@ -17,7 +17,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInEnum" - Identifier = Identifiers.MaxLinesInEnum - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInEnum" + Identifier = Identifiers.MaxLinesInEnum + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInFunction.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInFunction.fs index 61809da86..641f3b08d 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInFunction.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInFunction.fs @@ -14,7 +14,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInFunction" - Identifier = Identifiers.MaxLinesInFunction - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInFunction" + Identifier = Identifiers.MaxLinesInFunction + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInLambdaFunction.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInLambdaFunction.fs index 435675986..5259cc815 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInLambdaFunction.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInLambdaFunction.fs @@ -11,7 +11,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInLambdaFunction" - Identifier = Identifiers.MaxLinesInLambdaFunction - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInLambdaFunction" + Identifier = Identifiers.MaxLinesInLambdaFunction + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInMatchLambdaFunction.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInMatchLambdaFunction.fs index fa16e3c67..6defc3b46 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInMatchLambdaFunction.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInMatchLambdaFunction.fs @@ -11,7 +11,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInMatchLambdaFunction" - Identifier = Identifiers.MaxLinesInMatchLambdaFunction - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInMatchLambdaFunction" + Identifier = Identifiers.MaxLinesInMatchLambdaFunction + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInMember.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInMember.fs index 1504c90b6..9ac8cc1d7 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInMember.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInMember.fs @@ -14,7 +14,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInMember" - Identifier = Identifiers.MaxLinesInMember - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInMember" + Identifier = Identifiers.MaxLinesInMember + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInModule.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInModule.fs index 90650eec2..2187a5500 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInModule.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInModule.fs @@ -11,7 +11,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInModule" - Identifier = Identifiers.MaxLinesInModule - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInModule" + Identifier = Identifiers.MaxLinesInModule + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInProperty.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInProperty.fs index 70189f93b..2abc11c11 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInProperty.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInProperty.fs @@ -14,7 +14,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInProperty" - Identifier = Identifiers.MaxLinesInProperty - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInProperty" + Identifier = Identifiers.MaxLinesInProperty + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInRecord.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInRecord.fs index 191c9ca30..a97d65572 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInRecord.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInRecord.fs @@ -17,7 +17,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInRecord" - Identifier = Identifiers.MaxLinesInRecord - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInRecord" + Identifier = Identifiers.MaxLinesInRecord + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInUnion.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInUnion.fs index ca67265eb..5a9cd5813 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInUnion.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInUnion.fs @@ -17,7 +17,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInUnion" - Identifier = Identifiers.MaxLinesInUnion - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInUnion" + Identifier = Identifiers.MaxLinesInUnion + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInValue.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInValue.fs index 5a2fcebbb..abdcf89cf 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInValue.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/MaxLinesInValue.fs @@ -14,7 +14,13 @@ let runner (config:Helper.SourceLength.Config) (args:AstNodeRuleParams) = | _ -> Array.empty let rule config = - { Name = "MaxLinesInValue" - Identifier = Identifiers.MaxLinesInValue - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "MaxLinesInValue" + Identifier = Identifiers.MaxLinesInValue + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/SourceLength/SourceLengthHelper.fs b/src/FSharpLint.Core/Rules/Conventions/SourceLength/SourceLengthHelper.fs index 6ecc22f48..bfa26d6eb 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SourceLength/SourceLengthHelper.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SourceLength/SourceLengthHelper.fs @@ -17,9 +17,9 @@ type private MultilineCommentMarker = | Begin of int | End of int -let private error name i actual = +let private error name lineCount actual = let errorFormatString = Resources.GetString("RulesSourceLengthError") - String.Format(errorFormatString, name, i, actual) + String.Format(errorFormatString, name, lineCount, actual) let private singleLineCommentRegex = Regex(@"^[\s]*\/\/.*$", RegexOptions.Multiline) @@ -40,16 +40,16 @@ let private stripMultilineComments (source: string) = let rec getTopLevelBalancedPairs (toProcess: List) (stack: List) : List = match toProcess with - | [] -> [] + | [] -> List.Empty | Begin(index)::tail -> getTopLevelBalancedPairs tail (index::stack) | End(index)::tail -> match stack with - | [] -> [] - | [ beginIndex ] -> (beginIndex, index) :: getTopLevelBalancedPairs tail [] + | [] -> List.Empty + | [ beginIndex ] -> (beginIndex, index) :: getTopLevelBalancedPairs tail List.Empty | _::restOfStack -> getTopLevelBalancedPairs tail restOfStack - getTopLevelBalancedPairs markers [] + getTopLevelBalancedPairs markers List.Empty |> List.fold (fun (currSource: string) (startIndex, endIndex) -> currSource.Substring(0, startIndex) @@ -73,10 +73,13 @@ let checkSourceLengthRule (config:Config) range fileContents errorName = let skipResult = sourceCodeLines.Length - commentLinesCount - blankLinesCount if skipResult > config.MaxLines then - { Range = range - Message = error errorName config.MaxLines skipResult - SuggestedFix = None - TypeChecks = [] } |> Array.singleton + Array.singleton + { + Range = range + Message = error errorName config.MaxLines skipResult + SuggestedFix = None + TypeChecks = List.Empty + } else Array.empty | None -> Array.empty diff --git a/src/FSharpLint.Core/Rules/Conventions/SuggestUseAutoProperty.fs b/src/FSharpLint.Core/Rules/Conventions/SuggestUseAutoProperty.fs index e1efccf39..f98b6ce5c 100644 --- a/src/FSharpLint.Core/Rules/Conventions/SuggestUseAutoProperty.fs +++ b/src/FSharpLint.Core/Rules/Conventions/SuggestUseAutoProperty.fs @@ -6,43 +6,63 @@ open FSharpLint.Framework open FSharpLint.Framework.Suggestion open FSharpLint.Framework.Ast open FSharpLint.Framework.Rules +open FSharpLint.Framework.Utilities -let rec private isImmutableValueExpression (args: AstNodeRuleParams) (expression: SynExpr) = +[] +let rec private isImmutableValueExpression (args: AstNodeRuleParams) (expression: SynExpr) (continuation: bool -> bool) = match expression with - | SynExpr.Const (_constant, _range) -> true + | SynExpr.Const (_constant, _range) -> continuation true | SynExpr.Ident ident -> let isMutableVariable = + let exists memberDef = + match memberDef with + | SynMemberDefn.LetBindings (bindings, _, _, _) -> + List.exists (fun (SynBinding (_, _, _, isMutable, _, _, _, headPat, _, _, _, _, _)) -> + match headPat with + | SynPat.Named (SynIdent(bindingIdent, _), _, _, _) when isMutable -> + bindingIdent.idText = ident.idText + | _ -> false) bindings + | _ -> false + match args.GetParents args.NodeIndex with | TypeDefinition (SynTypeDefn (_, SynTypeDefnRepr.ObjectModel (_, members, _), _, _, _, _)) :: _ -> - members - |> List.exists (fun (memberDef: SynMemberDefn) -> - match memberDef with - | SynMemberDefn.LetBindings (bindings, _, _, _) -> - bindings - |> List.exists (fun (SynBinding (_, _, _, isMutable, _, _, _, headPat, _, _, _, _, _)) -> - match headPat with - | SynPat.Named (SynIdent(bindingIdent, _), _, _, _) when isMutable -> - bindingIdent.idText = ident.idText - | _ -> false) - | _ -> false) + List.exists exists members | _ -> false - not isMutableVariable + not isMutableVariable |> continuation | SynExpr.ArrayOrList (_, elements, _) -> - elements - |> List.forall (isImmutableValueExpression args) + areImmutableAllValueExpressions args elements id | SynExpr.ArrayOrListComputed (_, innerExpr, _) -> - isImmutableValueExpression args innerExpr - || isImmutableSequentialExpression args innerExpr - | _ -> false + isImmutableValueExpression + args + innerExpr + (fun prevResult -> prevResult || isImmutableSequentialExpression args innerExpr id) + | _ -> continuation false -and isImmutableSequentialExpression args expression = +and [] areImmutableAllValueExpressions (args: AstNodeRuleParams) (expressions: list) (continuation: bool -> bool) = + match expressions with + | head::tail -> + areImmutableAllValueExpressions + args + tail + (fun prevResult -> + isImmutableValueExpression args head (fun prevResult2 -> prevResult2 && prevResult)) + | [] -> continuation true + +and [] isImmutableSequentialExpression args expression (continuation: bool -> bool) = match expression with | SynExpr.Sequential (_, _, expr1, expr2, _, _) -> - isImmutableValueExpression args expr1 - && (isImmutableSequentialExpression args expr2 - || isImmutableValueExpression args expr2) - | _ -> false + isImmutableValueExpression + args + expr1 + (fun prevResult -> + isImmutableSequentialExpression args expr2 + (fun prevResult2 -> + isImmutableValueExpression args expr2 + (fun prevResult3 -> prevResult && prevResult2 || prevResult3) + ) + ) + | _ -> continuation false let private hasStructAttribute node = match node with @@ -81,10 +101,10 @@ let private runner (args: AstNodeRuleParams) = memberRange ) ) when memberFlags.IsInstance -> - match expr, argPats with + match (expr, argPats) with | _, SynArgPats.Pats pats when pats.Length > 0 -> // non-property member Array.empty - | expression, _ when isImmutableValueExpression args expression -> + | expression, _ when isImmutableValueExpression args expression id -> match args.GetParents args.NodeIndex with | parentNode :: _ when hasStructAttribute parentNode -> Array.empty @@ -99,18 +119,22 @@ let private runner (args: AstNodeRuleParams) = ToText = $"val {memberName.idText}" } | _ -> None) - { Range = memberRange - Message = Resources.GetString "RulesSuggestUseAutoProperty" - SuggestedFix = Some suggestedFix - TypeChecks = List.Empty } - |> Array.singleton + Array.singleton + { Range = memberRange + Message = Resources.GetString "RulesSuggestUseAutoProperty" + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty } | _ -> Array.empty | _ -> Array.empty let rule = - { Name = "SuggestUseAutoProperty" - Identifier = Identifiers.SuggestUseAutoProperty - RuleConfig = - { AstNodeRuleConfig.Runner = runner - Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "SuggestUseAutoProperty" + Identifier = Identifiers.SuggestUseAutoProperty + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/UnneededRecKeyword.fs b/src/FSharpLint.Core/Rules/Conventions/UnneededRecKeyword.fs index 29b7fb823..38a65f7f6 100644 --- a/src/FSharpLint.Core/Rules/Conventions/UnneededRecKeyword.fs +++ b/src/FSharpLint.Core/Rules/Conventions/UnneededRecKeyword.fs @@ -56,7 +56,7 @@ let private emitWarning (func: RecursiveFunctionInfo) = TypeChecks = list.Empty } let runner (args: AstNodeRuleParams) = - match args.AstNode, args.CheckInfo with + match (args.AstNode, args.CheckInfo) with | RecursiveFunctions(funcs), Some checkInfo -> funcs |> List.choose @@ -69,9 +69,13 @@ let runner (args: AstNodeRuleParams) = | _ -> Array.empty let rule = - { Name = "UnneededRecKeyword" - Identifier = Identifiers.UnneededRecKeyword - RuleConfig = - { AstNodeRuleConfig.Runner = runner - Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "UnneededRecKeyword" + Identifier = Identifiers.UnneededRecKeyword + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Conventions/UsedUnderscorePrefixedElements.fs b/src/FSharpLint.Core/Rules/Conventions/UsedUnderscorePrefixedElements.fs index 1ca49f0ab..513db8f9a 100644 --- a/src/FSharpLint.Core/Rules/Conventions/UsedUnderscorePrefixedElements.fs +++ b/src/FSharpLint.Core/Rules/Conventions/UsedUnderscorePrefixedElements.fs @@ -13,30 +13,42 @@ open FSharpLint.Framework.Rules let runner (args: AstNodeRuleParams) = // hack to only run rule once if args.NodeIndex = 0 then + let processSymbolUse (usage: FSharpSymbolUse) = + match usage.Symbol with + | :? FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue as symbol -> + let conditions = + not usage.IsFromDefinition + && symbol.FullName.StartsWith "_" + && symbol.FullName <> "_" + && not symbol.IsCompilerGenerated + if conditions then + Some { + Range = usage.Range + Message = String.Format(Resources.GetString ("RulesUsedUnderscorePrefixedElements")) + SuggestedFix = None + TypeChecks = List.Empty + } + else + None + | _ -> None + match args.CheckInfo with | Some checkResults -> checkResults.GetAllUsesOfAllSymbolsInFile() - |> Seq.choose (fun usage -> - match usage.Symbol with - | :? FSharp.Compiler.Symbols.FSharpMemberOrFunctionOrValue as symbol -> - if not usage.IsFromDefinition && symbol.FullName.StartsWith "_" - && symbol.FullName <> "_" && not symbol.IsCompilerGenerated then - Some { - Range = usage.Range - Message = String.Format(Resources.GetString ("RulesUsedUnderscorePrefixedElements")) - SuggestedFix = None - TypeChecks = List.Empty - } - else - None - | _ -> None ) + |> Seq.choose processSymbolUse |> Seq.toArray | None -> Array.empty else Array.empty let rule = - { Name = "UsedUnderscorePrefixedElements" - Identifier = Identifiers.UsedUnderscorePrefixedElements - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "UsedUnderscorePrefixedElements" + Identifier = Identifiers.UsedUnderscorePrefixedElements + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchClauseIndentation.fs b/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchClauseIndentation.fs index 016005119..1e921c4ce 100644 --- a/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchClauseIndentation.fs +++ b/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchClauseIndentation.fs @@ -15,53 +15,73 @@ let check (config:Config) (args:AstNodeRuleParams) matchExprRange (clauses:SynMa let matchStartIndentation = ExpressionUtilities.getLeadingSpaces matchExprRange args.FileContent let indentationLevelError = + + let processClause (firstClause: SynMatchClause) = + let clauseIndentation = ExpressionUtilities.getLeadingSpaces firstClause.Range args.FileContent + if isLambda then + if clauseIndentation <> matchStartIndentation + args.GlobalConfig.numIndentationSpaces then + Some + { + Range = firstClause.Range + Message = Resources.GetString("RulesFormattingLambdaPatternMatchClauseIndentationError") + SuggestedFix = None + TypeChecks = List.Empty + } + else + None + elif clauseIndentation <> matchStartIndentation then + Some + { + Range = firstClause.Range + Message = Resources.GetString("RulesFormattingPatternMatchClauseIndentationError") + SuggestedFix = None + TypeChecks = List.Empty + } + else + None + if isLambda && config.AllowSingleLineLambda && clauses |> List.forall (fun clause -> clause.Range.StartLine = matchExprRange.StartLine) then None else clauses |> List.tryHead - |> Option.bind (fun firstClause -> - let clauseIndentation = ExpressionUtilities.getLeadingSpaces firstClause.Range args.FileContent - if isLambda then - if clauseIndentation <> matchStartIndentation + args.GlobalConfig.numIndentationSpaces then - { Range = firstClause.Range - Message = Resources.GetString("RulesFormattingLambdaPatternMatchClauseIndentationError") - SuggestedFix = None - TypeChecks = [] } |> Some - else - None - elif clauseIndentation <> matchStartIndentation then - { Range = firstClause.Range - Message = Resources.GetString("RulesFormattingPatternMatchClauseIndentationError") - SuggestedFix = None - TypeChecks = [] } |> Some - else - None) + |> Option.bind processClause let consistentIndentationErrors = + let choose (clauseOneSpaces: int) (clauseTwo: SynMatchClause) (clauseTwoSpaces: int) = + if clauseOneSpaces <> clauseTwoSpaces then + Some + { + Range = clauseTwo.Range + Message = Resources.GetString("RulesFormattingPatternMatchClauseSameIndentationError") + SuggestedFix = None + TypeChecks = List.Empty + } + else + None + clauses |> List.toArray |> Array.map (fun clause -> (clause, ExpressionUtilities.getLeadingSpaces clause.Range args.FileContent)) |> Array.pairwise - |> Array.choose (fun ((_, clauseOneSpaces), (clauseTwo, clauseTwoSpaces)) -> - if clauseOneSpaces <> clauseTwoSpaces then - { Range = clauseTwo.Range - Message = Resources.GetString("RulesFormattingPatternMatchClauseSameIndentationError") - SuggestedFix = None - TypeChecks = [] } |> Some - else - None) + |> Array.choose (fun ((_, clauseOneSpaces), (clauseTwo, clauseTwoSpaces)) -> choose clauseOneSpaces clauseTwo clauseTwoSpaces) - [| - indentationLevelError |> Option.toArray - consistentIndentationErrors - |] - |> Array.concat + Array.concat + [| + Option.toArray indentationLevelError + consistentIndentationErrors + |] let runner (config:Config) (args:AstNodeRuleParams) = PatternMatchFormatting.isActualPatternMatch args (check config) let rule config = - { Name = "PatternMatchClauseIndentation" - Identifier = Identifiers.PatternMatchClauseIndentation - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "PatternMatchClauseIndentation" + Identifier = Identifiers.PatternMatchClauseIndentation + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchClausesOnNewLine.fs b/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchClausesOnNewLine.fs index 03302b81a..770b55521 100644 --- a/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchClausesOnNewLine.fs +++ b/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchClausesOnNewLine.fs @@ -9,22 +9,33 @@ open FSharpLint.Framework.Rules open FSharpLint.Rules.Helper let check args _ (clauses:SynMatchClause list) _ = + let choose (clauseOne: SynMatchClause) (clauseTwo: SynMatchClause) = + if clauseOne.Range.EndLine = clauseTwo.Range.StartLine then + Some + { + Range = clauseTwo.Range + Message = Resources.GetString("RulesFormattingPatternMatchClausesOnNewLineError") + SuggestedFix = None + TypeChecks = List.Empty + } + else + None + clauses |> List.toArray |> Array.pairwise - |> Array.choose (fun (clauseOne, clauseTwo) -> - if clauseOne.Range.EndLine = clauseTwo.Range.StartLine then - { Range = clauseTwo.Range - Message = Resources.GetString("RulesFormattingPatternMatchClausesOnNewLineError") - SuggestedFix = None - TypeChecks = [] } |> Some - else - None) + |> Array.choose (fun (clauseOne, clauseTwo) -> choose clauseOne clauseTwo) let runner (args:AstNodeRuleParams) = PatternMatchFormatting.isActualPatternMatch args check let rule = - { Name = "PatternMatchClausesOnNewLine" - Identifier = Identifiers.PatternMatchClausesOnNewLine - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "PatternMatchClausesOnNewLine" + Identifier = Identifiers.PatternMatchClausesOnNewLine + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchExpressionIndentation.fs b/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchExpressionIndentation.fs index ee72ffdeb..b398fbd45 100644 --- a/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchExpressionIndentation.fs +++ b/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchExpressionIndentation.fs @@ -9,9 +9,7 @@ open FSharpLint.Framework.Rules open FSharpLint.Rules.Helper let check (args:AstNodeRuleParams) _ (clauses:SynMatchClause list) _ = - clauses - |> List.toArray - |> Array.choose (fun clause -> + let processClause clause = let (SynMatchClause (pat, guard, expr, _, _, _)) = clause let clauseIndentation = ExpressionUtilities.getLeadingSpaces clause.Range args.FileContent let exprIndentation = ExpressionUtilities.getLeadingSpaces expr.Range args.FileContent @@ -20,17 +18,30 @@ let check (args:AstNodeRuleParams) _ (clauses:SynMatchClause list) _ = |> Option.map (fun expr -> expr.Range.EndLine) |> Option.defaultValue pat.Range.EndLine if expr.Range.StartLine <> matchPatternEndLine && exprIndentation <> clauseIndentation + args.GlobalConfig.numIndentationSpaces then - { Range = expr.Range - Message = Resources.GetString("RulesFormattingMatchExpressionIndentationError") - SuggestedFix = None - TypeChecks = [] } |> Some + Some + { + Range = expr.Range + Message = Resources.GetString("RulesFormattingMatchExpressionIndentationError") + SuggestedFix = None + TypeChecks = List.Empty + } else - None) + None + + clauses + |> List.toArray + |> Array.choose processClause let runner (args:AstNodeRuleParams) = PatternMatchFormatting.isActualPatternMatch args check let rule = - { Name = "PatternMatchExpressionIndentation" - Identifier = Identifiers.PatternMatchExpressionIndentation - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "PatternMatchExpressionIndentation" + Identifier = Identifiers.PatternMatchExpressionIndentation + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchOrClausesOnNewLine.fs b/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchOrClausesOnNewLine.fs index 6718f6504..d40a08cc9 100644 --- a/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchOrClausesOnNewLine.fs +++ b/src/FSharpLint.Core/Rules/Formatting/PatternMatchFormatting/PatternMatchOrClausesOnNewLine.fs @@ -9,26 +9,37 @@ open FSharpLint.Framework.Rules open FSharpLint.Rules.Helper let check args _ (clauses:SynMatchClause list) _ = + let choose (clauseOne: SynPat) (clauseTwo: SynPat) = + if clauseOne.Range.EndLine = clauseTwo.Range.StartLine then + Some + { + Range = clauseTwo.Range + Message = Resources.GetString("RulesFormattingPatternMatchOrClausesOnNewLineError") + SuggestedFix = None + TypeChecks = List.Empty + } + else + None + clauses |> List.toArray |> Array.collect (function | SynMatchClause (SynPat.Or (firstPat, secondPat, _, _), _, _, _, _, _) -> [|firstPat; secondPat|] - | _ -> [||]) + | _ -> Array.empty) |> Array.pairwise - |> Array.choose (fun (clauseOne, clauseTwo) -> - if clauseOne.Range.EndLine = clauseTwo.Range.StartLine then - { Range = clauseTwo.Range - Message = Resources.GetString("RulesFormattingPatternMatchOrClausesOnNewLineError") - SuggestedFix = None - TypeChecks = [] } |> Some - else - None) + |> Array.choose (fun (clauseOne, clauseTwo) -> choose clauseOne clauseTwo) let runner (args:AstNodeRuleParams) = PatternMatchFormatting.isActualPatternMatch args check let rule = - { Name = "PatternMatchOrClausesOnNewLine" - Identifier = Identifiers.PatternMatchOrClausesOnNewLine - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "PatternMatchOrClausesOnNewLine" + Identifier = Identifiers.PatternMatchOrClausesOnNewLine + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/Spacing/ClassMemberSpacing.fs b/src/FSharpLint.Core/Rules/Formatting/Spacing/ClassMemberSpacing.fs index f8c997457..71d7110ca 100644 --- a/src/FSharpLint.Core/Rules/Formatting/Spacing/ClassMemberSpacing.fs +++ b/src/FSharpLint.Core/Rules/Formatting/Spacing/ClassMemberSpacing.fs @@ -10,10 +10,7 @@ open FSharpLint.Framework.Rules open FSharpLint.Framework.ExpressionUtilities let checkClassMemberSpacing (args:AstNodeRuleParams) (members:SynMemberDefns) = - members - |> List.toArray - |> Array.pairwise - |> Array.choose (fun (memberOne, memberTwo) -> + let choose (memberOne: SynMemberDefn) (memberTwo: SynMemberDefn) = let numPrecedingCommentLines = countPrecedingCommentLines args.FileContent memberOne.Range.End memberTwo.Range.Start if memberTwo.Range.StartLine <> memberOne.Range.EndLine + 2 + numPrecedingCommentLines then let intermediateRange = @@ -25,16 +22,24 @@ let checkClassMemberSpacing (args:AstNodeRuleParams) (members:SynMemberDefns) = else 0 Range.mkRange - "" + String.Empty (Position.mkPos (memberOne.Range.EndLine + 1) 0) (Position.mkPos (memberTwo.Range.StartLine + endOffset) 0) - { Range = intermediateRange - Message = Resources.GetString("RulesFormattingClassMemberSpacingError") - SuggestedFix = None - TypeChecks = [] } |> Some + Some + { + Range = intermediateRange + Message = Resources.GetString("RulesFormattingClassMemberSpacingError") + SuggestedFix = None + TypeChecks = List.Empty + } else - None) + None + + members + |> List.toArray + |> Array.pairwise + |> Array.choose (fun (memberOne, memberTwo) -> choose memberOne memberTwo) let runner args = match args.AstNode with @@ -45,7 +50,13 @@ let runner args = | _ -> Array.empty let rule = - { Name = "ClassMemberSpacing" - Identifier = Identifiers.ClassMemberSpacing - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "ClassMemberSpacing" + Identifier = Identifiers.ClassMemberSpacing + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/Spacing/ModuleDeclSpacing.fs b/src/FSharpLint.Core/Rules/Formatting/Spacing/ModuleDeclSpacing.fs index cb91518b2..08a140e6a 100644 --- a/src/FSharpLint.Core/Rules/Formatting/Spacing/ModuleDeclSpacing.fs +++ b/src/FSharpLint.Core/Rules/Formatting/Spacing/ModuleDeclSpacing.fs @@ -10,32 +10,35 @@ open FSharpLint.Framework.Rules open FSharpLint.Framework.ExpressionUtilities let checkModuleDeclSpacing (args:AstNodeRuleParams) synModuleOrNamespace = + + let choose (declOne: SynModuleDecl) (declTwo: SynModuleDecl) = + let numPrecedingCommentLines = countPrecedingCommentLines args.FileContent declOne.Range.End declTwo.Range.Start + if declTwo.Range.StartLine <> declOne.Range.EndLine + 3 + numPrecedingCommentLines then + let intermediateRange = + let startLine = declOne.Range.EndLine + 1 + let endLine = declTwo.Range.StartLine + let endOffset = if startLine = endLine then 1 else 0 + + Range.mkRange + String.Empty + (Position.mkPos (declOne.Range.EndLine + 1) 0) + (Position.mkPos (declTwo.Range.StartLine + endOffset) 0) + Some + { + Range = intermediateRange + Message = Resources.GetString("RulesFormattingModuleDeclSpacingError") + SuggestedFix = None + TypeChecks = List.Empty + } + else + None + match synModuleOrNamespace with | SynModuleOrNamespace (_, _, _, decls, _, _, _, _, _) -> decls |> List.toArray |> Array.pairwise - |> Array.choose (fun (declOne, declTwo) -> - let numPrecedingCommentLines = countPrecedingCommentLines args.FileContent declOne.Range.End declTwo.Range.Start - if declTwo.Range.StartLine <> declOne.Range.EndLine + 3 + numPrecedingCommentLines then - let intermediateRange = - let startLine = declOne.Range.EndLine + 1 - let endLine = declTwo.Range.StartLine - let endOffset = - if startLine = endLine - then 1 - else 0 - - Range.mkRange - "" - (Position.mkPos (declOne.Range.EndLine + 1) 0) - (Position.mkPos (declTwo.Range.StartLine + endOffset) 0) - { Range = intermediateRange - Message = Resources.GetString("RulesFormattingModuleDeclSpacingError") - SuggestedFix = None - TypeChecks = [] } |> Some - else - None) + |> Array.choose (fun (declOne, declTwo) -> choose declOne declTwo) let runner args = match args.AstNode with @@ -44,7 +47,13 @@ let runner args = | _ -> Array.empty let rule = - { Name = "ModuleDeclSpacing" - Identifier = Identifiers.ModuleDeclSpacing - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "ModuleDeclSpacing" + Identifier = Identifiers.ModuleDeclSpacing + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleCommaSpacing.fs b/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleCommaSpacing.fs index f16cc28fa..beda74140 100644 --- a/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleCommaSpacing.fs +++ b/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleCommaSpacing.fs @@ -11,31 +11,46 @@ open FSharpLint.Rules.Helper // Check for single space after commas in tuple. let checkTupleCommaSpacing (args:AstNodeRuleParams) (tupleExprs:SynExpr list) tupleRange _ = + let choose (expr: SynExpr) (nextExpr: SynExpr) = + if expr.Range.EndLine = nextExpr.Range.StartLine && expr.Range.EndColumn + 2 <> nextExpr.Range.StartColumn then + let commaRange = Range.mkRange String.Empty expr.Range.End nextExpr.Range.Start + let map commaText = + lazy + (Some + { + FromRange = commaRange + FromText = commaText + ToText = ", " + }) + let suggestedFix = + ExpressionUtilities.tryFindTextOfRange commaRange args.FileContent + |> Option.map map + + Some + { + Range = commaRange + Message = Resources.GetString("RulesFormattingTupleCommaSpacingError") + SuggestedFix = suggestedFix + TypeChecks = List.Empty + } + else + None + tupleExprs |> List.toArray |> Array.pairwise - |> Array.choose (fun (expr, nextExpr) -> - if expr.Range.EndLine = nextExpr.Range.StartLine && expr.Range.EndColumn + 2 <> nextExpr.Range.StartColumn then - let commaRange = Range.mkRange "" expr.Range.End nextExpr.Range.Start - let suggestedFix = - ExpressionUtilities.tryFindTextOfRange commaRange args.FileContent - |> Option.map (fun commaText -> - lazy( - { FromRange = commaRange - FromText = commaText - ToText = ", " } |> Some - ) ) - { Range = commaRange - Message = Resources.GetString("RulesFormattingTupleCommaSpacingError") - SuggestedFix = suggestedFix - TypeChecks = [] } |> Some - else - None) + |> Array.choose (fun (expr, nextExpr) -> choose expr nextExpr) let runner (args:AstNodeRuleParams) = TupleFormatting.isActualTuple args checkTupleCommaSpacing let rule = - { Name = "TupleCommaSpacing" - Identifier = Identifiers.TupleCommaSpacing - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "TupleCommaSpacing" + Identifier = Identifiers.TupleCommaSpacing + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleIndentation.fs b/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleIndentation.fs index d80ba107b..d90b024b1 100644 --- a/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleIndentation.fs +++ b/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleIndentation.fs @@ -12,24 +12,35 @@ open FSharpLint.Rules.Helper // Check that tuple items on separate lines have consistent indentation. let checkTupleIndentation _ (tupleExprs:SynExpr list) _ _ = + let choose (expr: SynExpr) (nextExpr: SynExpr) = + if expr.Range.StartColumn <> nextExpr.Range.StartColumn then + Some + { + Range = Range.mkRange "" expr.Range.Start nextExpr.Range.End + Message = Resources.GetString("RulesFormattingTupleIndentationError") + SuggestedFix = None + TypeChecks = List.Empty + } + else + None + tupleExprs |> List.toArray |> Array.groupBy (fun expr -> expr.Range.StartLine) |> Array.choose (snd >> Array.tryHead) |> Array.pairwise - |> Array.choose (fun (expr, nextExpr) -> - if expr.Range.StartColumn <> nextExpr.Range.StartColumn then - { Range = Range.mkRange "" expr.Range.Start nextExpr.Range.End - Message = Resources.GetString("RulesFormattingTupleIndentationError") - SuggestedFix = None - TypeChecks = [] } |> Some - else - None) + |> Array.choose (fun (expr, nextExpr) -> choose expr nextExpr) let runner (args:AstNodeRuleParams) = TupleFormatting.isActualTuple args checkTupleIndentation let rule = - { Name = "TupleIndentation" - Identifier = Identifiers.TupleIndentation - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "TupleIndentation" + Identifier = Identifiers.TupleIndentation + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleParentheses.fs b/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleParentheses.fs index 23d96ddaf..d388940b5 100644 --- a/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleParentheses.fs +++ b/src/FSharpLint.Core/Rules/Formatting/TupleFormatting/TupleParentheses.fs @@ -9,25 +9,41 @@ open FSharpLint.Framework.Rules open FSharpLint.Rules.Helper let checkTupleHasParentheses (args:AstNodeRuleParams) _ range parentNode = + let processText text = + let suggestedFix = + lazy + (Some + { + FromRange = range + FromText = text + ToText = $"({text})" + }) + + { + Range = range + Message = Resources.GetString("RulesFormattingTupleParenthesesError") + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty + } + match parentNode with | Some (AstNode.Expression (SynExpr.Paren _)) -> Array.empty | _ -> ExpressionUtilities.tryFindTextOfRange range args.FileContent - |> Option.map (fun text -> - let suggestedFix = lazy( - { FromRange = range; FromText = text; ToText = $"({text})" } - |> Some) - { Range = range - Message = Resources.GetString("RulesFormattingTupleParenthesesError") - SuggestedFix = Some suggestedFix - TypeChecks = [] }) + |> Option.map processText |> Option.toArray let runner (args:AstNodeRuleParams) = TupleFormatting.isActualTuple args checkTupleHasParentheses let rule = - { Name = "TupleParentheses" - Identifier = Identifiers.TupleParentheses - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "TupleParentheses" + Identifier = Identifiers.TupleParentheses + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/TypePrefixing.fs b/src/FSharpLint.Core/Rules/Formatting/TypePrefixing.fs index 3cae5dda8..01b51ad1a 100644 --- a/src/FSharpLint.Core/Rules/Formatting/TypePrefixing.fs +++ b/src/FSharpLint.Core/Rules/Formatting/TypePrefixing.fs @@ -16,20 +16,33 @@ type Mode = [] type Config = { Mode: Mode } -let checkTypePrefixing (config:Config) (args:AstNodeRuleParams) range typeName typeArgs isPostfix = +type CheckTypePrefixingConfig = + { + Config: Config + Args: AstNodeRuleParams + Range: FSharp.Compiler.Text.Range + TypeName: SynType + TypeArgs: string option + IsPostfix: bool + } + +let checkTypePrefixing (typePrefixingConfig: CheckTypePrefixingConfig) = let recommendPostfixErrMsg = lazy(Resources.GetString("RulesFormattingF#PostfixGenericError")) - match typeName with + match typePrefixingConfig.TypeName with | SynType.LongIdent lid -> let prefixSuggestion typeName = let suggestedFix = lazy( - (ExpressionUtilities.tryFindTextOfRange range args.FileContent, typeArgs) - ||> Option.map2 (fun fromText typeArgs -> { FromText = fromText; FromRange = range; ToText = $"{typeName}<{typeArgs}>" })) - { Range = range - Message = Resources.GetString("RulesFormattingGenericPrefixError") - SuggestedFix = Some suggestedFix - TypeChecks = [] } |> Some + (ExpressionUtilities.tryFindTextOfRange typePrefixingConfig.Range typePrefixingConfig.Args.FileContent, typePrefixingConfig.TypeArgs) + ||> Option.map2 (fun fromText typeArgs -> { FromText = fromText; FromRange = typePrefixingConfig.Range; ToText = $"{typeName}<{typeArgs}>" })) + Some + { + Range = typePrefixingConfig.Range + Message = Resources.GetString("RulesFormattingGenericPrefixError") + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty + } - match lid |> longIdentWithDotsToString with + match longIdentWithDotsToString lid with | "list" | "List" | "option" @@ -38,43 +51,52 @@ let checkTypePrefixing (config:Config) (args:AstNodeRuleParams) range typeName t | "Ref" as typeName -> // Prefer postfix. - if not isPostfix && config.Mode <> Mode.Always + if not typePrefixingConfig.IsPostfix && typePrefixingConfig.Config.Mode <> Mode.Always then let suggestedFix = lazy( - (ExpressionUtilities.tryFindTextOfRange range args.FileContent, typeArgs) - ||> Option.map2 (fun fromText typeArgs -> { FromText = fromText; FromRange = range; ToText = $"{typeArgs} {typeName}" })) - { Range = range - Message = String.Format(recommendPostfixErrMsg.Value, typeName) - SuggestedFix = Some suggestedFix - TypeChecks = [] } |> Some + (ExpressionUtilities.tryFindTextOfRange typePrefixingConfig.Range typePrefixingConfig.Args.FileContent, typePrefixingConfig.TypeArgs) + ||> Option.map2 (fun fromText typeArgs -> { FromText = fromText; FromRange = typePrefixingConfig.Range; ToText = $"{typeArgs} {typeName}" })) + Some + { + Range = typePrefixingConfig.Range + Message = String.Format(recommendPostfixErrMsg.Value, typeName) + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty + } else - if isPostfix && config.Mode = Mode.Always then + if typePrefixingConfig.IsPostfix && typePrefixingConfig.Config.Mode = Mode.Always then prefixSuggestion typeName else None - | "array" when config.Mode <> Mode.Always -> + | "array" when typePrefixingConfig.Config.Mode <> Mode.Always -> // Prefer special postfix (e.g. int []). let suggestedFix = lazy( - (ExpressionUtilities.tryFindTextOfRange range args.FileContent, typeArgs) - ||> Option.map2 (fun fromText typeArgs -> { FromText = fromText; FromRange = range; ToText = $"{typeArgs} []" })) - { Range = range - Message = Resources.GetString("RulesFormattingF#ArrayPostfixError") - SuggestedFix = Some suggestedFix - TypeChecks = [] } |> Some + (ExpressionUtilities.tryFindTextOfRange typePrefixingConfig.Range typePrefixingConfig.Args.FileContent, typePrefixingConfig.TypeArgs) + ||> Option.map2 (fun fromText typeArgs -> { FromText = fromText; FromRange = typePrefixingConfig.Range; ToText = $"{typeArgs} []" })) + Some + { + Range = typePrefixingConfig.Range + Message = Resources.GetString("RulesFormattingF#ArrayPostfixError") + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty + } | typeName -> - match (isPostfix, config.Mode) with + match (typePrefixingConfig.IsPostfix, typePrefixingConfig.Config.Mode) with | true, Mode.Never -> None | true, _ -> prefixSuggestion typeName | false, Mode.Never -> - { Range = range - Message = String.Format(recommendPostfixErrMsg.Value, typeName) - // TODO - SuggestedFix = None - TypeChecks = List.Empty } |> Some + Some + { + Range = typePrefixingConfig.Range + Message = String.Format(recommendPostfixErrMsg.Value, typeName) + // TODO + SuggestedFix = None + TypeChecks = List.Empty + } | false, _ -> None | _ -> @@ -84,20 +106,34 @@ let runner (config:Config) args = match args.AstNode with | AstNode.Type (SynType.App (typeName, _, typeArgs, _, _, isPostfix, range)) -> let typeArgs = typeArgsToString args.FileContent typeArgs - checkTypePrefixing config args range typeName typeArgs isPostfix + checkTypePrefixing + { + Config = config + Args = args + Range = range + TypeName = typeName + TypeArgs = typeArgs + IsPostfix = isPostfix + } |> Option.toArray | AstNode.Type (SynType.Array (1, _elementType, range)) when config.Mode = Mode.Always -> - { Range = range - Message = Resources.GetString("RulesFormattingF#ArrayPrefixError") - SuggestedFix = None - TypeChecks = List.Empty } - |> Array.singleton + Array.singleton + { Range = range + Message = Resources.GetString("RulesFormattingF#ArrayPrefixError") + SuggestedFix = None + TypeChecks = List.Empty } | _ -> Array.empty let rule config = - { Name = "TypePrefixing" - Identifier = Identifiers.TypePrefixing - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "TypePrefixing" + Identifier = Identifiers.TypePrefixing + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/TypedItemSpacing.fs b/src/FSharpLint.Core/Rules/Formatting/TypedItemSpacing.fs index a28fd51f4..683e436a9 100644 --- a/src/FSharpLint.Core/Rules/Formatting/TypedItemSpacing.fs +++ b/src/FSharpLint.Core/Rules/Formatting/TypedItemSpacing.fs @@ -16,21 +16,21 @@ type TypedItemStyle = [] type Config = { TypedItemStyle:TypedItemStyle } -let private getLeadingSpaces (s:string) = - let rec loop i = - if i < s.Length && s.[i] = ' ' - then loop (i + 1) - else i +let private getLeadingSpaces (text:string) = + let rec loop index = + if index < text.Length && text.[index] = ' ' + then loop (index + 1) + else index loop 0 -let private getTrailingSpaces (s:string) = - let rec loop i count = - if i >= 0 && s.[i] = ' ' - then loop (i - 1) (count + 1) +let private getTrailingSpaces (text:string) = + let rec loop index count = + if index >= 0 && text.[index] = ' ' + then loop (index - 1) (count + 1) else count - (loop (s.Length - 1) 0) + (loop (text.Length - 1) 0) let private expectedSpacesFromConfig (typedItemStyle:TypedItemStyle) = match typedItemStyle with @@ -44,8 +44,7 @@ let private checkRange (config:Config) (args:AstNodeRuleParams) (range:Range) = let (expectedSpacesBefore, expectedSpacesAfter) = expectedSpacesFromConfig config.TypedItemStyle - ExpressionUtilities.tryFindTextOfRange range args.FileContent - |> Option.bind (fun text -> + let bind (text: string) = match text.Split(':') with | [|otherText; typeText|] -> let spacesBeforeColon = getTrailingSpaces otherText @@ -56,17 +55,24 @@ let private checkRange (config:Config) (args:AstNodeRuleParams) (range:Range) = let spacesBeforeString = " " |> String.replicate expectedSpacesBefore let spacesAfterString = " " |> String.replicate expectedSpacesAfter let suggestedFix = lazy( - { FromRange = range; FromText = text; ToText = $"{trimmedOtherText}{spacesBeforeString}:{spacesAfterString}{trimmedTypeText}" } - |> Some) + Some { FromRange = range; + FromText = text; + ToText = $"{trimmedOtherText}{spacesBeforeString}:{spacesAfterString}{trimmedTypeText}" } + ) let errorFormatString = Resources.GetString("RulesFormattingTypedItemSpacingError") Some - { Range = range - Message = String.Format(errorFormatString, expectedSpacesBefore, expectedSpacesAfter) - SuggestedFix = Some suggestedFix - TypeChecks = [] } + { + Range = range + Message = String.Format(errorFormatString, expectedSpacesBefore, expectedSpacesAfter) + SuggestedFix = Some suggestedFix + TypeChecks = List.Empty + } else None - | _ -> None) + | _ -> None + + ExpressionUtilities.tryFindTextOfRange range args.FileContent + |> Option.bind bind /// Checks for correct spacing around colon of a typed item. let runner (config:Config) (args:AstNodeRuleParams) = @@ -76,10 +82,16 @@ let runner (config:Config) (args:AstNodeRuleParams) = // NOTE: This currently does not work for fields in union cases, since the range on union case fields is incorrect, // only including the type and not the field name. (https://github.com/dotnet/fsharp/issues/9279) checkRange config args range |> Option.toArray - | _ -> [||] + | _ -> Array.empty let rule config = - { Name = "TypedItemSpacing" - Identifier = Identifiers.TypedItemSpacing - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule \ No newline at end of file + AstNodeRule + { + Name = "TypedItemSpacing" + Identifier = Identifiers.TypedItemSpacing + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Formatting/UnionDefinitionIndentation.fs b/src/FSharpLint.Core/Rules/Formatting/UnionDefinitionIndentation.fs index 1dd6465cd..5a4aa75bd 100644 --- a/src/FSharpLint.Core/Rules/Formatting/UnionDefinitionIndentation.fs +++ b/src/FSharpLint.Core/Rules/Formatting/UnionDefinitionIndentation.fs @@ -8,7 +8,7 @@ open FSharpLint.Framework.Ast open FSharpLint.Framework.Rules let getUnionCaseStartColumn (SynUnionCase (attrs, _, _, _, _, range, _)) = - match attrs |> List.tryHead with + match List.tryHead attrs with | Some attr -> // startcolumn of the attributes now includes the `[<` starter sigil, so we can just use it! attr.Range.StartColumn @@ -24,31 +24,39 @@ let checkUnionDefinitionIndentation (args:AstNodeRuleParams) typeDefnRepr typeDe | firstCase :: _ -> let indentationLevelError = if getUnionCaseStartColumn firstCase - 2 <> typeDefnStartColumn + args.GlobalConfig.numIndentationSpaces then - { Range = firstCase.Range - Message = Resources.GetString("RulesFormattingUnionDefinitionIndentationError") - SuggestedFix = None - TypeChecks = [] } |> Some + Some + { + Range = firstCase.Range + Message = Resources.GetString("RulesFormattingUnionDefinitionIndentationError") + SuggestedFix = None + TypeChecks = List.Empty + } else None let consistentIndentationErrors = + let choose caseOne caseTwo = + if getUnionCaseStartColumn caseOne <> getUnionCaseStartColumn caseTwo then + Some + { + Range = caseTwo.Range + Message = Resources.GetString("RulesFormattingUnionDefinitionSameIndentationError") + SuggestedFix = None + TypeChecks = List.Empty + } + else + None + cases |> List.toArray |> Array.pairwise - |> Array.choose (fun (caseOne, caseTwo) -> - if getUnionCaseStartColumn caseOne <> getUnionCaseStartColumn caseTwo then - { Range = caseTwo.Range - Message = Resources.GetString("RulesFormattingUnionDefinitionSameIndentationError") - SuggestedFix = None - TypeChecks = [] } |> Some - else - None) + |> Array.choose (fun (caseOne, caseTwo) -> choose caseOne caseTwo) - [| - indentationLevelError |> Option.toArray - consistentIndentationErrors - |] - |> Array.concat + Array.concat + [| + Option.toArray indentationLevelError + consistentIndentationErrors + |] | _ -> Array.empty let runner args = @@ -62,7 +70,13 @@ let runner args = Array.empty let rule = - { Name = "UnionDefinitionIndentation" - Identifier = Identifiers.UnionDefinitionIndentation - RuleConfig = { AstNodeRuleConfig.Runner = runner; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "UnionDefinitionIndentation" + Identifier = Identifiers.UnionDefinitionIndentation + RuleConfig = + { + AstNodeRuleConfig.Runner = runner + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Hints/HintMatcher.fs b/src/FSharpLint.Core/Rules/Hints/HintMatcher.fs index 0860507bc..24030523b 100644 --- a/src/FSharpLint.Core/Rules/Hints/HintMatcher.fs +++ b/src/FSharpLint.Core/Rules/Hints/HintMatcher.fs @@ -13,6 +13,17 @@ open FSharpLint.Framework.Ast open FSharpLint.Framework.ExpressionUtilities open FSharpLint.Framework.HintParser open FSharpLint.Framework.Rules +open FSharpLint.Framework.HintParserTypes + +type ToStringConfig = + { + Replace: bool + ParentAstNode: AstNode option + Args: AstNodeRuleParams + MatchedVariables: Dictionary + ParentHintNode: option + HintNode: HintNode + } type Config = { HintTrie:MergeSyntaxTrees.Edges } @@ -50,7 +61,7 @@ type private LambdaMatch = | Match of Map | NoMatch -let private matchLambdaArguments (hintArgs:HintParser.LambdaArg list) (actualArgs:SynSimplePats list) = +let private matchLambdaArguments (hintArgs:HintParserTypes.LambdaArg list) (actualArgs:SynSimplePats list) = if List.length hintArgs <> List.length actualArgs then LambdaMatch.NoMatch else @@ -59,10 +70,9 @@ let private matchLambdaArguments (hintArgs:HintParser.LambdaArg list) (actualArg |> List.map matchLambdaArgument let allArgsMatch = - matches - |> List.forall (function + List.forall (function | LambdaArgumentMatch.NoMatch -> false - | _ -> true) + | _ -> true) matches if allArgsMatch then matches @@ -76,29 +86,29 @@ let private matchLambdaArguments (hintArgs:HintParser.LambdaArg list) (actualArg /// Converts a SynConst (FSharp AST) into a Constant (hint AST). let private matchConst = function - | SynConst.Bool(x) -> Some(Constant.Bool(x)) - | SynConst.Int16(x) -> Some(Constant.Int16(x)) - | SynConst.Int32(x) -> Some(Constant.Int32(x)) - | SynConst.Int64(x) -> Some(Constant.Int64(x)) - | SynConst.UInt16(x) -> Some(Constant.UInt16(x)) - | SynConst.UInt32(x) -> Some(Constant.UInt32(x)) - | SynConst.UInt64(x) -> Some(Constant.UInt64(x)) - | SynConst.Byte(x) -> Some(Constant.Byte(x)) - | SynConst.Bytes(x, _, _) -> Some(Constant.Bytes(x)) - | SynConst.Char(x) -> Some(Constant.Char(x)) - | SynConst.Decimal(x) -> Some(Constant.Decimal(x)) - | SynConst.Double(x) -> Some(Constant.Double(x)) - | SynConst.SByte(x) -> Some(Constant.SByte(x)) - | SynConst.Single(x) -> Some(Constant.Single(x)) - | SynConst.String(x, _, _) -> Some(Constant.String(x)) - | SynConst.UIntPtr(x) -> Some(Constant.UIntPtr(unativeint x)) - | SynConst.IntPtr(x) -> Some(Constant.IntPtr(nativeint x)) - | SynConst.UserNum(x, endChar) -> - Some(Constant.UserNum(System.Numerics.BigInteger.Parse(x), endChar.[0])) - | SynConst.Unit -> Some(Constant.Unit) - | SynConst.UInt16s(_) + | SynConst.Bool value -> Some(Constant.Bool value) + | SynConst.Int16 value -> Some(Constant.Int16 value) + | SynConst.Int32 value -> Some(Constant.Int32 value) + | SynConst.Int64 value -> Some(Constant.Int64 value) + | SynConst.UInt16 value -> Some(Constant.UInt16 value) + | SynConst.UInt32 value -> Some(Constant.UInt32 value) + | SynConst.UInt64 value -> Some(Constant.UInt64 value) + | SynConst.Byte value -> Some(Constant.Byte value) + | SynConst.Bytes (value, _, _) -> Some(Constant.Bytes value) + | SynConst.Char value -> Some(Constant.Char value) + | SynConst.Decimal value -> Some(Constant.Decimal value) + | SynConst.Double value -> Some(Constant.Double value) + | SynConst.SByte value -> Some(Constant.SByte value) + | SynConst.Single value -> Some(Constant.Single value) + | SynConst.String (value, _, _) -> Some(Constant.String value) + | SynConst.UIntPtr value -> Some(Constant.UIntPtr(unativeint value)) + | SynConst.IntPtr value -> Some(Constant.IntPtr(nativeint value)) + | SynConst.UserNum (value, endChar) -> + Some(Constant.UserNum(System.Numerics.BigInteger.Parse(value), endChar.[0])) + | SynConst.Unit -> Some Constant.Unit + | SynConst.UInt16s _ | SynConst.SourceIdentifier _ - | SynConst.Measure(_) -> None + | SynConst.Measure _ -> None module private Precedence = let private ofHint hint = @@ -155,8 +165,8 @@ module private MatchExpression = /// Extracts an expression from parentheses e.g. ((x + 4)) -> x + 4 let rec private removeParens = function - | AstNode.Expression(SynExpr.Paren(x, _, _, _)) -> x |> AstNode.Expression |> removeParens - | x -> x + | AstNode.Expression(SynExpr.Paren(expr, _, _, _)) -> expr |> AstNode.Expression |> removeParens + | node -> node [] type Arguments = @@ -176,7 +186,7 @@ module private MatchExpression = let ident = identAsDecompiledOpName ident Some(Expression.Identifier([ident])) | AstNode.Expression(SynExpr.LongIdent(_, ident, _, _)) -> - let identifier = ident.LongIdent |> List.map (fun x -> x.idText) + let identifier = List.map (fun (ident: Ident) -> ident.idText) ident.LongIdent Some(Expression.Identifier(identifier)) | AstNode.Expression(SynExpr.Const(constant, _)) -> matchConst constant |> Option.map Expression.Constant @@ -206,6 +216,8 @@ module private MatchExpression = | Match of (unit -> bool) list | NoMatch + let internal returnEmptyMatch () = Match List.Empty + let private (&&~) lhs rhs = match (lhs, rhs) with | Match(asyncLhs), Match(asyncRhs) -> Match(asyncLhs @ asyncRhs) @@ -218,19 +230,20 @@ module private MatchExpression = | ExpressionUtilities.Identifier([ident], _), ExpressionUtilities.Identifier([opIdent], _) when opIdent.idText = "op_Equality" -> match arguments.FSharpCheckFileResults with | Some(checkFile) -> - fun () -> + let checkSymbol () = let symbolUse = checkFile.GetSymbolUseAtLocation( - ident.idRange.StartLine, ident.idRange.EndColumn, "", [ident.idText]) + ident.idRange.StartLine, ident.idRange.EndColumn, String.Empty, [ident.idText]) match symbolUse with | Some(symbolUse) -> match symbolUse.Symbol with | :? FSharpParameter | :? FSharpField -> false - | :? FSharpMemberOrFunctionOrValue as x -> not x.IsProperty + | :? FSharpMemberOrFunctionOrValue as element -> not element.IsProperty | _ -> true | None -> true + checkSymbol |> List.singleton |> Match | None -> @@ -238,34 +251,36 @@ module private MatchExpression = match filterParens arguments.Breadcrumbs with | PossiblyInMethod | PossiblyInConstructor -> NoMatch - | _ -> Match([]) - | _ -> Match([]) + | _ -> Match(List.Empty) + | _ -> Match(List.Empty) - let rec matchHintExpr arguments = + [] + let rec matchHintExpr (continuation: unit -> HintMatch) arguments = let expr = removeParens arguments.Expression let arguments = { arguments with Expression = expr } + (continuation ()) &&~ match arguments.Hint with - | Expression.Variable(variable) when arguments.LambdaArguments |> Map.containsKey variable -> + | Expression.Variable(variable) when Map.containsKey variable arguments.LambdaArguments -> match expr with | AstNode.Expression(ExpressionUtilities.Identifier([identifier], _)) when identifier.idText = arguments.LambdaArguments.[variable] -> - Match([]) + Match(List.Empty) | _ -> NoMatch | Expression.Variable(var) -> match expr with - | AstNode.Expression(expr) -> arguments.MatchedVariables.TryAdd(var, expr) |> ignore + | AstNode.Expression(expr) -> arguments.MatchedVariables.TryAdd(var, expr) |> ignore | _ -> () - Match([]) + Match(List.Empty) | Expression.Wildcard -> - Match([]) + Match(List.Empty) | Expression.Null | Expression.Constant(_) | Expression.Identifier(_) -> - if matchExpr expr = Some(arguments.Hint) then Match([]) + if matchExpr expr = Some(arguments.Hint) then Match(List.Empty) else NoMatch | Expression.Parentheses(hint) -> - arguments.SubHint(expr, hint) |> matchHintExpr + arguments.SubHint(expr, hint) |> matchHintExpr returnEmptyMatch | Expression.Tuple(_) -> matchTuple arguments | Expression.List(_) -> @@ -287,106 +302,130 @@ module private MatchExpression = | Expression.LambdaBody(_) -> NoMatch | Expression.Else(_) -> NoMatch - and private matchFunctionApplication arguments = + and [] private matchFunctionApplication arguments = match (arguments.Expression, arguments.Hint) with | FuncApp(exprs, _), Expression.FunctionApplication(hintExprs) -> - let expressions = exprs |> List.map AstNode.Expression + let expressions = List.map AstNode.Expression exprs doExpressionsMatch expressions hintExprs arguments | _ -> NoMatch - and private doExpressionsMatch expressions hintExpressions (arguments:Arguments) = + and [] private doExpressionsMatch expressions hintExpressions (arguments:Arguments) = if List.length expressions = List.length hintExpressions then - (expressions, hintExpressions) - ||> List.map2 (fun x y -> arguments.SubHint(x, y) |> matchHintExpr) - |> List.fold (&&~) (Match([])) + let subHints = + (expressions, hintExpressions) + ||> List.map2 (fun expr hint -> arguments.SubHint(expr, hint)) + + let rec innerDoExpressionsMatch args: HintMatch = + match args with + | head::tail -> + head |> matchHintExpr (fun () -> innerDoExpressionsMatch tail) + | [] -> Match(List.Empty) + + innerDoExpressionsMatch subHints else NoMatch - and private matchIf arguments = + and [] private matchIf arguments = match (arguments.Expression, arguments.Hint) with | (AstNode.Expression(SynExpr.IfThenElse(cond, expr, None, _, _, _, _)), Expression.If(hintCond, hintExpr, None)) -> - arguments.SubHint(Expression cond, hintCond) |> matchHintExpr &&~ - (arguments.SubHint(Expression expr, hintExpr) |> matchHintExpr) + matchHintExpr + (fun () -> (arguments.SubHint(Expression expr, hintExpr) |> matchHintExpr returnEmptyMatch)) + (arguments.SubHint(Expression cond, hintCond)) | (AstNode.Expression(SynExpr.IfThenElse(cond, expr, Some(elseExpr), _, _, _, _)), Expression.If(hintCond, hintExpr, Some(Expression.Else(hintElseExpr)))) -> - arguments.SubHint(Expression cond, hintCond) |> matchHintExpr &&~ - (arguments.SubHint(Expression expr, hintExpr) |> matchHintExpr) &&~ - (arguments.SubHint(Expression elseExpr, hintElseExpr) |> matchHintExpr) + matchHintExpr + (fun () -> + matchHintExpr + (fun () -> arguments.SubHint(Expression expr, hintExpr) |> matchHintExpr returnEmptyMatch) + (arguments.SubHint(Expression elseExpr, hintElseExpr))) + (arguments.SubHint(Expression cond, hintCond)) + | _ -> NoMatch - and matchLambda arguments = + and [] matchLambda arguments = match (arguments.Expression, arguments.Hint) with | Lambda({ Arguments = args; Body = body }, _), Expression.Lambda(lambdaArgs, LambdaBody(Expression.LambdaBody(lambdaBody))) -> match matchLambdaArguments lambdaArgs args with | LambdaMatch.Match(lambdaArguments) -> - matchHintExpr { arguments.SubHint(AstNode.Expression(body), lambdaBody) with LambdaArguments = lambdaArguments } + matchHintExpr + returnEmptyMatch + { arguments.SubHint(AstNode.Expression(body), lambdaBody) with LambdaArguments = lambdaArguments } | LambdaMatch.NoMatch -> NoMatch | _ -> NoMatch - and private matchTuple arguments = + and [] private matchTuple arguments = match (arguments.Expression, arguments.Hint) with | AstNode.Expression(SynExpr.Tuple(_, expressions, _, _)), Expression.Tuple(hintExpressions) -> let expressions = List.map AstNode.Expression expressions doExpressionsMatch expressions hintExpressions arguments | _ -> NoMatch - and private matchList arguments = + and [] private matchList arguments = match (arguments.Expression, arguments.Hint) with | AstNode.Expression(SynExpr.ArrayOrList(false, expressions, _)), Expression.List(hintExpressions) -> let expressions = List.map AstNode.Expression expressions doExpressionsMatch expressions hintExpressions arguments | AstNode.Expression(SynExpr.ArrayOrListComputed(false, expression, _)), Expression.List([hintExpression]) -> - arguments.SubHint(AstNode.Expression(expression), hintExpression) |> matchHintExpr + arguments.SubHint(AstNode.Expression(expression), hintExpression) |> matchHintExpr returnEmptyMatch | _ -> NoMatch - and private matchArray arguments = + and [] private matchArray arguments = match (arguments.Expression, arguments.Hint) with | AstNode.Expression(SynExpr.ArrayOrList(true, expressions, _)), Expression.Array(hintExpressions) -> let expressions = List.map AstNode.Expression expressions doExpressionsMatch expressions hintExpressions arguments | AstNode.Expression(SynExpr.ArrayOrListComputed(true, expression, _)), Expression.Array([hintExpression]) -> - arguments.SubHint(AstNode.Expression(expression), hintExpression) |> matchHintExpr + arguments.SubHint(AstNode.Expression(expression), hintExpression) |> matchHintExpr returnEmptyMatch | _ -> NoMatch - and private matchInfixOperation arguments = + and [] private matchInfixOperation arguments = match (arguments.Expression, arguments.Hint) with | (AstNode.Expression(SynExpr.App(_, true, (ExpressionUtilities.Identifier(_) as opExpr), SynExpr.Tuple(_, [leftExpr; rightExpr], _, _), _)), Expression.InfixOperator(op, left, right)) -> - arguments.SubHint(AstNode.Expression(opExpr), op) |> matchHintExpr &&~ - (arguments.SubHint(AstNode.Expression(rightExpr), right) |> matchHintExpr) &&~ - (arguments.SubHint(AstNode.Expression(leftExpr), left) |> matchHintExpr) + matchHintExpr + (fun () -> + matchHintExpr + (fun () -> arguments.SubHint(AstNode.Expression(rightExpr), right) |> matchHintExpr returnEmptyMatch) + (arguments.SubHint(AstNode.Expression(leftExpr), left))) + (arguments.SubHint(AstNode.Expression(opExpr), op)) | (AstNode.Expression(SynExpr.App(_, _, infixExpr, rightExpr, _)), Expression.InfixOperator(op, left, right)) -> match removeParens <| AstNode.Expression(infixExpr) with | AstNode.Expression(SynExpr.App(_, true, opExpr, leftExpr, _)) -> - arguments.SubHint(AstNode.Expression(opExpr), op) |> matchHintExpr &&~ - (arguments.SubHint(AstNode.Expression(leftExpr), left) |> matchHintExpr) &&~ - (arguments.SubHint(AstNode.Expression(rightExpr), right) |> matchHintExpr) &&~ - notPropertyInitialisationOrNamedParameter arguments leftExpr opExpr + matchHintExpr + (fun () -> + matchHintExpr + (fun () -> + matchHintExpr + (fun () -> notPropertyInitialisationOrNamedParameter arguments leftExpr opExpr) + (arguments.SubHint(AstNode.Expression(rightExpr), right))) + (arguments.SubHint(AstNode.Expression(leftExpr), left))) + (arguments.SubHint(AstNode.Expression(opExpr), op)) | _ -> NoMatch | _ -> NoMatch - and private matchPrefixOperation arguments = + and [] private matchPrefixOperation arguments = match (arguments.Expression, arguments.Hint) with | (AstNode.Expression(SynExpr.App(_, _, opExpr, rightExpr, _)), Expression.PrefixOperator(Expression.Identifier([op]), expr)) -> - arguments.SubHint(AstNode.Expression(opExpr), Expression.Identifier([op])) |> matchHintExpr &&~ - (arguments.SubHint(AstNode.Expression(rightExpr), expr) |> matchHintExpr) + matchHintExpr + (fun () -> arguments.SubHint(AstNode.Expression(rightExpr), expr) |> matchHintExpr returnEmptyMatch) + (arguments.SubHint(AstNode.Expression(opExpr), Expression.Identifier([op]))) | _ -> NoMatch - and private matchAddressOf arguments = + and [] private matchAddressOf arguments = match (arguments.Expression, arguments.Hint) with | AstNode.Expression(SynExpr.AddressOf(synSingleAmp, addrExpr, _, _)), Expression.AddressOf(singleAmp, expr) when synSingleAmp = singleAmp -> - arguments.SubHint(AstNode.Expression(addrExpr), expr) |> matchHintExpr + arguments.SubHint(AstNode.Expression(addrExpr), expr) |> matchHintExpr returnEmptyMatch | _ -> NoMatch module private MatchPattern = let private matchPattern = function | SynPat.LongIdent(ident, _, _, _, _, _) -> - let identifier = ident.LongIdent |> List.map (fun x -> x.idText) + let identifier = List.map (fun (ident: Ident) -> ident.idText) (ident.LongIdent) Some(Pattern.Identifier(identifier)) | SynPat.Const(constant, _) -> matchConst constant |> Option.map Pattern.Constant @@ -396,12 +435,16 @@ module private MatchPattern = /// Extracts a pattern from parentheses e.g. ((x)) -> x let rec private removeParens = function - | SynPat.Paren(x, _) -> removeParens x - | x -> x + | SynPat.Paren(pattern, _) -> removeParens pattern + | pat -> pat + + let internal returnTrue () = true - let rec matchHintPattern (pattern, hint) = + [] + let rec matchHintPattern (continuation: unit -> bool) (pattern, hint) = let pattern = removeParens pattern + (continuation ()) && match hint with | Pattern.Variable(_) | Pattern.Wildcard -> @@ -415,7 +458,7 @@ module private MatchPattern = | Pattern.Or(_) -> matchOrPattern (pattern, hint) | Pattern.Parentheses(hint) -> - matchHintPattern (pattern, hint) + matchHintPattern returnTrue (pattern, hint) | Pattern.Tuple(_) -> matchTuple (pattern, hint) | Pattern.List(_) -> @@ -423,60 +466,74 @@ module private MatchPattern = | Pattern.Array(_) -> matchArray (pattern, hint) - and private doPatternsMatch patterns hintExpressions = - (List.length patterns = List.length hintExpressions) - && (patterns, hintExpressions) ||> List.forall2 (fun x y -> matchHintPattern (x, y)) + and [] private doPatternsMatch patterns hintExpressions = + if List.length patterns = List.length hintExpressions then + let rec innerDoPatternsMatch lst = + match lst with + | (pattern, hintExpression) :: tail -> + matchHintPattern + (fun () -> innerDoPatternsMatch tail) + (pattern, hintExpression) + | [] -> true + + innerDoPatternsMatch (List.zip patterns hintExpressions) + else + false - and private matchList (pattern, hint) = + and [] private matchList (pattern, hint) = match (pattern, hint) with | SynPat.ArrayOrList(false, patterns, _), Pattern.List(hintExpressions) -> doPatternsMatch patterns hintExpressions | _ -> false - and private matchArray (pattern, hint) = + and [] private matchArray (pattern, hint) = match (pattern, hint) with | SynPat.ArrayOrList(true, patterns, _), Pattern.Array(hintExpressions) -> doPatternsMatch patterns hintExpressions | _ -> false - and private matchTuple (pattern, hint) = + and [] private matchTuple (pattern, hint) = match (pattern, hint) with | SynPat.Tuple(_, patterns, _, _), Pattern.Tuple(hintExpressions) -> doPatternsMatch patterns hintExpressions | _ -> false - and private matchConsPattern (pattern, hint) = + and [] private matchConsPattern (pattern, hint) = match (pattern, hint) with | Cons(leftPattern, rightPattern), Pattern.Cons(left, right) -> - matchHintPattern (leftPattern, left) && matchHintPattern (rightPattern, right) + matchHintPattern + (fun () -> matchHintPattern returnTrue (rightPattern, right)) + (leftPattern, left) | _ -> false - and private matchOrPattern (pattern, hint) = + and [] private matchOrPattern (pattern, hint) = match (pattern, hint) with | SynPat.Or(leftPattern, rightPattern, _, _), Pattern.Or(left, right) -> - matchHintPattern (leftPattern, left) && matchHintPattern (rightPattern, right) + matchHintPattern + (fun () -> matchHintPattern returnTrue (rightPattern, right)) + (leftPattern, left) | _ -> false module private FormatHint = let private constantToString = function - | Constant.Bool(x) -> if x then "true" else "false" - | Constant.Int16(x) -> $"{x}s" - | Constant.Int32(x) -> $"{x}" - | Constant.Int64(x) -> $"{x}L" - | Constant.UInt16(x) -> $"{x}us" - | Constant.UInt32(x) -> $"{x}u" - | Constant.UInt64(x) -> $"{x}UL" - | Constant.Byte(x) -> $"{x}uy" - | Constant.Bytes(x) -> $"{x}" - | Constant.Char(x) -> $"'{x}'" - | Constant.Decimal(x) -> $"{x}m" - | Constant.Double(x) -> $"{x}" - | Constant.SByte(x) -> $"{x}y" - | Constant.Single(x) -> $"{x}f" - | Constant.String(x) -> $"\"{x}\"" - | Constant.UIntPtr(x) -> $"{x}" - | Constant.IntPtr(x) -> $"{x}" - | Constant.UserNum(x, _) -> $"{x}" + | Constant.Bool(value) -> if value then "true" else "false" + | Constant.Int16(value) -> $"{value}s" + | Constant.Int32(value) -> $"{value}" + | Constant.Int64(value) -> $"{value}L" + | Constant.UInt16(value) -> $"{value}us" + | Constant.UInt32(value) -> $"{value}u" + | Constant.UInt64(value) -> $"{value}UL" + | Constant.Byte(value) -> $"{value}uy" + | Constant.Bytes(value) -> $"{value}" + | Constant.Char(value) -> $"'{value}'" + | Constant.Decimal(value) -> $"{value}m" + | Constant.Double(value) -> $"{value}" + | Constant.SByte(value) -> $"{value}y" + | Constant.Single(value) -> $"{value}f" + | Constant.String(value) -> $"\"{value}\"" + | Constant.UIntPtr(value) -> $"{value}" + | Constant.IntPtr(value) -> $"{value}" + | Constant.UserNum(value, _) -> $"{value}" | Constant.Unit -> "()" let private surroundExpressionsString hintToString left right sep expressions = @@ -489,19 +546,31 @@ module private FormatHint = let private opToString = function | Expression.Identifier(identifier) -> String.concat "." identifier - | x -> - Debug.Assert(false, $"Expected operator to be an expression identifier, but was {x.ToString()}") - "" - - let rec toString replace parentAstNode (args:AstNodeRuleParams) (matchedVariables:Dictionary<_, SynExpr>) parentHintNode hintNode = - let toString = toString replace parentAstNode args matchedVariables (Some hintNode) + | expression -> + Debug.Assert(false, $"Expected operator to be an expression identifier, but was {expression.ToString()}") + String.Empty + + // very hard to turn into tail-recursive form because of the way it operates + // (convert sub-expressions to strings and then combine them) + // fsharplint:disable EnsureTailCallDiagnosticsInRecursiveFunctions + let rec toString (config: ToStringConfig) = + let toString hintNode = + toString + { + Replace = config.Replace + ParentAstNode = config.ParentAstNode + Args = config.Args + MatchedVariables = config.MatchedVariables + ParentHintNode = (Some config.HintNode) + HintNode = hintNode + } let str = - match hintNode with - | HintExpr(Expression.Variable(varChar)) when replace -> - match matchedVariables.TryGetValue varChar with + match config.HintNode with + | HintExpr(Expression.Variable(varChar)) when config.Replace -> + match config.MatchedVariables.TryGetValue varChar with | true, expr -> - match ExpressionUtilities.tryFindTextOfRange expr.Range args.FileContent with + match ExpressionUtilities.tryFindTextOfRange expr.Range config.Args.FileContent with | Some(replacement) -> replacement | _ -> varChar.ToString() | _ -> varChar.ToString() @@ -522,7 +591,7 @@ module private FormatHint = each) |> String.concat "." | HintExpr(Expression.FunctionApplication(expressions)) -> - expressions |> surroundExpressionsString (HintExpr >> toString) "" "" " " + surroundExpressionsString (HintExpr >> toString) String.Empty String.Empty " " expressions | HintExpr(Expression.InfixOperator(operator, leftHint, rightHint)) -> $"{toString (HintExpr leftHint)} {opToString operator} {toString (HintExpr rightHint)}" | HintPat(Pattern.Cons(leftHint, rightHint)) -> @@ -536,23 +605,23 @@ module private FormatHint = | HintExpr(Expression.Parentheses(hint)) -> $"({toString (HintExpr hint)})" | HintPat(Pattern.Parentheses(hint)) -> $"({toString (HintPat hint)})" | HintExpr(Expression.Lambda(arguments, LambdaBody(body))) -> - $"fun {lambdaArgumentsToString replace parentAstNode args matchedVariables arguments} -> {toString (HintExpr body)}" + $"fun {lambdaArgumentsToString config.Replace config.ParentAstNode config.Args config.MatchedVariables arguments} -> {toString (HintExpr body)}" | HintExpr(Expression.LambdaArg(argument)) -> toString (HintExpr argument) | HintExpr(Expression.LambdaBody(body)) -> toString (HintExpr body) | HintExpr(Expression.Tuple(expressions)) -> - expressions |> surroundExpressionsString (HintExpr >> toString) "(" ")" "," + surroundExpressionsString (HintExpr >> toString) "(" ")" "," expressions | HintExpr(Expression.List(expressions)) -> - expressions |> surroundExpressionsString (HintExpr >> toString) "[" "]" ";" + surroundExpressionsString (HintExpr >> toString) "[" "]" ";" expressions | HintExpr(Expression.Array(expressions)) -> - expressions |> surroundExpressionsString (HintExpr >> toString) "[|" "|]" ";" + surroundExpressionsString (HintExpr >> toString) "[|" "|]" ";" expressions | HintPat(Pattern.Tuple(expressions)) -> - expressions |> surroundExpressionsString (HintPat >> toString) "(" ")" "," + surroundExpressionsString (HintPat >> toString) "(" ")" "," expressions | HintPat(Pattern.List(expressions)) -> - expressions |> surroundExpressionsString (HintPat >> toString) "[" "]" ";" + surroundExpressionsString (HintPat >> toString) "[" "]" ";" expressions | HintPat(Pattern.Array(expressions)) -> - expressions |> surroundExpressionsString (HintPat >> toString) "[|" "|]" ";" + surroundExpressionsString (HintPat >> toString) "[|" "|]" ";" expressions | HintExpr(Expression.If(cond, expr, None)) -> $"if {toString (HintExpr cond)} then {toString (HintExpr expr)}" | HintExpr(Expression.If(cond, expr, Some(elseExpr))) -> @@ -561,47 +630,85 @@ module private FormatHint = $"else {toString (HintExpr expr)}" | HintExpr(Expression.Null) | HintPat(Pattern.Null) -> "null" - if replace && Precedence.requiresParenthesis matchedVariables hintNode parentAstNode parentHintNode then $"({str})" + if config.Replace && Precedence.requiresParenthesis config.MatchedVariables config.HintNode config.ParentAstNode config.ParentHintNode then $"({str})" else str and private lambdaArgumentsToString replace parentAstNode args matchedVariables (arguments:LambdaArg list) = + let exprToString expr = + toString + { + Replace = replace + ParentAstNode = parentAstNode + Args = args + MatchedVariables = matchedVariables + ParentHintNode = None + HintNode = (HintExpr expr) + } arguments - |> List.map (fun (LambdaArg expr) -> toString replace parentAstNode args matchedVariables None (HintExpr expr)) + |> List.map (fun (LambdaArg expr) -> exprToString expr) |> String.concat " " - -let private hintError typeChecks hint (args:AstNodeRuleParams) range matchedVariables parentAstNode = - let matched = FormatHint.toString false None args matchedVariables None hint.MatchedNode - - match hint.Suggestion with + // fsharplint:enable EnsureTailCallDiagnosticsInRecursiveFunctions + +type HintErrorConfig = + { + TypeChecks: (unit -> bool) list + Hint: Hint + Args: AstNodeRuleParams + Range: FSharp.Compiler.Text.Range + MatchedVariables: Dictionary + ParentAstNode: AstNode option + } + +let private hintError (config: HintErrorConfig) = + let toStringConfig = + { + ToStringConfig.Replace = false + ToStringConfig.ParentAstNode = None + ToStringConfig.Args = config.Args + ToStringConfig.MatchedVariables = config.MatchedVariables + ToStringConfig.ParentHintNode = None + ToStringConfig.HintNode = config.Hint.MatchedNode + } + + let matched = FormatHint.toString toStringConfig + + match config.Hint.Suggestion with | Suggestion.Expr(expr) -> - let suggestion = FormatHint.toString false None args matchedVariables None (HintExpr expr) + let suggestion = FormatHint.toString { toStringConfig with HintNode = (HintExpr expr) } let errorFormatString = Resources.GetString("RulesHintRefactor") let error = System.String.Format(errorFormatString, matched, suggestion) - let toText = FormatHint.toString true parentAstNode args matchedVariables None (HintExpr expr) + let toText = + FormatHint.toString + { + toStringConfig with + Replace = true + ParentAstNode = config.ParentAstNode + HintNode = (HintExpr expr) + } let suggestedFix = lazy( - ExpressionUtilities.tryFindTextOfRange range args.FileContent - |> Option.map (fun fromText -> { FromText = fromText; FromRange = range; ToText = toText })) + ExpressionUtilities.tryFindTextOfRange config.Range config.Args.FileContent + |> Option.map (fun fromText -> { FromText = fromText; FromRange = config.Range; ToText = toText })) - { Range = range; Message = error; SuggestedFix = Some suggestedFix; TypeChecks = typeChecks } + { Range = config.Range; Message = error; SuggestedFix = Some suggestedFix; TypeChecks = config.TypeChecks } | Suggestion.Message(message) -> let errorFormatString = Resources.GetString("RulesHintSuggestion") let error = System.String.Format(errorFormatString, matched, message) - { Range = range; Message = error; SuggestedFix = None; TypeChecks = typeChecks } + { Range = config.Range; Message = error; SuggestedFix = None; TypeChecks = config.TypeChecks } let private getMethodParameters (checkFile:FSharpCheckFileResults) (methodIdent: SynLongIdent) = let symbol = checkFile.GetSymbolUseAtLocation( methodIdent.Range.StartLine, methodIdent.Range.EndColumn, - "", - methodIdent.LongIdent |> List.map (fun x -> x.idText)) + String.Empty, + List.map (fun (ident: Ident) -> ident.idText) methodIdent.LongIdent) match symbol with | Some(symbol) when (symbol.Symbol :? FSharpMemberOrFunctionOrValue) -> let symbol = symbol.Symbol :?> FSharpMemberOrFunctionOrValue - if symbol.IsMember then symbol.CurriedParameterGroups |> Seq.tryHead + if symbol.IsMember then Seq.tryHead symbol.CurriedParameterGroups else None | _ -> None @@ -622,7 +729,7 @@ let private (|RequiresCheck|CanBeReplaced|CannotBeReplaced|) (breadcrumbs, range match filterParens breadcrumbs with | AstNode.Expression(SynExpr.Tuple(_, exprs, _, _))::AstNode.Expression(SynExpr.App(ExprAtomicFlag.Atomic, _, SynExpr.DotGet(_, _, methodIdent, _), _, _))::_ | AstNode.Expression(SynExpr.Tuple(_, exprs, _, _))::AstNode.Expression(SynExpr.App(ExprAtomicFlag.Atomic, _, SynExpr.LongIdent(_, methodIdent, _, _), _, _))::_ -> - match exprs |> List.tryFindIndex (fun x -> x.Range = range) with + match List.tryFindIndex (fun (expr: SynExpr) -> expr.Range = range) exprs with | Some(index) -> RequiresCheck(index, methodIdent) | None -> CannotBeReplaced | AstNode.Expression(SynExpr.App(ExprAtomicFlag.Atomic, _, SynExpr.DotGet(_, _, methodIdent, _), _, _))::_ @@ -637,13 +744,21 @@ let private (|SuggestingReplacementOfLambda|OtherSuggestion|) = function let [] private MaxBreadcrumbs = 6 let private suggestions = ResizeArray() -let private confirmFuzzyMatch (args:AstNodeRuleParams) (hint:HintParser.Hint) = +let private confirmFuzzyMatch (args:AstNodeRuleParams) (hint:HintParserTypes.Hint) = let breadcrumbs = args.GetParents MaxBreadcrumbs match (args.AstNode, hint.MatchedNode) with | AstNode.Expression(SynExpr.Paren(_)), HintExpr(_) | AstNode.Pattern(SynPat.Paren(_)), HintPat(_) -> () - | AstNode.Pattern(pattern), HintPat(hintPattern) when MatchPattern.matchHintPattern (pattern, hintPattern) -> - hintError [] hint args pattern.Range (Dictionary<_, _>()) None + | AstNode.Pattern(pattern), HintPat(hintPattern) when MatchPattern.matchHintPattern MatchPattern.returnTrue (pattern, hintPattern) -> + hintError + { + TypeChecks = List.Empty + Hint = hint + Args = args + Range = pattern.Range + MatchedVariables = (Dictionary<_, _>()) + ParentAstNode = None + } |> suggestions.Add | AstNode.Expression(expr), HintExpr(hintExpr) -> let arguments = @@ -654,10 +769,18 @@ let private confirmFuzzyMatch (args:AstNodeRuleParams) (hint:HintParser.Hint) = MatchExpression.FSharpCheckFileResults = args.CheckInfo MatchExpression.Breadcrumbs = breadcrumbs } - match MatchExpression.matchHintExpr arguments with + match MatchExpression.matchHintExpr MatchExpression.returnEmptyMatch arguments with | MatchExpression.Match(typeChecks) -> let suggest checks = - hintError checks hint args expr.Range arguments.MatchedVariables (List.tryHead breadcrumbs) + hintError + { + TypeChecks = checks + Hint = hint + Args = args + Range = expr.Range + MatchedVariables = arguments.MatchedVariables + ParentAstNode = (List.tryHead breadcrumbs) + } |> suggestions.Add match (hint.MatchedNode, hint.Suggestion) with @@ -688,7 +811,13 @@ let private runner (config:Config) (args:AstNodeRuleParams) = result let rule config = - { Name = "Hints" - Identifier = Identifiers.Hints - RuleConfig = { AstNodeRuleConfig.Runner = runner config; Cleanup = ignore } } - |> AstNodeRule + AstNodeRule + { + Name = "Hints" + Identifier = Identifiers.Hints + RuleConfig = + { + AstNodeRuleConfig.Runner = runner config + Cleanup = ignore + } + } diff --git a/src/FSharpLint.Core/Rules/Hints/HintsHelper.fs b/src/FSharpLint.Core/Rules/Hints/HintsHelper.fs index 0d31aea21..e9a96c1f9 100644 --- a/src/FSharpLint.Core/Rules/Hints/HintsHelper.fs +++ b/src/FSharpLint.Core/Rules/Hints/HintsHelper.fs @@ -21,17 +21,17 @@ open MergeSyntaxTrees /// Confirms if two parts of the ast look alike. /// This is required as hints can bind variables: the bound location needs to be compared to /// parts of the ast that the hint covers with the same variable. -let private isMatch i j (nodeArray:AbstractSyntaxArray.Node []) = - let numChildrenI = nodeArray.[i].NumberOfChildren - let numChildrenJ = nodeArray.[j].NumberOfChildren +let private isMatch iIndex jIndex (nodeArray:AbstractSyntaxArray.Node []) = + let numChildrenI = nodeArray.[iIndex].NumberOfChildren + let numChildrenJ = nodeArray.[jIndex].NumberOfChildren if numChildrenI = numChildrenJ then let numChildren = numChildrenI - { 0..numChildren } |> Seq.forall (fun child -> - i + child < nodeArray.Length && - j + child < nodeArray.Length && - nodeArray.[i + child].Hashcode = nodeArray.[j + child].Hashcode) + Seq.forall (fun child -> + iIndex + child < nodeArray.Length && + jIndex + child < nodeArray.Length && + nodeArray.[iIndex + child].Hashcode = nodeArray.[jIndex + child].Hashcode) { 0..numChildren } else false let inline private isParen (node:AbstractSyntaxArray.Node) = @@ -39,31 +39,34 @@ let inline private isParen (node:AbstractSyntaxArray.Node) = | AstNode.Expression(SynExpr.Paren(_)) -> true | _ -> false +// hard to turn into tail-recursive form +// fsharplint:disable EnsureTailCallDiagnosticsInRecursiveFunctions /// Compares the hint trie against a given location in the abstract syntax array. -let rec checkTrie i trie (nodeArray:AbstractSyntaxArray.Node []) (boundVariables:Dictionary<_, _>) notify = - trie.MatchedHint |> List.iter notify +let rec checkTrie index trie (nodeArray:AbstractSyntaxArray.Node []) (boundVariables:Dictionary<_, _>) notify = + List.iter notify trie.MatchedHint - if i < nodeArray.Length then - let node = nodeArray.[i] + if index < nodeArray.Length then + let node = nodeArray.[index] if isParen node then // Skip the paren. - checkTrie (i + 1) trie nodeArray boundVariables notify + checkTrie (index + 1) trie nodeArray boundVariables notify else match trie.Edges.Lookup.TryGetValue node.Hashcode with - | true, trie -> checkTrie (i + 1) trie nodeArray boundVariables notify + | true, trie -> checkTrie (index + 1) trie nodeArray boundVariables notify | false, _ -> () - trie.Edges.AnyMatch - |> List.iter (fun (var, trie) -> + let collect var trie = match var with | Some(var) -> match boundVariables.TryGetValue var with - | true, varI when isMatch varI i nodeArray -> - checkTrie (i + node.NumberOfChildren + 1) trie nodeArray boundVariables notify + | true, varI when isMatch varI index nodeArray -> + checkTrie (index + node.NumberOfChildren + 1) trie nodeArray boundVariables notify | false, _ -> - boundVariables.Add(var, i) - checkTrie (i + node.NumberOfChildren + 1) trie nodeArray boundVariables notify + boundVariables.Add(var, index) + checkTrie (index + node.NumberOfChildren + 1) trie nodeArray boundVariables notify | true, _ -> () - | None -> checkTrie (i + node.NumberOfChildren + 1) trie nodeArray boundVariables notify) + | None -> checkTrie (index + node.NumberOfChildren + 1) trie nodeArray boundVariables notify + List.iter (fun (var, trie) -> collect var trie) trie.Edges.AnyMatch +// fsharplint:enable EnsureTailCallDiagnosticsInRecursiveFunctions diff --git a/src/FSharpLint.Core/Rules/Typography/Indentation.fs b/src/FSharpLint.Core/Rules/Typography/Indentation.fs index 2f5ed2ae8..a9d9372bf 100644 --- a/src/FSharpLint.Core/Rules/Typography/Indentation.fs +++ b/src/FSharpLint.Core/Rules/Typography/Indentation.fs @@ -28,36 +28,38 @@ module ContextBuilder = | other -> (other::items) - helper [] seqExpr + helper List.Empty seqExpr |> List.rev let private createAbsoluteAndOffsetOverrides expectedIndentation (rangeToUpdate:Range) = let absoluteOverride = (rangeToUpdate.StartLine, (true, expectedIndentation)) let relativeOverrides = - [(rangeToUpdate.StartLine + 1)..rangeToUpdate.EndLine] - |> List.map (fun offsetLine -> - (offsetLine, (false, expectedIndentation))) + List.map (fun offsetLine -> + (offsetLine, (false, expectedIndentation))) [(rangeToUpdate.StartLine + 1)..rangeToUpdate.EndLine] (absoluteOverride::relativeOverrides) let rec private collectRecordFields = function | (SynExpr.Record ( _, _, fields, _)) -> let subRecords = fields - |> List.choose (fun (SynExprRecordField(_, _, expr, _)) -> expr |> Option.map collectRecordFields) + |> List.choose (fun (SynExprRecordField(_, _, expr, _)) -> Option.map collectRecordFields expr) |> List.concat fields::subRecords | _ -> - [] + List.Empty let private createAbsoluteAndOffsetOverridesBasedOnFirst (ranges:Range list) = match ranges with | (first::others) -> let expectedIndentation = first.StartColumn - others - |> List.collect (fun other -> - [ for lineNumber=other.StartLine to other.EndLine do - yield (lineNumber, (true, expectedIndentation)) ]) - | _ -> [] + List.collect + (fun (other: Range) -> + [ + for lineNumber = other.StartLine to other.EndLine do + yield (lineNumber, (true, expectedIndentation)) + ]) + others + | _ -> List.Empty let private indentationOverridesForNode (node:AstNode) = match node with @@ -98,7 +100,7 @@ module ContextBuilder = match funcExpr with | ExpressionUtilities.Identifier([ ident ], _) when ident.idText = "op_ColonEquals" -> // := for reference cell assignment should be handled like normal equals, not like an infix operator. - [] + List.Empty | _ -> let expectedIndentation = innerArg.Range.StartColumn createAbsoluteAndOffsetOverrides expectedIndentation outerArg.Range @@ -125,7 +127,7 @@ module ContextBuilder = |> List.map (fun ((_, fieldIdent), _, _) -> fieldIdent.idRange) |> firstRangePerLine |> createAbsoluteAndOffsetOverridesBasedOnFirst - | _ -> [] + | _ -> List.Empty let builder current node = indentationOverridesForNode node @@ -135,7 +137,7 @@ module ContextBuilder = let checkIndentation (expectedSpaces:int) (line:string) (lineNumber:int) (indentationOverrides:Map) = let lineTrimmedStart = line.TrimStart() let numLeadingSpaces = line.Length - lineTrimmedStart.Length - let range = Range.mkRange "" (Position.mkPos lineNumber 0) (Position.mkPos lineNumber numLeadingSpaces) + let range = Range.mkRange String.Empty (Position.mkPos lineNumber 0) (Position.mkPos lineNumber numLeadingSpaces) if lineTrimmedStart.StartsWith "//" || lineTrimmedStart.StartsWith "(*" then None @@ -144,27 +146,37 @@ let checkIndentation (expectedSpaces:int) (line:string) (lineNumber:int) (indent | (true, expectedIndentation) -> if numLeadingSpaces <> expectedIndentation then let errorString = Resources.GetString("RulesTypographyOverridenIndentationError") - { Range = range - Message = errorString - SuggestedFix = None - TypeChecks = [] } |> Some + Some + { + Range = range + Message = errorString + SuggestedFix = None + TypeChecks = List.Empty + } else None | (false, indentationOffset) -> if (numLeadingSpaces - indentationOffset) % expectedSpaces <> 0 then let errorFormatString = Resources.GetString("RulesTypographyOverridenIndentationError") - { Range = range - Message = String.Format(errorFormatString, expectedSpaces) - SuggestedFix = None - TypeChecks = [] } |> Some + + Some + { + Range = range + Message = String.Format(errorFormatString, expectedSpaces) + SuggestedFix = None + TypeChecks = List.Empty + } else None elif numLeadingSpaces % expectedSpaces <> 0 then let errorFormatString = Resources.GetString("RulesTypographyIndentationError") - { Range = range - Message = String.Format(errorFormatString, expectedSpaces) - SuggestedFix = None - TypeChecks = [] } |> Some + Some + { + Range = range + Message = String.Format(errorFormatString, expectedSpaces) + SuggestedFix = None + TypeChecks = List.Empty + } else None @@ -173,7 +185,9 @@ let runner context args = |> Option.toArray let rule = - { Name = "Indentation" - Identifier = Identifiers.Indentation - RuleConfig = { Runner = runner } } - |> IndentationRule \ No newline at end of file + IndentationRule + { + Name = "Indentation" + Identifier = Identifiers.Indentation + RuleConfig = { Runner = runner } + } diff --git a/src/FSharpLint.Core/Rules/Typography/MaxCharactersOnLine.fs b/src/FSharpLint.Core/Rules/Typography/MaxCharactersOnLine.fs index 621f0ef2b..27dc5202d 100644 --- a/src/FSharpLint.Core/Rules/Typography/MaxCharactersOnLine.fs +++ b/src/FSharpLint.Core/Rules/Typography/MaxCharactersOnLine.fs @@ -13,17 +13,25 @@ let checkMaxCharactersOnLine (config:Config) (args:LineRuleParams) = let maxCharacters = config.MaxCharactersOnLine let lineLength = String.length args.Line if lineLength > maxCharacters then - let range = Range.mkRange "" (Position.mkPos args.LineNumber (maxCharacters + 1)) (Position.mkPos args.LineNumber lineLength) + let range = Range.mkRange String.Empty (Position.mkPos args.LineNumber (maxCharacters + 1)) (Position.mkPos args.LineNumber lineLength) let errorFormatString = Resources.GetString("RulesTypographyLineLengthError") - { Range = range - Message = String.Format(errorFormatString, (maxCharacters + 1)) - SuggestedFix = None - TypeChecks = [] } |> Array.singleton + Array.singleton + { + Range = range + Message = String.Format(errorFormatString, (maxCharacters + 1)) + SuggestedFix = None + TypeChecks = List.Empty + } else Array.empty let rule config = - { Name = "MaxCharactersOnLine" - Identifier = Identifiers.MaxCharactersOnLine - RuleConfig = { LineRuleConfig.Runner = checkMaxCharactersOnLine config } } - |> LineRule \ No newline at end of file + LineRule + { + Name = "MaxCharactersOnLine" + Identifier = Identifiers.MaxCharactersOnLine + RuleConfig = + { + LineRuleConfig.Runner = checkMaxCharactersOnLine config + } + } diff --git a/src/FSharpLint.Core/Rules/Typography/MaxLinesInFile.fs b/src/FSharpLint.Core/Rules/Typography/MaxLinesInFile.fs index a45b432fa..daafcdf0b 100644 --- a/src/FSharpLint.Core/Rules/Typography/MaxLinesInFile.fs +++ b/src/FSharpLint.Core/Rules/Typography/MaxLinesInFile.fs @@ -12,10 +12,14 @@ type Config = { MaxLinesInFile:int } let private checkNumberOfLinesInFile numberOfLines line maxLines = if numberOfLines > maxLines then let errorFormatString = Resources.GetString("RulesTypographyFileLengthError") - { Range = Range.mkRange "" (Position.mkPos (maxLines + 1) 0) (Position.mkPos numberOfLines (String.length line)) - Message = String.Format(errorFormatString, (maxLines + 1)) - SuggestedFix = None - TypeChecks = [] } |> Array.singleton + Array.singleton + { + Range = + Range.mkRange "" (Position.mkPos (maxLines + 1) 0) (Position.mkPos numberOfLines (String.length line)) + Message = String.Format(errorFormatString, (maxLines + 1)) + SuggestedFix = None + TypeChecks = List.Empty + } else Array.empty @@ -26,7 +30,12 @@ let checkMaxLinesInFile (config:Config) (args:LineRuleParams) = Array.empty let rule config = - { Name = "MaxLinesInFile" - Identifier = Identifiers.MaxLinesInFile - RuleConfig = { LineRuleConfig.Runner = checkMaxLinesInFile config } } - |> LineRule \ No newline at end of file + LineRule + { + Name = "MaxLinesInFile" + Identifier = Identifiers.MaxLinesInFile + RuleConfig = + { + LineRuleConfig.Runner = checkMaxLinesInFile config + } + } diff --git a/src/FSharpLint.Core/Rules/Typography/NoTabCharacters.fs b/src/FSharpLint.Core/Rules/Typography/NoTabCharacters.fs index 96886e6a6..4fa34d427 100644 --- a/src/FSharpLint.Core/Rules/Typography/NoTabCharacters.fs +++ b/src/FSharpLint.Core/Rules/Typography/NoTabCharacters.fs @@ -18,33 +18,36 @@ module ContextBuilder = current let private isInLiteralString literalStrings range = - literalStrings |> Seq.exists (fun (_, literalRange) -> ExpressionUtilities.rangeContainsOtherRange literalRange range) + Seq.exists (fun (_, literalRange) -> ExpressionUtilities.rangeContainsOtherRange literalRange range) literalStrings let checkNoTabCharacters literalStrings (args:LineRuleParams) = let indexOfTab = args.Line.IndexOf('\t') if indexOfTab >= 0 then - let range = Range.mkRange "" (Position.mkPos args.LineNumber indexOfTab) (Position.mkPos args.LineNumber (indexOfTab + 1)) + let range = Range.mkRange String.Empty (Position.mkPos args.LineNumber indexOfTab) (Position.mkPos args.LineNumber (indexOfTab + 1)) if isInLiteralString literalStrings range |> not then - { Range = range - Message = Resources.GetString("RulesTypographyTabCharacterError") - SuggestedFix = - Some( - lazy - (Some( - { FromRange = range - FromText = "\t" - ToText = String.replicate args.GlobalConfig.numIndentationSpaces " " } - )) - ) - TypeChecks = [] } |> Array.singleton + Array.singleton + { Range = range + Message = Resources.GetString("RulesTypographyTabCharacterError") + SuggestedFix = + Some( + lazy + (Some( + { FromRange = range + FromText = "\t" + ToText = String.replicate args.GlobalConfig.numIndentationSpaces " " } + )) + ) + TypeChecks = List.Empty } else Array.empty else Array.empty let rule = - { Name = "NoTabCharacters" - Identifier = Identifiers.NoTabCharacters - RuleConfig = { Runner = checkNoTabCharacters } } - |> NoTabCharactersRule \ No newline at end of file + NoTabCharactersRule + { + Name = "NoTabCharacters" + Identifier = Identifiers.NoTabCharacters + RuleConfig = { Runner = checkNoTabCharacters } + } diff --git a/src/FSharpLint.Core/Rules/Typography/TrailingNewLineInFile.fs b/src/FSharpLint.Core/Rules/Typography/TrailingNewLineInFile.fs index 0eccac9c4..c66207aeb 100644 --- a/src/FSharpLint.Core/Rules/Typography/TrailingNewLineInFile.fs +++ b/src/FSharpLint.Core/Rules/Typography/TrailingNewLineInFile.fs @@ -9,15 +9,23 @@ open FSharp.Compiler.Text let checkTrailingNewLineInFile (args:LineRuleParams) = if args.IsLastLine && args.FileContent.EndsWith("\n") then let pos = Position.mkPos args.LineNumber 0 - { Range = Range.mkRange "" pos pos - Message = Resources.GetString("RulesTypographyTrailingLineError") - SuggestedFix = None - TypeChecks = [] } |> Array.singleton + Array.singleton + { + Range = Range.mkRange "" pos pos + Message = Resources.GetString("RulesTypographyTrailingLineError") + SuggestedFix = None + TypeChecks = List.Empty + } else Array.empty let rule = - { Name = "TrailingNewLineInFile" - Identifier = Identifiers.TrailingNewLineInFile - RuleConfig = { LineRuleConfig.Runner = checkTrailingNewLineInFile } } - |> LineRule \ No newline at end of file + LineRule + { + Name = "TrailingNewLineInFile" + Identifier = Identifiers.TrailingNewLineInFile + RuleConfig = + { + LineRuleConfig.Runner = checkTrailingNewLineInFile + } + } diff --git a/src/FSharpLint.Core/Rules/Typography/TrailingWhitespaceOnLine.fs b/src/FSharpLint.Core/Rules/Typography/TrailingWhitespaceOnLine.fs index 27495ffa2..ea01f8224 100644 --- a/src/FSharpLint.Core/Rules/Typography/TrailingWhitespaceOnLine.fs +++ b/src/FSharpLint.Core/Rules/Typography/TrailingWhitespaceOnLine.fs @@ -17,7 +17,7 @@ let private isSymbol character = [ '>';'<';'+';'-';'*';'=';'~';'%';'&';'|';'@' '#';'^';'!';'?';'/';'.';':';',';'(';')';'[';']';'{';'}' ] - symbols |> List.exists ((=) character) + List.exists ((=) character) symbols let private doesStringNotEndWithWhitespace (config:Config) (str:string) = match (config.NumberOfSpacesAllowed, config.OneSpaceAllowedAfterOperator) with @@ -46,16 +46,24 @@ let checkTrailingWhitespaceOnLine (config:Config) (args:LineRuleParams) = if stringEndsWithWhitespace then let whitespaceLength = lengthOfWhitespaceOnEnd line - let range = Range.mkRange "" (Position.mkPos lineNumber (line.Length - whitespaceLength)) (Position.mkPos lineNumber line.Length) - { Range = range - Message = Resources.GetString("RulesTypographyTrailingWhitespaceError") - SuggestedFix = None - TypeChecks = [] } |> Array.singleton + let range = Range.mkRange String.Empty (Position.mkPos lineNumber (line.Length - whitespaceLength)) (Position.mkPos lineNumber line.Length) + Array.singleton + { + Range = range + Message = Resources.GetString("RulesTypographyTrailingWhitespaceError") + SuggestedFix = None + TypeChecks = List.Empty + } else Array.empty let rule config = - { Name = "TrailingWhitespaceOnLine" - Identifier = Identifiers.TrailingWhitespaceOnLine - RuleConfig = { LineRuleConfig.Runner = checkTrailingWhitespaceOnLine config } } - |> LineRule \ No newline at end of file + LineRule + { + Name = "TrailingWhitespaceOnLine" + Identifier = Identifiers.TrailingWhitespaceOnLine + RuleConfig = + { + LineRuleConfig.Runner = checkTrailingWhitespaceOnLine config + } + } diff --git a/src/FSharpLint.Core/fsharplint.json b/src/FSharpLint.Core/fsharplint.json index 24e13511f..162b6bd42 100644 --- a/src/FSharpLint.Core/fsharplint.json +++ b/src/FSharpLint.Core/fsharplint.json @@ -331,7 +331,7 @@ "additionalPartials": [] } }, - "ensureTailCallDiagnosticsInRecursiveFunctions": { "enabled": false }, + "ensureTailCallDiagnosticsInRecursiveFunctions": { "enabled": true }, "favourAsKeyword": { "enabled": true }, "hints": { "add": [ diff --git a/tests/FSharpLint.Benchmarks/Benchmark.fs b/tests/FSharpLint.Benchmarks/Benchmark.fs index c7962c848..63f5e703c 100644 --- a/tests/FSharpLint.Benchmarks/Benchmark.fs +++ b/tests/FSharpLint.Benchmarks/Benchmark.fs @@ -6,6 +6,7 @@ open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Text open FSharpLint.Application.Lint open FSharpLint.Framework +open FSharpLint.Framework.Utilities type Benchmark () = @@ -23,8 +24,6 @@ type Benchmark () = parseResults.ParseTree - let () x y = Path.Combine(x, y) - let basePath = ".." ".." ".." ".." ".." ".." ".." ".." let sourceFile = basePath "TypeChecker.fs" @@ -35,4 +34,4 @@ type Benchmark () = [] member this.LintParsedFile () = - lintParsedFile OptionalLintParameters.Default fileInfo sourceFile |> ignore + lintParsedFile OptionalLintParameters.Default fileInfo sourceFile |> ignore diff --git a/tests/FSharpLint.Benchmarks/Program.fs b/tests/FSharpLint.Benchmarks/Program.fs index 13768750c..5df8f4193 100644 --- a/tests/FSharpLint.Benchmarks/Program.fs +++ b/tests/FSharpLint.Benchmarks/Program.fs @@ -11,5 +11,5 @@ let main _ = .Run( DefaultConfig.Instance .AddJob(Job.Default) - .AddDiagnoser(EtwProfiler())) |> ignore + .AddDiagnoser(EtwProfiler())) |> ignore 0 diff --git a/tests/FSharpLint.Console.Tests/TestApp.fs b/tests/FSharpLint.Console.Tests/TestApp.fs index 322a39cab..e200f9c2b 100644 --- a/tests/FSharpLint.Console.Tests/TestApp.fs +++ b/tests/FSharpLint.Console.Tests/TestApp.fs @@ -15,7 +15,7 @@ type TemporaryFile(fileContent : string, extension) = do File.WriteAllText(filename, fileContent) - member _.FileName = filename + member val FileName = filename interface System.IDisposable with member _.Dispose() = diff --git a/tests/FSharpLint.Core.Tests/Framework/TestAbstractSyntaxArray.fs b/tests/FSharpLint.Core.Tests/Framework/TestAbstractSyntaxArray.fs index 31c9158ae..34ba45144 100644 --- a/tests/FSharpLint.Core.Tests/Framework/TestAbstractSyntaxArray.fs +++ b/tests/FSharpLint.Core.Tests/Framework/TestAbstractSyntaxArray.fs @@ -12,20 +12,20 @@ open FSharpLint.Framework.ExpressionUtilities [] type TestAst() = - let unionCaseName (x:'T) = - match FSharpValue.GetUnionFields(x, typeof<'T>) with + let unionCaseName (unionCase: 'UnionType) = + match FSharpValue.GetUnionFields(unionCase, typeof<'UnionType>) with | case, _ -> case.Name let astToExpr ast = - let (|Module|_|) x = - match x with + let (|Module|_|) moduleOrNamespace = + match moduleOrNamespace with | SynModuleOrNamespace(_, _, _, SynModuleDecl.Expr(app, _)::_, _, _, _, _, _) -> Some(app) | _ -> None match ast with - | ParsedInput.ImplFile(x) -> - match x with + | ParsedInput.ImplFile(implFile) -> + match implFile with | ParsedImplFileInput(_, _, _, _, _, Module(app)::_, _, _, _) -> app | _ -> failwith "Expected at least one module or namespace." @@ -37,49 +37,49 @@ type TestAst() = member _.``Flatten with right pipe adds lhs to end of function application.``() = match generateAst "x |> List.map (fun x -> x)" |> astToExpr |> Expression with | FuncApp(expressions, _) -> - Assert.AreEqual(["LongIdent"; "Lambda"; "Ident"], expressions |> List.map astNodeName) + Assert.AreEqual(["LongIdent"; "Lambda"; "Ident"], List.map astNodeName expressions) | _ -> Assert.Fail() [] member _.``Flatten with left pipe adds rhs to end of function application.``() = match generateAst "List.map (fun x -> x) <| x" |> astToExpr |> Expression with | FuncApp(expressions, _) -> - Assert.AreEqual(["LongIdent"; "Lambda"; "Ident"], expressions |> List.map astNodeName) + Assert.AreEqual(["LongIdent"; "Lambda"; "Ident"], List.map astNodeName expressions) | _ -> Assert.Fail() [] member _.``Flatten with right pipe adds lhs to end of function application no matter the number of arguments on rhs.``() = match generateAst "x |> List.map (fun x -> x) 1" |> astToExpr |> Expression with | FuncApp(expressions, _) -> - Assert.AreEqual(["LongIdent"; "Lambda"; "Const"; "Ident"], expressions |> List.map astNodeName) + Assert.AreEqual(["LongIdent"; "Lambda"; "Const"; "Ident"], List.map astNodeName expressions) | _ -> Assert.Fail() [] member _.``Flatten with binary operator on lhs of right pipe.``() = match generateAst "x::[] |> List.map (fun x -> x)" |> astToExpr |> Expression with | FuncApp(expressions, _) -> - Assert.AreEqual(["LongIdent"; "Lambda"; "App"], expressions |> List.map astNodeName) + Assert.AreEqual(["LongIdent"; "Lambda"; "App"], List.map astNodeName expressions) | _ -> Assert.Fail() [] member _.``Flatten with function application on lhs of right pipe.``() = match generateAst "foo x |> List.map (fun x -> x)" |> astToExpr |> Expression with | FuncApp(expressions, _) -> - Assert.AreEqual(["LongIdent"; "Lambda"; "App"], expressions |> List.map astNodeName) + Assert.AreEqual(["LongIdent"; "Lambda"; "App"], List.map astNodeName expressions) | _ -> Assert.Fail() [] member _.``Flatten with multiple right pipes.``() = match generateAst "x |> foo |> List.map (fun x -> x)" |> astToExpr |> Expression with | FuncApp(expressions, _) -> - Assert.AreEqual(["LongIdent"; "Lambda"; "App"], expressions |> List.map astNodeName) + Assert.AreEqual(["LongIdent"; "Lambda"; "App"], List.map astNodeName expressions) | _ -> Assert.Fail() [] member _.``Flatten with multiple left pipes.``() = match generateAst "List.map (fun x -> x) <| 1 <| x" |> astToExpr |> Expression with | FuncApp(expressions, _) -> - Assert.AreEqual(["LongIdent"; "Lambda"; "Const"; "Ident"], expressions |> List.map astNodeName) + Assert.AreEqual(["LongIdent"; "Lambda"; "Const"; "Ident"], List.map astNodeName expressions) | _ -> Assert.Fail() [] @@ -95,7 +95,7 @@ type TestAst() = for _ in 0..iterations do stopwatch.Restart() - astToArray tree |> ignore + astToArray tree |> ignore stopwatch.Stop() @@ -112,7 +112,7 @@ type TestAst() = let array = astToArray tree - let actual = array |> Array.map (fun x -> x.Hashcode) |> Array.toList + let actual = array |> Array.map (fun node -> node.Hashcode) |> Array.toList let expected = [ Utilities.hash2 SyntaxNode.ModuleOrNamespace 0 @@ -132,8 +132,8 @@ type TestAst() = Utilities.hash2 SyntaxNode.Identifier "woofs" ] Assert.AreEqual(expected, actual) - - let expected = array |> Array.map (fun x -> (x.NumberOfChildren, x.ParentIndex)) |> Array.toList + + let expected = array |> Array.map (fun node -> (node.NumberOfChildren, node.ParentIndex)) |> Array.toList Assert.AreEqual([ (14, 0) (13, 0) (12, 1) @@ -158,7 +158,7 @@ type TestAst() = let array = astToArray tree - let actual = array |> Array.map (fun x -> x.Hashcode) |> List.ofArray + let actual = array |> Array.map (fun node -> node.Hashcode) |> List.ofArray let expected = [ Utilities.hash2 SyntaxNode.ModuleOrNamespace 0 @@ -171,7 +171,7 @@ type TestAst() = Assert.AreEqual(expected, actual) - let expected = array |> Array.map (fun x -> (x.NumberOfChildren, x.ParentIndex)) |> List.ofArray + let expected = array |> Array.map (fun node -> (node.NumberOfChildren, node.ParentIndex)) |> List.ofArray Assert.AreEqual([ (6, 0) (5, 0) (4, 1) diff --git a/tests/FSharpLint.Core.Tests/Framework/TestConfiguration.fs b/tests/FSharpLint.Core.Tests/Framework/TestConfiguration.fs index a5b53bdae..2da2f1738 100644 --- a/tests/FSharpLint.Core.Tests/Framework/TestConfiguration.fs +++ b/tests/FSharpLint.Core.Tests/Framework/TestConfiguration.fs @@ -4,8 +4,8 @@ open NUnit.Framework open FSharpLint.Framework.Configuration type System.String with - member path.ToPlatformIndependentPath() = - path.Replace('\\', System.IO.Path.DirectorySeparatorChar) + member this.ToPlatformIndependentPath() = + this.Replace('\\', System.IO.Path.DirectorySeparatorChar) let configWithHints hints = { Configuration.Zero with Hints = hints } diff --git a/tests/FSharpLint.Core.Tests/Framework/TestExpressionUtilities.fs b/tests/FSharpLint.Core.Tests/Framework/TestExpressionUtilities.fs index 459a7d029..7f401b454 100644 --- a/tests/FSharpLint.Core.Tests/Framework/TestExpressionUtilities.fs +++ b/tests/FSharpLint.Core.Tests/Framework/TestExpressionUtilities.fs @@ -1,5 +1,6 @@ module FSharpLint.Core.Tests.TestExpressionUtilities +open System open NUnit.Framework open FSharp.Compiler.Text open FSharpLint.Framework.ExpressionUtilities @@ -11,7 +12,7 @@ type TestExpressionUtilities() = let text = "123\n345\n678" let textOfRange (line1, col1) (line2, col2) = - tryFindTextOfRange (Range.mkRange "" (Position.mkPos line1 col1) (Position.mkPos line2 col2)) text + tryFindTextOfRange (Range.mkRange String.Empty (Position.mkPos line1 col1) (Position.mkPos line2 col2)) text Assert.AreEqual(Some "123", textOfRange (1, 0) (1, 3)) Assert.AreEqual(Some "345", textOfRange (2, 0) (2, 3)) diff --git a/tests/FSharpLint.Core.Tests/Framework/TestFuzzyHintMatcher.fs b/tests/FSharpLint.Core.Tests/Framework/TestFuzzyHintMatcher.fs index 6d3b3a970..01de9df97 100644 --- a/tests/FSharpLint.Core.Tests/Framework/TestFuzzyHintMatcher.fs +++ b/tests/FSharpLint.Core.Tests/Framework/TestFuzzyHintMatcher.fs @@ -6,7 +6,7 @@ open FSharpLint.Rules.Helper.Hints open FSharpLint.Framework open FSharpLint.Framework.AbstractSyntaxArray open FSharpLint.Framework.HintParser -open FSharpLint.Framework.HintParser.MergeSyntaxTrees +open FSharpLint.Framework.MergeSyntaxTrees open NUnit.Framework open FParsec open TestUtils @@ -27,6 +27,8 @@ type TestAst() = | Success(hint, _, _) -> hint | Failure(message, _, _) -> failwith message + // List of hints is quite big and would trigger MaxLinesInValue and MaxLinesInMember rules. + // fsharplint:disable MaxLinesInValue MaxLinesInMember [] [] member _.``Performance of matching fuzzy matching hints``() = @@ -147,6 +149,7 @@ type TestAst() = stopwatch.Stop() Assert.Less(stopwatch.ElapsedMilliseconds, 50) fprintf TestContext.Out "Iterated array in %d milliseconds." stopwatch.ElapsedMilliseconds + // fsharplint:enable MaxLinesInValue MaxLinesInMember [] [] diff --git a/tests/FSharpLint.Core.Tests/Framework/TestHintParser.fs b/tests/FSharpLint.Core.Tests/Framework/TestHintParser.fs index 54f19dc4e..ebf96bed2 100644 --- a/tests/FSharpLint.Core.Tests/Framework/TestHintParser.fs +++ b/tests/FSharpLint.Core.Tests/Framework/TestHintParser.fs @@ -6,7 +6,8 @@ open NUnit.Framework open FSharpLint.Framework open FSharpLint.Framework.HintParser open FParsec -open MergeSyntaxTrees +open FSharpLint.Framework.MergeSyntaxTrees +open FSharpLint.Framework.HintParserTypes open System.Collections.Generic [] diff --git a/tests/FSharpLint.Core.Tests/Rules/Conventions/CyclomaticComplexity.fs b/tests/FSharpLint.Core.Tests/Rules/Conventions/CyclomaticComplexity.fs index 271a7a1a1..e73c208fe 100644 --- a/tests/FSharpLint.Core.Tests/Rules/Conventions/CyclomaticComplexity.fs +++ b/tests/FSharpLint.Core.Tests/Rules/Conventions/CyclomaticComplexity.fs @@ -14,9 +14,9 @@ let private MaxComplexity = 5 let private NewLine = "\n" /// Indent all lines of a string equally by the given number of spaces. -let private indent numSpaces (s: string) = +let private indent numSpaces (inputText: string) = let indentStr = String.replicate numSpaces " " - let result = indentStr + s.Replace(NewLine, $"{NewLine}{indentStr}") + let result = indentStr + inputText.Replace(NewLine, $"{NewLine}{indentStr}") result /// Generates a body of code containing a match expression. @@ -28,7 +28,7 @@ let private makeMatchSnippet len = /// Generates a body of code containing a match expression with a when clause containing a logical operator in each pattern. let private makeMatchSnippetWithLogicalOperatorsInWhenClause len = - let patterns = Seq.map (fun i -> $"| x when x = \"%d{i*len}\" || x = \"%d{i*len+1}\" -> ()") [| 1..len-1 |] |> String.concat NewLine + let patterns = Seq.map (fun index -> $"| x when x = \"%d{index*len}\" || x = \"%d{index*len+1}\" -> ()") [| 1..len-1 |] |> String.concat NewLine $"""match "dummyString" with {patterns} | _ -> ()""" @@ -86,18 +86,16 @@ let private matchExpression len = /// Generates a body of code containing a match expression with multiple combined patterns. let private matchExpressionWithCombinedPatterns len = let patterns = Seq.map (sprintf "| \"%d\"") [| 1..len-1 |] |> String.concat NewLine - $"""match "dummyString" with + makeProgram "f()" $"""match "dummyString" with {patterns} | _ -> ()""" - |> makeProgram "f()" /// Generates a body of code containing a match function with multiple patterns. let private matchFunction len = - $""" function -{(Seq.map (sprintf " | \"%d\"") [| 1..len-1 |] |> String.concat NewLine)} + makeProgram "f" $""" function +{(Seq.map (fun index -> (sprintf " | \"%d\"" index)) [| 1..len-1 |] |> String.concat NewLine)} | _ -> () f "dummyString" """ - |> makeProgram "f" /// Generates a computational expression with a match! expression containing multiple patterns. let private matchBang len = @@ -137,18 +135,18 @@ type TestConventionsCyclomaticComplexity() = static member private FailureCasesSource = seq { let num = MaxComplexity + 1 - let errorLocation = 2, 4 - yield ifElseExpressions num, errorLocation - yield forExpressions num, errorLocation - yield foreachExpressions num, errorLocation - yield whileExpressions num, errorLocation - yield matchExpression num, errorLocation - yield matchExpressionWithCombinedPatterns num, errorLocation - yield matchFunction num, errorLocation - yield matchBang num, errorLocation - yield ifThenExpressionWithMultipleAndConditionals num, errorLocation - yield ifThenExpressionWithMultipleOrConditionals num, errorLocation - yield whileWithBooleanOperatorsInConditionExpressions num, errorLocation + let errorLocation = (2, 4) + yield (ifElseExpressions num, errorLocation) + yield (forExpressions num, errorLocation) + yield (foreachExpressions num, errorLocation) + yield (whileExpressions num, errorLocation) + yield (matchExpression num, errorLocation) + yield (matchExpressionWithCombinedPatterns num, errorLocation) + yield (matchFunction num, errorLocation) + yield (matchBang num, errorLocation) + yield (ifThenExpressionWithMultipleAndConditionals num, errorLocation) + yield (ifThenExpressionWithMultipleOrConditionals num, errorLocation) + yield (whileWithBooleanOperatorsInConditionExpressions num, errorLocation) } |> Seq.map (fun (x, y) -> [| box x; box y |]) /// Verifies that no cyclomatic complexity over-maximum flags are raised on source that has cyclomatic complexity <= maxComplexity. @@ -247,7 +245,7 @@ let f() = [] member this.EnsureRedundantWarningsNotReported() = // generates a vapid match clause - let genMatchClause i = $"""| "{i}" -> match str with + let genMatchClause index = $"""| "{index}" -> match str with | "A" -> () | "B" -> ()""" // create a snippet of code with 10 match clauses @@ -256,4 +254,4 @@ let f() = let f (str: string) = match str with""" + NewLine + matchClauses this.Parse code - Assert.AreEqual(1, this.ErrorRanges.Length) \ No newline at end of file + Assert.AreEqual(1, this.ErrorRanges.Length) diff --git a/tests/FSharpLint.Core.Tests/Rules/Conventions/SourceLength.fs b/tests/FSharpLint.Core.Tests/Rules/Conventions/SourceLength.fs index 34d8669d5..6ed14cc08 100644 --- a/tests/FSharpLint.Core.Tests/Rules/Conventions/SourceLength.fs +++ b/tests/FSharpLint.Core.Tests/Rules/Conventions/SourceLength.fs @@ -15,8 +15,8 @@ let generateNewLines numNewLines numIndents = else String.replicate numIndents " " $"{indentationChars}printf System.String.Empty\n") - (Array.create numNewLines "") - |> String.concat "" + (Array.create numNewLines String.Empty) + |> String.concat String.Empty let generateAbstractMembers numMembers numIndents = Array.init numMembers (fun index -> $"abstract member Foo%i{index} : unit -> unit\n") diff --git a/tests/FSharpLint.Core.Tests/Rules/TestAstNodeRule.fs b/tests/FSharpLint.Core.Tests/Rules/TestAstNodeRule.fs index 56283f051..44fa93e72 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestAstNodeRule.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestAstNodeRule.fs @@ -1,5 +1,6 @@ module FSharpLint.Core.Tests.TestAstNodeRuleBase +open System open FSharp.Compiler.CodeAnalysis open FSharpLint.Application open FSharpLint.Framework @@ -25,7 +26,7 @@ type TestAstNodeRuleBase (rule:Rule) = | AstNodeRule rule -> rule | _ -> failwithf "TestAstNodeRuleBase only accepts AstNodeRules" - let globalConfig = globalConfig |> Option.defaultValue GlobalRuleConfig.Default + let globalConfig = Option.defaultValue GlobalRuleConfig.Default globalConfig match parseResults with | ParseFileResult.Success parseInfo -> @@ -34,9 +35,22 @@ type TestAstNodeRuleBase (rule:Rule) = match checkFile with | Some false -> None | _ -> parseInfo.TypeCheckResults - let suggestions = runAstNodeRules (Array.singleton rule) globalConfig checkResult (Option.defaultValue "" fileName) input (input.Split("\n")) syntaxArray |> fst + + let suggestions = + runAstNodeRules + { + Rules = Array.singleton rule + GlobalConfig = globalConfig + TypeCheckResults = checkResult + FilePath = (Option.defaultValue String.Empty fileName) + FileContent = input + Lines = (input.Split("\n")) + SyntaxArray = syntaxArray + } + |> fst + rule.RuleConfig.Cleanup() - suggestions |> Array.iter this.PostSuggestion + Array.iter this.PostSuggestion suggestions | _ -> failwithf "Failed to parse" \ No newline at end of file diff --git a/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs b/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs index fc13aa975..e81a14b06 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestHintMatcherBase.fs @@ -1,11 +1,12 @@ module FSharpLint.Core.Tests.TestHintMatcherBase +open System open FParsec open FSharp.Compiler.CodeAnalysis open FSharpLint.Application open FSharpLint.Framework open FSharpLint.Framework.HintParser -open FSharpLint.Framework.HintParser.MergeSyntaxTrees +open FSharpLint.Framework.MergeSyntaxTrees open FSharpLint.Framework.ParseFile open FSharpLint.Rules @@ -48,7 +49,7 @@ type TestHintMatcherBase () = | Rules.AstNodeRule rule -> rule | _ -> failwithf "TestHintMatcherBase only accepts AstNodeRules" - let globalConfig = globalConfig |> Option.defaultValue GlobalRuleConfig.Default + let globalConfig = Option.defaultValue GlobalRuleConfig.Default globalConfig match parseResults with | ParseFileResult.Success parseInfo -> @@ -57,7 +58,18 @@ type TestHintMatcherBase () = match checkFile with | Some false -> None | _ -> parseInfo.TypeCheckResults - let suggestions = runAstNodeRules (Array.singleton rule) globalConfig checkResult (Option.defaultValue "" fileName) input (input.Split "\n") syntaxArray |> fst - suggestions |> Array.iter this.PostSuggestion + let suggestions = + runAstNodeRules + { + Rules = Array.singleton rule + GlobalConfig = globalConfig + TypeCheckResults = checkResult + FilePath = (Option.defaultValue String.Empty fileName) + FileContent = input + Lines = (input.Split("\n")) + SyntaxArray = syntaxArray + } + |> fst + Array.iter this.PostSuggestion suggestions | _ -> failwithf "Failed to parse" \ No newline at end of file diff --git a/tests/FSharpLint.Core.Tests/Rules/TestIndentationRule.fs b/tests/FSharpLint.Core.Tests/Rules/TestIndentationRule.fs index c139cdb7b..6d5696772 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestIndentationRule.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestIndentationRule.fs @@ -15,7 +15,7 @@ type TestIndentationRuleBase (rule:Rule) = let checker = FSharpChecker.Create(keepAssemblyContents=true) let sourceText = SourceText.ofString input - let fileName = fileName |> Option.defaultValue "Test.fsx" + let fileName = Option.defaultValue "Test.fsx" fileName let projectOptions, _ = checker.GetProjectOptionsFromScript(fileName, sourceText) |> Async.RunSynchronously let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions @@ -26,13 +26,31 @@ type TestIndentationRuleBase (rule:Rule) = | IndentationRule rule -> rule | _ -> failwithf "TestIndentationRuleBase only accepts IndentationRules" - let globalConfig = globalConfig |> Option.defaultValue GlobalRuleConfig.Default + let globalConfig = Option.defaultValue GlobalRuleConfig.Default globalConfig let lines = input.Split "\n" let syntaxArray = AbstractSyntaxArray.astToArray parseResults.ParseTree - let (_, context) = runAstNodeRules Array.empty globalConfig None fileName input lines syntaxArray - let lineRules = { LineRules.IndentationRule = Some rule; NoTabCharactersRule = None; GenericLineRules = [||] } - - runLineRules lineRules globalConfig fileName input lines context + let (_, context) = + runAstNodeRules + { + Rules = Array.empty + GlobalConfig = globalConfig + TypeCheckResults = None + FilePath = fileName + FileContent = input + Lines = lines + SyntaxArray = syntaxArray + } + let lineRules = { LineRules.IndentationRule = Some rule; NoTabCharactersRule = None; GenericLineRules = Array.empty } + + runLineRules + { + LineRules = lineRules + GlobalConfig = globalConfig + FilePath = fileName + FileContent = input + Lines = lines + Context = context + } |> Array.iter this.PostSuggestion \ No newline at end of file diff --git a/tests/FSharpLint.Core.Tests/Rules/TestLineRule.fs b/tests/FSharpLint.Core.Tests/Rules/TestLineRule.fs index 5f0817dca..5b39acd5a 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestLineRule.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestLineRule.fs @@ -15,13 +15,13 @@ type TestLineRuleBase (rule:Rule) = let checker = FSharpChecker.Create(keepAssemblyContents=true) let sourceText = SourceText.ofString input - let fileName = fileName |> Option.defaultValue "Test.fsx" + let fileName = Option.defaultValue "Test.fsx" fileName let projectOptions, _ = checker.GetProjectOptionsFromScript(fileName, sourceText) |> Async.RunSynchronously let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions let parseResults = checker.ParseFile("test.fsx", sourceText, parsingOptions) |> Async.RunSynchronously - let globalConfig = globalConfig |> Option.defaultValue GlobalRuleConfig.Default + let globalConfig = Option.defaultValue GlobalRuleConfig.Default globalConfig let rule = match rule with @@ -31,8 +31,26 @@ type TestLineRuleBase (rule:Rule) = let lines = input.Split "\n" let syntaxArray = AbstractSyntaxArray.astToArray parseResults.ParseTree - let (_, context) = runAstNodeRules Array.empty globalConfig None fileName input lines syntaxArray + let (_, context) = + runAstNodeRules + { + Rules = Array.empty + GlobalConfig = globalConfig + TypeCheckResults = None + FilePath = fileName + FileContent = input + Lines = lines + SyntaxArray = syntaxArray + } let lineRules = { LineRules.IndentationRule = None; NoTabCharactersRule = None; GenericLineRules = [|rule|] } - runLineRules lineRules globalConfig fileName input lines context + runLineRules + { + LineRules = lineRules + GlobalConfig = globalConfig + FilePath = fileName + FileContent = input + Lines = lines + Context = context + } |> Array.iter this.PostSuggestion \ No newline at end of file diff --git a/tests/FSharpLint.Core.Tests/Rules/TestNoTabCharactersRule.fs b/tests/FSharpLint.Core.Tests/Rules/TestNoTabCharactersRule.fs index 694ffdf99..6a7882dbd 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestNoTabCharactersRule.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestNoTabCharactersRule.fs @@ -15,7 +15,7 @@ type TestNoTabCharactersRuleBase (rule:Rule) = let checker = FSharpChecker.Create(keepAssemblyContents=true) let sourceText = SourceText.ofString input - let fileName = fileName |> Option.defaultValue "Test.fsx" + let fileName = Option.defaultValue "Test.fsx" fileName let projectOptions, _ = checker.GetProjectOptionsFromScript(fileName, sourceText) |> Async.RunSynchronously let parsingOptions, _ = checker.GetParsingOptionsFromProjectOptions projectOptions @@ -26,13 +26,31 @@ type TestNoTabCharactersRuleBase (rule:Rule) = | NoTabCharactersRule rule -> rule | _ -> failwithf "TestNoTabCharactersRuleBase only accepts NoTabCharactersRules" - let globalConfig = globalConfig |> Option.defaultValue GlobalRuleConfig.Default + let globalConfig = Option.defaultValue GlobalRuleConfig.Default globalConfig let lines = input.Split "\n" let syntaxArray = AbstractSyntaxArray.astToArray parseResults.ParseTree - let (_, context) = runAstNodeRules Array.empty globalConfig None fileName input lines syntaxArray - let lineRules = { LineRules.IndentationRule = None; NoTabCharactersRule = Some rule; GenericLineRules = [||] } - - runLineRules lineRules globalConfig fileName input lines context + let (_, context) = + runAstNodeRules + { + Rules = Array.empty + GlobalConfig = globalConfig + TypeCheckResults = None + FilePath = fileName + FileContent = input + Lines = lines + SyntaxArray = syntaxArray + } + let lineRules = { LineRules.IndentationRule = None; NoTabCharactersRule = Some rule; GenericLineRules = Array.empty } + + runLineRules + { + LineRules = lineRules + GlobalConfig = globalConfig + FilePath = fileName + FileContent = input + Lines = lines + Context = context + } |> Array.iter this.PostSuggestion \ No newline at end of file diff --git a/tests/FSharpLint.Core.Tests/Rules/TestRuleBase.fs b/tests/FSharpLint.Core.Tests/Rules/TestRuleBase.fs index 169886a5e..2d03f1f77 100644 --- a/tests/FSharpLint.Core.Tests/Rules/TestRuleBase.fs +++ b/tests/FSharpLint.Core.Tests/Rules/TestRuleBase.fs @@ -26,29 +26,29 @@ type TestRuleBase () = member _.ErrorRanges = suggestions - |> Seq.map (fun s -> (s.Details.Range.StartLine, s.Details.Range.StartColumn)) + |> Seq.map (fun linterSuggestion -> (linterSuggestion.Details.Range.StartLine, linterSuggestion.Details.Range.StartColumn)) |> List.ofSeq member _.ErrorExistsAt(startLine, startColumn) = suggestions - |> Seq.exists (fun s -> s.Details.Range.StartLine = startLine && s.Details.Range.StartColumn = startColumn) + |> Seq.exists (fun linterSuggestion -> linterSuggestion.Details.Range.StartLine = startLine && linterSuggestion.Details.Range.StartColumn = startColumn) member _.ErrorsAt(startLine, startColumn) = suggestions - |> Seq.filter (fun s -> s.Details.Range.StartLine = startLine && s.Details.Range.StartColumn = startColumn) + |> Seq.filter (fun linterSuggestion -> linterSuggestion.Details.Range.StartLine = startLine && linterSuggestion.Details.Range.StartColumn = startColumn) member _.ErrorExistsOnLine(startLine) = suggestions - |> Seq.exists (fun s -> s.Details.Range.StartLine = startLine) + |> Seq.exists (fun linterSuggestion -> linterSuggestion.Details.Range.StartLine = startLine) member _.NoErrorExistsOnLine(startLine) = suggestions - |> Seq.exists (fun s -> s.Details.Range.StartLine = startLine) + |> Seq.exists (fun linterSuggestion -> linterSuggestion.Details.Range.StartLine = startLine) |> not // prevent tests from passing if errors exist, just not on the line being checked member _.NoErrorsExist = - suggestions |> Seq.isEmpty + Seq.isEmpty suggestions member _.ErrorsExist = suggestions |> Seq.isEmpty |> not @@ -58,15 +58,16 @@ type TestRuleBase () = | xs when xs.Count = 0 -> "No errors" | _ -> suggestions - |> Seq.map (fun s -> $"(({s.Details.Range.StartRange.StartLine}, {s.Details.Range.StartColumn}) - ({s.Details.Range.EndRange.EndLine}, {s.Details.Range.EndRange.EndColumn}) -> {s.Details.Message})") - |> (fun x -> String.Join("; ", x)) + |> Seq.map (fun linterSuggestion -> + $"(({linterSuggestion.Details.Range.StartRange.StartLine}, {linterSuggestion.Details.Range.StartColumn}) - ({linterSuggestion.Details.Range.EndRange.EndLine}, {linterSuggestion.Details.Range.EndRange.EndColumn}) -> {linterSuggestion.Details.Message})") + |> (fun suggestionMsg -> String.Join("; ", suggestionMsg)) member this.ErrorWithMessageExistsAt(message, startLine, startColumn) = this.ErrorsAt(startLine, startColumn) - |> Seq.exists (fun s -> s.Details.Message = message) + |> Seq.exists (fun linterSuggestion -> linterSuggestion.Details.Message = message) member _.AssertErrorWithMessageExists(message) = - let foundSuggestions = suggestions |> Seq.map (fun s -> s.Details.Message) + let foundSuggestions = suggestions |> Seq.map (fun linterSuggestion -> linterSuggestion.Details.Message) let foundSuggestionsStr = String.concat "," foundSuggestions Assert.IsTrue(foundSuggestions |> Seq.contains message, $"Couldn't find message '{message}', found: [{foundSuggestionsStr}]") @@ -76,10 +77,10 @@ type TestRuleBase () = member this.ApplyQuickFix (source:string) = let firstSuggestedFix = suggestions - |> Seq.choose (fun x -> x.Details.SuggestedFix) + |> Seq.choose (fun linterSuggestion -> linterSuggestion.Details.SuggestedFix) |> Seq.tryHead - match firstSuggestedFix |> Option.bind (fun x -> x.Value) with + match Option.bind (fun (suggestedFix: Lazy>) -> suggestedFix.Value) firstSuggestedFix with | Some(fix) -> let startIndex = ExpressionUtilities.findPos fix.FromRange.Start source let endIndex = ExpressionUtilities.findPos fix.FromRange.End source diff --git a/tests/FSharpLint.Core.Tests/TestUtils.fs b/tests/FSharpLint.Core.Tests/TestUtils.fs index 3db24e14c..1a26a61cf 100644 --- a/tests/FSharpLint.Core.Tests/TestUtils.fs +++ b/tests/FSharpLint.Core.Tests/TestUtils.fs @@ -30,11 +30,13 @@ let getPerformanceTestInput = let memoizedResult = ref None - fun () -> + let getMemoizedResult () = match !memoizedResult with | Some(result) -> result | None -> - let text = performanceTestSourceFile |> File.ReadAllText + let text = File.ReadAllText performanceTestSourceFile let result = (generateAst text, text) memoizedResult := Some(result) - result \ No newline at end of file + result + + getMemoizedResult diff --git a/tests/FSharpLint.FunctionalTest/TestApi.fs b/tests/FSharpLint.FunctionalTest/TestApi.fs index f42a6ffb2..d7fe3282b 100644 --- a/tests/FSharpLint.FunctionalTest/TestApi.fs +++ b/tests/FSharpLint.FunctionalTest/TestApi.fs @@ -8,8 +8,7 @@ module TestApi = open FSharpLint.Application.Lint open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Text - - let () x y = Path.Combine(x, y) + open FSharpLint.Framework.Utilities let basePath = TestContext.CurrentContext.TestDirectory ".." ".." ".." ".." ".." @@ -38,7 +37,7 @@ module TestApi = [] member _.``Performance of linting an existing file``() = let text = File.ReadAllText sourceFile - let tree = text |> generateAst + let tree = generateAst text let fileInfo = { Ast = tree; Source = text; TypeCheckResults = None } let stopwatch = Stopwatch.StartNew() @@ -49,7 +48,7 @@ module TestApi = for _ in 0..iterations do stopwatch.Restart() - lintParsedFile OptionalLintParameters.Default fileInfo sourceFile |> ignore + lintParsedFile OptionalLintParameters.Default fileInfo sourceFile |> ignore stopwatch.Stop()