From b6f7a6d0013c7d3c45620b5d07ee6984d76b182b Mon Sep 17 00:00:00 2001 From: Richard Webb Date: Fri, 17 Oct 2025 17:54:00 +0100 Subject: [PATCH 1/2] [WIP] FCS 43.10 --- Directory.Packages.props | 4 +-- src/FSharpLint.Core/Framework/Ast.fs | 28 +++++++++++-------- .../AsyncExceptionWithoutReturn.fs | 2 +- .../Rules/Conventions/AvoidTooShortNames.fs | 6 ++-- .../Conventions/Binding/BindingHelper.fs | 2 +- .../Binding/FavourIgnoreOverLetWild.fs | 2 +- .../Conventions/Binding/UselessBinding.fs | 2 +- .../FavourNonMutablePropertyInitialization.fs | 4 +-- .../Conventions/FavourStaticEmptyFields.fs | 2 +- .../Rules/Conventions/Naming/NamingHelper.fs | 2 +- .../Conventions/RecursiveAsyncFunction.fs | 2 +- .../Rules/Typography/Indentation.fs | 13 +++++++-- .../Framework/TestAbstractSyntaxArray.fs | 2 +- 13 files changed, 43 insertions(+), 28 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index efe44081a..3e6e95bce 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,9 +8,9 @@ - + - + diff --git a/src/FSharpLint.Core/Framework/Ast.fs b/src/FSharpLint.Core/Framework/Ast.fs index a290b4b68..32ec02cc7 100644 --- a/src/FSharpLint.Core/Framework/Ast.fs +++ b/src/FSharpLint.Core/Framework/Ast.fs @@ -237,7 +237,7 @@ module Ast = | SynPat.Attrib(pattern, _, _) | SynPat.Paren(pattern, _) -> add <| Pattern pattern | SynPat.Named(_) -> () - | SynPat.Record(patternsAndIdentifier, _) -> List.revIter (fun (_, _, pattern) -> pattern |> Pattern |> add) patternsAndIdentifier + | SynPat.Record(patPairFieldList, _) -> patPairFieldList |> List.revIter(_.Pattern >> Pattern >> add) | SynPat.Const(_) | SynPat.Wild(_) | SynPat.FromParseError(_) @@ -303,13 +303,6 @@ module Ast = | SynExpr.DotNamedIndexedPropertySet(expression, _, expression1, expression2, _) | SynExpr.For(_, _, _, _, expression, _, expression1, expression2, _) -> addMany [Expression expression2; Expression expression1; Expression expression] - | SynExpr.LetOrUseBang(_, _, _, pattern, rightHandSide, andBangs, leftHandSide, _, _) -> - addMany [Expression rightHandSide; Expression leftHandSide] - // TODO: is the the correct way to handle the new `and!` syntax? - List.iter (fun (SynExprAndBang(_, _, _, pattern, body, _, _)) -> - addMany [Expression body; Pattern pattern] - ) andBangs - add <| Pattern pattern | SynExpr.ForEach(_, _, _, _, pattern, expression, expression1, _) -> addMany [Expression expression1; Expression expression; Pattern pattern] | SynExpr.MatchLambda(_, _, matchClauses, _, _) -> @@ -327,9 +320,22 @@ module Ast = | SynExpr.Upcast(expression, synType, _) | SynExpr.Downcast(expression, synType, _) -> addMany [Type synType; Expression expression] - | SynExpr.LetOrUse(_, _, bindings, expression, _, _) -> + // regular let or use + | SynExpr.LetOrUse(_, _, _, false, bindings, expression, _, _) -> add <| Expression expression List.revIter (Binding >> add) bindings + // let! or use! + | SynExpr.LetOrUse(_, _, _, true, bindings, leftHandSide, _, _) -> + match bindings with + | firstBinding :: andBangs -> + match firstBinding with + | SynBinding(headPat = pattern; expr = rightHandSide) -> + addMany [Expression rightHandSide; Expression leftHandSide] + List.iter (fun (SynBinding(headPat = pattern; expr = body)) -> + addMany [Expression body; Pattern pattern] + ) andBangs + add <| Pattern pattern + | [] -> () // error case. @@TODO@@ any other handling needed here? | SynExpr.Ident(ident) -> add <| Identifier([ident.idText], ident.idRange) | SynExpr.LongIdent(_, SynLongIdent(ident, _, _), _, range) -> add <| Identifier(List.map (fun (identifier: Ident) -> identifier.idText) ident, range) @@ -414,7 +420,7 @@ module Ast = | SynArgPats.Pats(patterns) -> patterns |> List.revIter (Pattern >> add) | SynArgPats.NamePatPairs(namePatterns, _, _) -> - namePatterns |> List.revIter (fun (_, _, pattern) -> pattern |> Pattern |> add) + namePatterns |> List.revIter (_.Pattern >> Pattern >> add) let inline private typeRepresentationChildren node add = match node with @@ -471,7 +477,7 @@ module Ast = | Else(expression) | Expression(expression) -> expressionChildren expression add - | File(ParsedInput.ImplFile(ParsedImplFileInput(_, _, _, _, _, moduleOrNamespaces, _, _, _))) -> + | File(ParsedInput.ImplFile(ParsedImplFileInput(contents = moduleOrNamespaces))) -> moduleOrNamespaces |> List.revIter (ModuleOrNamespace >> add) | UnionCase(unionCase) -> unionCaseChildren unionCase add diff --git a/src/FSharpLint.Core/Rules/Conventions/AsyncExceptionWithoutReturn.fs b/src/FSharpLint.Core/Rules/Conventions/AsyncExceptionWithoutReturn.fs index 978ed38ef..ff9e7de5a 100644 --- a/src/FSharpLint.Core/Rules/Conventions/AsyncExceptionWithoutReturn.fs +++ b/src/FSharpLint.Core/Rules/Conventions/AsyncExceptionWithoutReturn.fs @@ -55,7 +55,7 @@ let rec checkExpression (expression: SynExpr) (range: range) (continuation: unit } | SynExpr.App (_, _, funcExpr, _, range) -> checkExpression funcExpr range returnEmptyArray - | SynExpr.LetOrUse (_, _, _, body, range, _) -> + | SynExpr.LetOrUse (_, _, _, false, _, body, range, _) -> checkExpression body range returnEmptyArray | _ -> Array.empty) (continuation ()) diff --git a/src/FSharpLint.Core/Rules/Conventions/AvoidTooShortNames.fs b/src/FSharpLint.Core/Rules/Conventions/AvoidTooShortNames.fs index 5c7f04171..32ad88a2e 100644 --- a/src/FSharpLint.Core/Rules/Conventions/AvoidTooShortNames.fs +++ b/src/FSharpLint.Core/Rules/Conventions/AvoidTooShortNames.fs @@ -54,8 +54,10 @@ let private getParameterWithBelowMinimumLength (pats: SynPat list): (Ident * str let private getIdentifiers (args:AstNodeRuleParams) = match args.AstNode with - | AstNode.Expression(SynExpr.LetOrUseBang(_, _, _, pat, _, _, _, _, _)) -> - getParameterWithBelowMinimumLength [pat] + | AstNode.Expression(SynExpr.LetOrUse(isBang = true; bindings = binding :: _)) -> + match binding with + | SynBinding(headPat = pat) -> + getParameterWithBelowMinimumLength [pat] | AstNode.Expression(SynExpr.Lambda(_, _, lambdaArgs, _, _, _, _)) -> let lambdaIdent = FunctionReimplementation.getLambdaParamIdent lambdaArgs match lambdaIdent with diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/BindingHelper.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/BindingHelper.fs index dd9ec7ced..a13851689 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/BindingHelper.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/BindingHelper.fs @@ -8,6 +8,6 @@ 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 + | AstNode.Expression(SynExpr.LetOrUse(_, false, _, false, _, _, _, _)) -> true | _ -> false else false diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/FavourIgnoreOverLetWild.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/FavourIgnoreOverLetWild.fs index 4a40f86fa..7aaa963b6 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/FavourIgnoreOverLetWild.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/FavourIgnoreOverLetWild.fs @@ -33,7 +33,7 @@ let private runner (args:AstNodeRuleParams) = let bindingRange = match args.GetParents(args.NodeIndex) with | AstNode.ModuleDeclaration(SynModuleDecl.Let(_, _, range)) :: _ - | AstNode.Expression(SynExpr.LetOrUse(_, false, _, _, range, _)) :: _ -> + | AstNode.Expression(SynExpr.LetOrUse(_, false, _, false, _, _, range, _)) :: _ -> Some(range) | _ -> None diff --git a/src/FSharpLint.Core/Rules/Conventions/Binding/UselessBinding.fs b/src/FSharpLint.Core/Rules/Conventions/Binding/UselessBinding.fs index 380457eb2..7595ab4bb 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Binding/UselessBinding.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Binding/UselessBinding.fs @@ -52,7 +52,7 @@ let private runner (args:AstNodeRuleParams) = match args.GetParents(args.NodeIndex) with | AstNode.ModuleDeclaration(SynModuleDecl.Let(_, _, range)) :: _ -> Some({ FromRange = range; FromText = "let"; ToText = String.Empty }) - | AstNode.Expression(SynExpr.LetOrUse(_, false, _, _, range, _)) :: _ -> + | AstNode.Expression(SynExpr.LetOrUse(_, false, _, false, _, _, range, _)) :: _ -> Some({ FromRange = range; FromText = "use"; ToText = String.Empty }) | _ -> None match args.AstNode with diff --git a/src/FSharpLint.Core/Rules/Conventions/FavourNonMutablePropertyInitialization.fs b/src/FSharpLint.Core/Rules/Conventions/FavourNonMutablePropertyInitialization.fs index cf1986321..7a5dfd304 100644 --- a/src/FSharpLint.Core/Rules/Conventions/FavourNonMutablePropertyInitialization.fs +++ b/src/FSharpLint.Core/Rules/Conventions/FavourNonMutablePropertyInitialization.fs @@ -64,7 +64,7 @@ let rec private processLetBinding (instanceNames: Set) (body: SynExpr) ( and [] processExpression (expression: SynExpr) (continuation: unit -> array) : array = Array.append (match expression with - | SynExpr.LetOrUse(_, _, bindings, body, _, _) -> + | SynExpr.LetOrUse(_, _, _, false, bindings, body, _, _) -> let instanceNames = extraFromBindings bindings List.Empty |> Set.ofList processLetBinding instanceNames body returnEmptyArray | SynExpr.Sequential(_, _, expr1, expr2, _, _) -> @@ -74,7 +74,7 @@ and [] processExpression (expression: SynExpr) (continuation: unit -> let runner args = match args.AstNode with - | Binding(SynBinding(_, _, _, _, _, _, _, _, _, SynExpr.LetOrUse(_, _, bindings, body, _, _), _, _, _)) -> + | Binding(SynBinding(_, _, _, _, _, _, _, _, _, SynExpr.LetOrUse(_, _, _, false, bindings, body, _, _), _, _, _)) -> let instanceNames = extraFromBindings bindings List.Empty |> Set.ofList processLetBinding instanceNames body returnEmptyArray | Match(SynMatchClause(_, _, expr, _, _, _)) -> diff --git a/src/FSharpLint.Core/Rules/Conventions/FavourStaticEmptyFields.fs b/src/FSharpLint.Core/Rules/Conventions/FavourStaticEmptyFields.fs index 0ba6790fa..a4ed9e8cb 100644 --- a/src/FSharpLint.Core/Rules/Conventions/FavourStaticEmptyFields.fs +++ b/src/FSharpLint.Core/Rules/Conventions/FavourStaticEmptyFields.fs @@ -49,7 +49,7 @@ let private runner (args: AstNodeRuleParams) = | AstNode.Expression(SynExpr.Record(_, _, synExprRecordField, _)) -> let mapping = function - | SynExprRecordField(_, _, expr, _) -> + | SynExprRecordField(_, _, expr, _, _) -> match expr with | Some(SynExpr.ArrayOrList(isArray, [], range)) -> let emptyLiteralType = if isArray then EmptyArrayLiteral else EmptyListLiteral diff --git a/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs b/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs index 8e8fd6f4e..75956342a 100644 --- a/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs +++ b/src/FSharpLint.Core/Rules/Conventions/Naming/NamingHelper.fs @@ -405,7 +405,7 @@ let rec private innerGetPatternIdents<'Item> (accessibility:AccessControlLevel) idents (match args with | SynArgPats.NamePatPairs(pats, _, _) -> - innerGetAllPatternIdents AccessControlLevel.Private getIdents (pats |> List.map (fun(_, _, synPat) -> synPat)) + innerGetAllPatternIdents AccessControlLevel.Private getIdents (pats |> List.map _.Pattern) | SynArgPats.Pats(pats) -> innerGetAllPatternIdents AccessControlLevel.Private getIdents pats) | SynPat.Named(_, _, access, _) -> diff --git a/src/FSharpLint.Core/Rules/Conventions/RecursiveAsyncFunction.fs b/src/FSharpLint.Core/Rules/Conventions/RecursiveAsyncFunction.fs index 834ba4ac0..10c2268a8 100644 --- a/src/FSharpLint.Core/Rules/Conventions/RecursiveAsyncFunction.fs +++ b/src/FSharpLint.Core/Rules/Conventions/RecursiveAsyncFunction.fs @@ -58,7 +58,7 @@ let checkRecursiveAsyncFunction (args:AstNodeRuleParams) (range:Range) (doBangEx match crumb with | AstNode.ModuleDeclaration (SynModuleDecl.Let (true, bindings, _)) -> bindings - | AstNode.Expression (SynExpr.LetOrUse (true, false, bindings, _, _, _)) -> + | AstNode.Expression (SynExpr.LetOrUse (true, false, _, false, bindings, _, _, _)) -> bindings | _ -> List.Empty) |> List.choose getFunctionNameFromAsyncCompExprBinding diff --git a/src/FSharpLint.Core/Rules/Typography/Indentation.fs b/src/FSharpLint.Core/Rules/Typography/Indentation.fs index a9d9372bf..87d9aa9f1 100644 --- a/src/FSharpLint.Core/Rules/Typography/Indentation.fs +++ b/src/FSharpLint.Core/Rules/Typography/Indentation.fs @@ -42,7 +42,7 @@ module ContextBuilder = | (SynExpr.Record ( _, _, fields, _)) -> let subRecords = fields - |> List.choose (fun (SynExprRecordField(_, _, expr, _)) -> Option.map collectRecordFields expr) + |> List.choose (fun (SynExprRecordField(_, _, expr, _, _)) -> Option.map collectRecordFields expr) |> List.concat fields::subRecords | _ -> @@ -77,7 +77,7 @@ module ContextBuilder = collectRecordFields record |> List.collect (fun recordFields -> recordFields - |> List.map (fun (SynExprRecordField((fieldName, _), _, _, _)) -> fieldName.Range) + |> List.map (fun (SynExprRecordField((fieldName, _), _, _, _, _)) -> fieldName.Range) |> firstRangePerLine |> createAbsoluteAndOffsetOverridesBasedOnFirst) | Expression (SynExpr.ArrayOrListComputed(expr=(SynExpr.ComputationExpr(expr=expr)))) -> @@ -124,7 +124,14 @@ module ContextBuilder = |> createAbsoluteAndOffsetOverridesBasedOnFirst | Pattern (SynPat.Record (fieldPats=fieldPats)) -> fieldPats - |> List.map (fun ((_, fieldIdent), _, _) -> fieldIdent.idRange) + |> List.map (fun patPairField -> patPairField.Range) + // @@TODO@@ Do we need to look at the ranges inside FieldName here? + //let fieldIdent = + // match patPairField.FieldName.LongIdent with + // | [id] -> id + // | lid -> List.last lid + // + //fieldIdent.idRange) |> firstRangePerLine |> createAbsoluteAndOffsetOverridesBasedOnFirst | _ -> List.Empty diff --git a/tests/FSharpLint.Core.Tests/Framework/TestAbstractSyntaxArray.fs b/tests/FSharpLint.Core.Tests/Framework/TestAbstractSyntaxArray.fs index 34ba45144..0951a1016 100644 --- a/tests/FSharpLint.Core.Tests/Framework/TestAbstractSyntaxArray.fs +++ b/tests/FSharpLint.Core.Tests/Framework/TestAbstractSyntaxArray.fs @@ -26,7 +26,7 @@ type TestAst() = match ast with | ParsedInput.ImplFile(implFile) -> match implFile with - | ParsedImplFileInput(_, _, _, _, _, Module(app)::_, _, _, _) -> + | ParsedImplFileInput(_, _, _, _, Module(app)::_, _, _, _) -> app | _ -> failwith "Expected at least one module or namespace." | _ -> failwith "Expected an implementation file." From de27b1b3bde664fffbdb82589ed9f71b5a22a5ee Mon Sep 17 00:00:00 2001 From: Richard Webb Date: Sun, 19 Oct 2025 19:49:54 +0100 Subject: [PATCH 2/2] Temporarily disable lint warning about expressionChildren() being too long --- src/FSharpLint.Core/Framework/Ast.fs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/FSharpLint.Core/Framework/Ast.fs b/src/FSharpLint.Core/Framework/Ast.fs index 32ec02cc7..1c4de619f 100644 --- a/src/FSharpLint.Core/Framework/Ast.fs +++ b/src/FSharpLint.Core/Framework/Ast.fs @@ -256,6 +256,8 @@ module Ast = add <| Pattern lhs add <| Pattern rhs + + // fsharplint:disable FL0025 let inline private expressionChildren (node: SynExpr) (add: AstNode -> unit) = let addMany = List.iter add @@ -381,6 +383,8 @@ module Ast = expr2 |> Option.iter (Expression >> add) | _ -> () + // fsharplint:enable + let inline private typeSimpleRepresentationChildren node add = match node with | SynTypeDefnSimpleRepr.Union(_, unionCases, _) -> List.revIter (UnionCase >> add) unionCases