diff --git a/build/ScaffoldCodeFix.fs b/build/ScaffoldCodeFix.fs index 1e297e8a6..0e3d1cb59 100644 --- a/build/ScaffoldCodeFix.fs +++ b/build/ScaffoldCodeFix.fs @@ -38,48 +38,13 @@ let mkCodeFixImplementation codeFixName = open FSharp.Compiler.Symbols open FSharp.Compiler.Syntax +open FSharp.Compiler.Text open FsToolkit.ErrorHandling open Ionide.LanguageServerProtocol.Types open FsAutoComplete.CodeFix.Types open FsAutoComplete open FsAutoComplete.LspHelpers -// The syntax tree can be an intimidating set of types to work with. -// It is a tree structure but it consists out of many different types. -// See https://fsharp.github.io/fsharp-compiler-docs/reference/fsharp-compiler-syntax.html -// It can be useful to inspect a syntax tree via a code sample using https://fsprojects.github.io/fantomas-tools/#/ast -// For example `let a b c = ()` in -// https://fsprojects.github.io/fantomas-tools/#/ast?data=N4KABGBEAmCmBmBLAdrAzpAXFSAacUiaAYmolmPAIYA2as%%2BEkAxgPZwWQ2wAuYVYAEZhmYALxgAFAEo8BSLAAeAByrJoFHgCcArrBABfIA -// Let's say we want to find the (FCS) range for identifier `a`. -let visitSyntaxTree - (cursor: FSharp.Compiler.Text.pos) - (tree: ParsedInput) - = - // We will use a syntax visitor to traverse the tree from the top to the node of interest. - // See https://github.com/dotnet/fsharp/blob/main/src/Compiler/Service/ServiceParseTreeWalk.fsi - // We implement the different members of interest and allow the default traversal to move to the lower levels we care about. - let visitor = - // A visitor will report the first item it finds. - // Think of it as `List.tryPick` - // It is not an ideal solution to find all nodes inside a tree, be aware of that. - // For example finding all function names. - {{ new SyntaxVisitorBase() with - // We know that `a` will be part of a `SynPat.LongIdent` - // This was visible in the online tool. - member _.VisitPat(path, defaultTraverse, synPat) = - match synPat with - | SynPat.LongIdent(longDotId = SynLongIdent(id = [ functionNameIdent ])) -> - // When our code fix operates on the user's code there is no way of knowing what will be inside the syntax tree. - // So we need to be careful and verify that the pattern is indeed matching the position of the cursor. - if FSharp.Compiler.Text.Range.rangeContainsPos functionNameIdent.idRange cursor then - Some functionNameIdent.idRange - else - None - | _ -> None }} - - // Invoke the visitor and kick off the traversal. - SyntaxTraversal.Traverse(cursor, tree, visitor) - // TODO: add proper title for code fix let title = "%s{codeFixName} Codefix" @@ -98,9 +63,29 @@ let fix let! (parseAndCheckResults:ParseAndCheckResults, line:string, sourceText:IFSACSourceText) = getParseResultsForFile fileName fcsPos - // As an example, we want to check whether the users cursor is inside a function definition name. - // We will traverse the syntax tree to verify this is the case. - match visitSyntaxTree fcsPos parseAndCheckResults.GetParseResults.ParseTree with + // The syntax tree can be an intimidating set of types to work with. + // It is a tree structure but it consists out of many different types. + // See https://fsharp.github.io/fsharp-compiler-docs/reference/fsharp-compiler-syntax.html + // It can be useful to inspect a syntax tree via a code sample using https://fsprojects.github.io/fantomas-tools/#/ast + // For example `let a b c = ()` in + // https://fsprojects.github.io/fantomas-tools/#/ast?data=N4KABGBEAmCmBmBLAdrAzpAXFSAacUiaAYmolmPAIYA2as%%2BEkAxgPZwWQ2wAuYVYAEZhmYALxgAFAEo8BSLAAeAByrJoFHgCcArrBABfIA + // Let's say we want to find the (FCS) range for identifier `a` if the user's cursor is inside the function name. + // We will query the syntax tree to verify this is the case. + let maybeFunctionNameRange = + (fcsPos, parseAndCheckResults.GetParseResults.ParseTree) + ||> ParsedInput.tryPick (fun _path node -> + match node with + // We know that `a` will be part of a `SynPat.LongIdent` + // This was visible in the online tool. + | SyntaxNode.SynPat(SynPat.LongIdent(longDotId = SynLongIdent(id = [ functionNameIdent ]))) when + // When our code fix operates on the user's code there is no way of knowing what will be inside the syntax tree. + // So we need to be careful and verify that the pattern is indeed matching the position of the cursor. + Range.rangeContainsPos functionNameIdent.idRange fcsPos + -> + Some functionNameIdent.idRange + | _ -> None) + + match maybeFunctionNameRange with | None -> // The cursor is not in a position we are interested in. // This code fix should not trigger any suggestions so we return an empty list. diff --git a/src/FsAutoComplete.Core/AbstractClassStubGenerator.fs b/src/FsAutoComplete.Core/AbstractClassStubGenerator.fs index 9266c09ac..458c1f62f 100644 --- a/src/FsAutoComplete.Core/AbstractClassStubGenerator.fs +++ b/src/FsAutoComplete.Core/AbstractClassStubGenerator.fs @@ -27,72 +27,6 @@ type AbstractClassData = | ObjExpr(baseTy = t) | ExplicitImpl(baseTy = t) -> expandTypeParameters t -let private (|ExplicitCtor|_|) = - function - | SynMemberDefn.Member( - memberDefn = SynBinding(valData = SynValData(memberFlags = Some({ MemberKind = SynMemberKind.Constructor })))) -> - Some() - | _ -> None - -/// checks to see if a type definition inherits an abstract class, and if so collects the members defined at that -let private walkTypeDefn (SynTypeDefn(_, repr, members, implicitCtor, _, _)) = - option { - let reprMembers = - match repr with - | SynTypeDefnRepr.ObjectModel(_, members, _) -> members // repr members already includes the implicit ctor if present - | _ -> Option.toList implicitCtor - - let allMembers = reprMembers @ members - - let! inheritType, inheritMemberRange = // this must exist for abstract types - allMembers - |> List.tryPick (function - | SynMemberDefn.ImplicitInherit(inheritType, _, _, range) -> Some(inheritType, range) - | _ -> None) - - let furthestMemberToSkip, otherMembers = - ((inheritMemberRange, []), allMembers) - // find the last of the following kinds of members, as object-programming members must come after these - // * implicit/explicit constructors - // * `inherit` expressions - // * class-level `do`/`let` bindings (`do` bindings are actually `LetBindings` in the AST) - ||> List.fold (fun (m, otherMembers) memb -> - match memb with - | (SynMemberDefn.ImplicitCtor _ | ExplicitCtor | SynMemberDefn.ImplicitInherit _ | SynMemberDefn.LetBindings _) as possible -> - let c = Range.rangeOrder.Compare(m, possible.Range) - - let m' = if c < 0 then possible.Range else m - - m', otherMembers - | otherMember -> m, otherMember :: otherMembers) - - let otherMembersInDeclarationOrder = otherMembers |> List.rev - return AbstractClassData.ExplicitImpl(inheritType, otherMembersInDeclarationOrder, furthestMemberToSkip) - - } - -/// find the declaration of the abstract class being filled in at the given position -let private tryFindAbstractClassExprInParsedInput - (pos: Position) - (parsedInput: ParsedInput) - : AbstractClassData option = - SyntaxTraversal.Traverse( - pos, - parsedInput, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(path, traverseExpr, defaultTraverse, expr) = - match expr with - | SynExpr.ObjExpr( - objType = baseTy; withKeyword = withKeyword; bindings = bindings; newExprRange = newExprRange) -> - Some(AbstractClassData.ObjExpr(baseTy, bindings, newExprRange, withKeyword)) - | _ -> defaultTraverse expr - - override _.VisitModuleDecl(_, defaultTraverse, decl) = - match decl with - | SynModuleDecl.Types(types, _) -> List.tryPick walkTypeDefn types - | _ -> defaultTraverse decl } - ) - /// Walk the parse tree for the given document and look for the definition of any abstract classes in use at the given pos. /// This looks for implementations of abstract types in object expressions, as well as inheriting of abstract types inside class type declarations. let tryFindAbstractClassExprInBufferAtPos @@ -102,7 +36,56 @@ let tryFindAbstractClassExprInBufferAtPos = asyncOption { let! parseResults = codeGenService.ParseFileInProject document.FileName - return! tryFindAbstractClassExprInParsedInput pos parseResults.ParseTree + + return! + (pos, parseResults.ParseTree) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynExpr(SynExpr.ObjExpr( + objType = baseTy; withKeyword = withKeyword; bindings = bindings; newExprRange = newExprRange)) -> + Some(ObjExpr(baseTy, bindings, newExprRange, withKeyword)) + + | SyntaxNode.SynTypeDefn(SynTypeDefn(_, repr, members, implicitCtor, _, _)) -> + option { + let reprMembers = + match repr with + | SynTypeDefnRepr.ObjectModel(_, members, _) -> members // repr members already includes the implicit ctor if present + | _ -> Option.toList implicitCtor + + let allMembers = reprMembers @ members + + let! inheritType, inheritMemberRange = // this must exist for abstract types + allMembers + |> List.tryPick (function + | SynMemberDefn.ImplicitInherit(inheritType, _, _, range) -> Some(inheritType, range) + | _ -> None) + + let furthestMemberToSkip, otherMembers = + ((inheritMemberRange, []), allMembers) + // find the last of the following kinds of members, as object-programming members must come after these + // * implicit/explicit constructors + // * `inherit` expressions + // * class-level `do`/`let` bindings (`do` bindings are actually `LetBindings` in the AST) + ||> List.fold (fun (m, otherMembers) memb -> + match memb with + | SynMemberDefn.ImplicitCtor _ + | SynMemberDefn.Member( + memberDefn = SynBinding( + valData = SynValData(memberFlags = Some { MemberKind = SynMemberKind.Constructor }))) + | SynMemberDefn.ImplicitInherit _ + | SynMemberDefn.LetBindings _ as possible -> + let c = Range.rangeOrder.Compare(m, possible.Range) + + let m' = if c < 0 then possible.Range else m + + m', otherMembers + | otherMember -> m, otherMember :: otherMembers) + + let otherMembersInDeclarationOrder = otherMembers |> List.rev + return ExplicitImpl(inheritType, otherMembersInDeclarationOrder, furthestMemberToSkip) + } + + | _ -> None) } let getMemberNameAndRanges abstractClassData = diff --git a/src/FsAutoComplete.Core/Commands.fs b/src/FsAutoComplete.Core/Commands.fs index f25d996f5..66b93c4b5 100644 --- a/src/FsAutoComplete.Core/Commands.fs +++ b/src/FsAutoComplete.Core/Commands.fs @@ -1267,142 +1267,102 @@ type Commands() = static member GenerateXmlDocumentation(tyRes: ParseAndCheckResults, triggerPosition: Position, lineStr: LineStr) = asyncResult { let longIdentContainsPos (longIdent: LongIdent) (pos: FSharp.Compiler.Text.pos) = - longIdent - |> List.tryFind (fun i -> rangeContainsPos i.idRange pos) - |> Option.isSome - - let isLowerAstElemWithEmptyPreXmlDoc input pos = - SyntaxTraversal.Traverse( - pos, - input, - { new SyntaxVisitorBase<_>() with - member _.VisitBinding(_, defaultTraverse, synBinding) = - match synBinding with - | SynBinding(xmlDoc = xmlDoc; valData = valData) as s when - rangeContainsPos s.RangeOfBindingWithoutRhs pos && xmlDoc.IsEmpty - -> - match valData with - | SynValData(memberFlags = Some({ MemberKind = SynMemberKind.PropertyGet })) - | SynValData(memberFlags = Some({ MemberKind = SynMemberKind.PropertySet })) - | SynValData(memberFlags = Some({ MemberKind = SynMemberKind.PropertyGetSet })) -> None - | _ -> Some false - | _ -> defaultTraverse synBinding - - member _.VisitComponentInfo(_, synComponentInfo) = - match synComponentInfo with - | SynComponentInfo(longId = longId; xmlDoc = xmlDoc) when - longIdentContainsPos longId pos && xmlDoc.IsEmpty - -> - Some false - | _ -> None - - member _.VisitRecordDefn(_, fields, _) = - let isInLine c = - match c with - | SynField(xmlDoc = xmlDoc; idOpt = Some ident) when - rangeContainsPos ident.idRange pos && xmlDoc.IsEmpty - -> - Some false - | _ -> None - - fields |> List.tryPick isInLine - - member _.VisitUnionDefn(_, cases, _) = - let isInLine c = - match c with - | SynUnionCase(xmlDoc = xmlDoc; ident = (SynIdent(ident = ident))) when - rangeContainsPos ident.idRange pos && xmlDoc.IsEmpty - -> - Some false - | _ -> None - - cases |> List.tryPick isInLine - - member _.VisitEnumDefn(_, cases, _) = - let isInLine b = - match b with - | SynEnumCase(xmlDoc = xmlDoc; ident = (SynIdent(ident = ident))) when - rangeContainsPos ident.idRange pos && xmlDoc.IsEmpty - -> - Some false - | _ -> None - - cases |> List.tryPick isInLine - - member _.VisitLetOrUse(_, _, defaultTraverse, bindings, _) = - let isInLine b = - match b with - | SynBinding(xmlDoc = xmlDoc) as s when - rangeContainsPos s.RangeOfBindingWithoutRhs pos && xmlDoc.IsEmpty - -> - Some false - | _ -> defaultTraverse b - - bindings |> List.tryPick isInLine - - member _.VisitExpr(_, _, defaultTraverse, expr) = defaultTraverse expr } // needed for nested let bindings - ) - - let isModuleOrNamespaceOrAutoPropertyWithEmptyPreXmlDoc input pos = - SyntaxTraversal.Traverse( - pos, - input, - { new SyntaxVisitorBase<_>() with - - member _.VisitModuleOrNamespace(_, synModuleOrNamespace) = - match synModuleOrNamespace with - | SynModuleOrNamespace(longId = longId; xmlDoc = xmlDoc) when - longIdentContainsPos longId pos && xmlDoc.IsEmpty - -> - Some false - | SynModuleOrNamespace(decls = decls) -> - - let rec findNested decls = - decls - |> List.tryPick (fun d -> - match d with - | SynModuleDecl.NestedModule(moduleInfo = moduleInfo; decls = decls) -> - match moduleInfo with - | SynComponentInfo(longId = longId; xmlDoc = xmlDoc) when - longIdentContainsPos longId pos && xmlDoc.IsEmpty - -> - Some false - | _ -> findNested decls - | SynModuleDecl.Types(typeDefns = typeDefns) -> - typeDefns - |> List.tryPick (fun td -> - match td with - | SynTypeDefn(typeRepr = SynTypeDefnRepr.ObjectModel(_, members, _)) -> - members - |> List.tryPick (fun m -> - match m with - | SynMemberDefn.AutoProperty(ident = ident; xmlDoc = xmlDoc) when - rangeContainsPos ident.idRange pos && xmlDoc.IsEmpty - -> - Some true - | SynMemberDefn.GetSetMember( - memberDefnForSet = Some(SynBinding( - xmlDoc = xmlDoc; headPat = SynPat.LongIdent(longDotId = longDotId)))) when - rangeContainsPos longDotId.Range pos && xmlDoc.IsEmpty - -> - Some false - | SynMemberDefn.GetSetMember( - memberDefnForGet = Some(SynBinding( - xmlDoc = xmlDoc; headPat = SynPat.LongIdent(longDotId = longDotId)))) when - rangeContainsPos longDotId.Range pos && xmlDoc.IsEmpty - -> - Some false - | _ -> None) - | _ -> None) - | _ -> None) - - findNested decls } - ) + longIdent |> List.exists (fun i -> rangeContainsPos i.idRange pos) let isAstElemWithEmptyPreXmlDoc input pos = - match isLowerAstElemWithEmptyPreXmlDoc input pos with - | Some isAutoProperty -> Some isAutoProperty - | _ -> isModuleOrNamespaceOrAutoPropertyWithEmptyPreXmlDoc input pos + (pos, input) + ||> ParsedInput.tryPickLast (fun _path node -> + let (|AnyGetSetMemberInRange|_|) = + List.tryPick (function + | SynMemberDefn.GetSetMember( + memberDefnForSet = Some(SynBinding(xmlDoc = xmlDoc; headPat = SynPat.LongIdent(longDotId = longDotId)))) + | SynMemberDefn.GetSetMember( + memberDefnForGet = Some(SynBinding(xmlDoc = xmlDoc; headPat = SynPat.LongIdent(longDotId = longDotId)))) when + rangeContainsPos longDotId.Range pos && xmlDoc.IsEmpty + -> + Some() + | _ -> None) + + match node with + | SyntaxNode.SynBinding(SynBinding( + valData = SynValData(Some { MemberKind = SynMemberKind.PropertyGet }, _, _))) + | SyntaxNode.SynBinding(SynBinding( + valData = SynValData(Some { MemberKind = SynMemberKind.PropertySet }, _, _))) + | SyntaxNode.SynBinding(SynBinding( + valData = SynValData(Some { MemberKind = SynMemberKind.PropertyGetSet }, _, _))) -> None + + | SyntaxNode.SynBinding(SynBinding(xmlDoc = xmlDoc) as s) when + rangeContainsPos s.RangeOfBindingWithoutRhs pos && xmlDoc.IsEmpty + -> + Some false + + | SyntaxNode.SynTypeDefn(SynTypeDefn(typeRepr = SynTypeDefnRepr.ObjectModel(members = AnyGetSetMemberInRange))) -> + Some false + + | SyntaxNode.SynTypeDefn(SynTypeDefn(typeInfo = SynComponentInfo(longId = longId; xmlDoc = xmlDoc))) when + longIdentContainsPos longId pos && xmlDoc.IsEmpty + -> + Some false + + | SyntaxNode.SynTypeDefn(SynTypeDefn( + typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Record(recordFields = fields), _))) -> + let isInLine c = + match c with + | SynField(xmlDoc = xmlDoc; idOpt = Some ident) when rangeContainsPos ident.idRange pos && xmlDoc.IsEmpty -> + Some false + | _ -> None + + fields |> List.tryPick isInLine + + | SyntaxNode.SynTypeDefn(SynTypeDefn( + typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Union(unionCases = cases), _))) -> + let isInLine c = + match c with + | SynUnionCase(xmlDoc = xmlDoc; ident = SynIdent(ident = ident)) when + rangeContainsPos ident.idRange pos && xmlDoc.IsEmpty + -> + Some false + | _ -> None + + cases |> List.tryPick isInLine + + | SyntaxNode.SynTypeDefn(SynTypeDefn( + typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Enum(cases = cases), _))) -> + let isInLine c = + match c with + | SynEnumCase(xmlDoc = xmlDoc; ident = SynIdent(ident = ident)) when + rangeContainsPos ident.idRange pos && xmlDoc.IsEmpty + -> + Some false + | _ -> None + + cases |> List.tryPick isInLine + + | SyntaxNode.SynModuleOrNamespace(SynModuleOrNamespace(longId = longId; xmlDoc = xmlDoc)) when + longIdentContainsPos longId pos && xmlDoc.IsEmpty + -> + Some false + + | SyntaxNode.SynModule(SynModuleDecl.NestedModule( + moduleInfo = SynComponentInfo(longId = longId; xmlDoc = xmlDoc))) when + longIdentContainsPos longId pos && xmlDoc.IsEmpty + -> + Some false + + | SyntaxNode.SynMemberDefn(SynMemberDefn.AutoProperty(ident = ident; xmlDoc = xmlDoc)) when + rangeContainsPos ident.idRange pos && xmlDoc.IsEmpty + -> + Some true + + | SyntaxNode.SynMemberDefn(SynMemberDefn.GetSetMember( + memberDefnForGet = Some(SynBinding(xmlDoc = xmlDoc; headPat = SynPat.LongIdent(longDotId = longDotId))))) + | SyntaxNode.SynMemberDefn(SynMemberDefn.GetSetMember( + memberDefnForSet = Some(SynBinding(xmlDoc = xmlDoc; headPat = SynPat.LongIdent(longDotId = longDotId))))) when + rangeContainsPos longDotId.Range pos && xmlDoc.IsEmpty + -> + Some false + + | _ -> None) let trimmed = lineStr.TrimStart(' ') let indentLength = lineStr.Length - trimmed.Length diff --git a/src/FsAutoComplete.Core/FCSPatches.fs b/src/FsAutoComplete.Core/FCSPatches.fs index 2398f9252..f8baac0f4 100644 --- a/src/FsAutoComplete.Core/FCSPatches.fs +++ b/src/FsAutoComplete.Core/FCSPatches.fs @@ -4,10 +4,7 @@ module FsAutoComplete.FCSPatches open FSharp.Compiler.Syntax -open FSharp.Compiler.Text -open FsAutoComplete.UntypedAstUtils open FSharp.Compiler.CodeAnalysis -open FSharp.Compiler.EditorServices module internal SynExprAppLocationsImpl = let rec private searchSynArgExpr traverseSynExpr expr ranges = @@ -36,375 +33,6 @@ module internal SynExprAppLocationsImpl = | None -> Some(e.Range :: ranges), Some inner | _ -> None, Some inner - let getAllCurriedArgsAtPosition pos parseTree = - SyntaxTraversal.Traverse( - pos, - parseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_path, traverseSynExpr, defaultTraverse, expr) = - match expr with - | SynExpr.App(_exprAtomicFlag, _isInfix, funcExpr, argExpr, range) when Position.posEq pos range.Start -> - let isInfixFuncExpr = - match funcExpr with - | SynExpr.App(_, isInfix, _, _, _) -> isInfix - | _ -> false - - if isInfixFuncExpr then - traverseSynExpr funcExpr - else - let workingRanges = - match traverseSynExpr funcExpr with - | Some ranges -> ranges - | None -> [] - - let xResult, cacheOpt = searchSynArgExpr traverseSynExpr argExpr workingRanges - - match xResult, cacheOpt with - | Some ranges, _ -> Some ranges - | None, Some cache -> cache - | _ -> traverseSynExpr argExpr - | _ -> defaultTraverse expr } - ) - |> Option.map List.rev - -type FSharpParseFileResults with - - member scope.IsPositionContainedInACurriedParameter pos = - let result = - SyntaxTraversal.Traverse( - pos, - scope.ParseTree, - { new SyntaxVisitorBase<_>() with - member __.VisitExpr(_path, traverseSynExpr, defaultTraverse, expr) = defaultTraverse (expr) - - override __.VisitBinding(_, _, binding) = - match binding with - | SynBinding(valData = valData; range = ((ContainsPos pos) as range)) -> - let info = valData.SynValInfo.CurriedArgInfos - let mutable found = false - - for group in info do - for arg in group do - match arg.Ident with - | Some(IdentContainsPos pos) -> found <- true - | _ -> () - - if found then Some range else None - | _ -> None } - ) - - result.IsSome - - member scope.TryRangeOfParenEnclosingOpEqualsGreaterUsage opGreaterEqualPos = - /// reused pattern to find applications of => (a symptom of improper C# style lambdas) - let (|InfixAppOfOpEqualsGreater|_|) = - function - | SynExpr.App(ExprAtomicFlag.NonAtomic, - false, - SynExpr.App(ExprAtomicFlag.NonAtomic, true, Ident "op_EqualsGreater", actualParamListExpr, _), - actualLambdaBodyExpr, - _) -> Some(actualParamListExpr, actualLambdaBodyExpr) - | _ -> None - - - let visitor = - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - | SynExpr.Paren((InfixAppOfOpEqualsGreater(lambdaArgs, lambdaBody) as app), _, _, _) -> - Some(app.Range, lambdaArgs.Range, lambdaBody.Range) - | _ -> defaultTraverse expr - - member _.VisitBinding(_, defaultTraverse, binding) = - match binding with - | SynBinding(kind = SynBindingKind.Normal; expr = InfixAppOfOpEqualsGreater(lambdaArgs, lambdaBody) as app) -> - Some(app.Range, lambdaArgs.Range, lambdaBody.Range) - | _ -> defaultTraverse binding } - - SyntaxTraversal.Traverse(opGreaterEqualPos, scope.ParseTree, visitor) - - member scope.TryRangeOfRefCellDereferenceContainingPos expressionPos = - SyntaxTraversal.Traverse( - expressionPos, - scope.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - | SynExpr.App(_, false, SynExpr.Ident funcIdent, expr, _) -> - if - funcIdent.idText = "op_Dereference" - && Range.rangeContainsPos expr.Range expressionPos - then - Some funcIdent.idRange - else - None - | _ -> defaultTraverse expr } - ) - - member scope.TryRangeOfRecordExpressionContainingPos pos = - SyntaxTraversal.Traverse( - pos, - scope.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - | SynExpr.Record(_, _, _, range) when Range.rangeContainsPos range pos -> Some range - | _ -> defaultTraverse expr } - ) - - member scope.TryRangeOfExprInYieldOrReturn pos = - SyntaxTraversal.Traverse( - pos, - scope.ParseTree, - { new SyntaxVisitorBase<_>() with - member __.VisitExpr(_path, _, defaultTraverse, expr) = - match expr with - | SynExpr.YieldOrReturn(_, expr, range) - | SynExpr.YieldOrReturnFrom(_, expr, range) when Range.rangeContainsPos range pos -> Some expr.Range - | _ -> defaultTraverse expr } - ) - - /// Attempts to find an Ident of a pipeline containing the given position, and the number of args already applied in that pipeline. - /// For example, '[1..10] |> List.map ' would give back the ident of '|>' and 1, because it applied 1 arg (the list) to 'List.map'. - member scope.TryIdentOfPipelineContainingPosAndNumArgsApplied pos = - SyntaxTraversal.Traverse( - pos, - scope.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - | SynExpr.App(_, _, SynExpr.App(_, true, SynExpr.Ident ident, _, _), argExpr, _) when - Range.rangeContainsPos argExpr.Range pos - -> - if ident.idText = "op_PipeRight" then Some(ident, 1) - elif ident.idText = "op_PipeRight2" then Some(ident, 2) - elif ident.idText = "op_PipeRight3" then Some(ident, 3) - else None - | _ -> defaultTraverse expr } - ) - - /// Determines if the given position is inside a function or method application. - member scope.IsPosContainedInApplicationPatched pos = - let result = - SyntaxTraversal.Traverse( - pos, - scope.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, traverseSynExpr, defaultTraverse, expr) = - match expr with - | SynExpr.TypeApp(_, _, _, _, _, _, range) when Range.rangeContainsPos range pos -> Some range - | SynExpr.App(_, _, _, SynExpr.ComputationExpr(_, expr, _), range) when Range.rangeContainsPos range pos -> - traverseSynExpr expr - | SynExpr.App(_, _, _, _, range) when Range.rangeContainsPos range pos -> Some range - | _ -> defaultTraverse expr } - ) - - result.IsSome - - /// Attempts to find the range of a function or method that is being applied. Also accounts for functions in pipelines. - member scope.TryRangeOfFunctionOrMethodBeingAppliedPatched pos = - let rec getIdentRangeForFuncExprInApp traverseSynExpr expr pos : Range option = - match expr with - | SynExpr.Ident ident -> Some ident.idRange - - | SynExpr.LongIdent(_, _, _, range) -> Some range - - | SynExpr.Paren(expr, _, _, range) when Range.rangeContainsPos range pos -> - getIdentRangeForFuncExprInApp traverseSynExpr expr pos - - | SynExpr.TypeApp(expr, _, _, _, _, _, _) -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos - - | SynExpr.App(_, _, funcExpr, argExpr, _) -> - match argExpr with - | SynExpr.App(_, _, _, _, range) when Range.rangeContainsPos range pos -> - getIdentRangeForFuncExprInApp traverseSynExpr argExpr pos - - // Special case: `async { ... }` is actually a CompExpr inside of the argExpr of a SynExpr.App - | SynExpr.ComputationExpr(_, expr, range) when Range.rangeContainsPos range pos -> - getIdentRangeForFuncExprInApp traverseSynExpr expr pos - - | SynExpr.Paren(expr, _, _, range) when Range.rangeContainsPos range pos -> - getIdentRangeForFuncExprInApp traverseSynExpr expr pos - - | _ -> - match funcExpr with - | SynExpr.App(_, true, _, _, _) when Range.rangeContainsPos argExpr.Range pos -> - // x |> List.map - // Don't dive into the funcExpr (the operator expr) - // because we dont want to offer sig help for that! - getIdentRangeForFuncExprInApp traverseSynExpr argExpr pos - | _ -> - // Generally, we want to dive into the func expr to get the range - // of the identifier of the function we're after - getIdentRangeForFuncExprInApp traverseSynExpr funcExpr pos - - | SynExpr.LetOrUse(bindings = bindings; body = body; range = range) when Range.rangeContainsPos range pos -> - let binding = - bindings - |> List.tryFind (fun x -> Range.rangeContainsPos x.RangeOfBindingWithRhs pos) - - match binding with - | Some(SynBinding(expr = expr)) -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos - | None -> getIdentRangeForFuncExprInApp traverseSynExpr body pos - - | SynExpr.IfThenElse(ifExpr = ifExpr; thenExpr = thenExpr; elseExpr = elseExpr; range = range) when - Range.rangeContainsPos range pos - -> - if Range.rangeContainsPos ifExpr.Range pos then - getIdentRangeForFuncExprInApp traverseSynExpr ifExpr pos - elif Range.rangeContainsPos thenExpr.Range pos then - getIdentRangeForFuncExprInApp traverseSynExpr thenExpr pos - else - match elseExpr with - | None -> None - | Some expr -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos - - | SynExpr.Match(expr = expr; clauses = clauses; range = range) when Range.rangeContainsPos range pos -> - if Range.rangeContainsPos expr.Range pos then - getIdentRangeForFuncExprInApp traverseSynExpr expr pos - else - let clause = - clauses |> List.tryFind (fun clause -> Range.rangeContainsPos clause.Range pos) - - match clause with - | None -> None - | Some clause -> - match clause with - | SynMatchClause(whenExpr = whenExpr; resultExpr = resultExpr) -> - match whenExpr with - | None -> getIdentRangeForFuncExprInApp traverseSynExpr resultExpr pos - | Some whenExpr -> - if Range.rangeContainsPos whenExpr.Range pos then - getIdentRangeForFuncExprInApp traverseSynExpr whenExpr pos - else - getIdentRangeForFuncExprInApp traverseSynExpr resultExpr pos - - - // Ex: C.M(x, y, ...) <--- We want to find where in the tupled application the call is being made - | SynExpr.Tuple(_, exprs, _, tupRange) when Range.rangeContainsPos tupRange pos -> - let expr = exprs |> List.tryFind (fun expr -> Range.rangeContainsPos expr.Range pos) - - match expr with - | None -> None - | Some expr -> getIdentRangeForFuncExprInApp traverseSynExpr expr pos - - // Capture the body of a lambda, often nested in a call to a collection function - | SynExpr.Lambda(body = body) when Range.rangeContainsPos body.Range pos -> - getIdentRangeForFuncExprInApp traverseSynExpr body pos - - | SynExpr.Do(expr, range) when Range.rangeContainsPos range pos -> - getIdentRangeForFuncExprInApp traverseSynExpr expr pos - - | SynExpr.Assert(expr, range) when Range.rangeContainsPos range pos -> - getIdentRangeForFuncExprInApp traverseSynExpr expr pos - - | SynExpr.ArbitraryAfterError(_debugStr, range) when Range.rangeContainsPos range pos -> Some range - - | expr -> traverseSynExpr expr |> Option.map (fun expr -> expr) - - - SyntaxTraversal.Traverse( - pos, - scope.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, traverseSynExpr, defaultTraverse, expr) = - match expr with - | SynExpr.TypeApp(expr, _, _, _, _, _, range) when Range.rangeContainsPos range pos -> - getIdentRangeForFuncExprInApp traverseSynExpr expr pos - | SynExpr.App(_, _, _funcExpr, _, range) as app when Range.rangeContainsPos range pos -> - getIdentRangeForFuncExprInApp traverseSynExpr app pos - | _ -> defaultTraverse expr } - ) - - /// Gets the ranges of all arguments, if they can be found, for a function application at the given position. - member scope.GetAllArgumentsForFunctionApplicationAtPosition pos = - SynExprAppLocationsImpl.getAllCurriedArgsAtPosition pos scope.ParseTree - - - member scope.TryRangeOfExpressionBeingDereferencedContainingPos expressionPos = - SyntaxTraversal.Traverse( - expressionPos, - scope.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - | SynExpr.App(_, false, SynExpr.Ident funcIdent, expr, _) -> - if - funcIdent.idText = "op_Dereference" - && Range.rangeContainsPos expr.Range expressionPos - then - Some expr.Range - else - None - | _ -> defaultTraverse expr } - ) - - /// Attempts to find the range of the string interpolation that contains a given position. - member scope.TryRangeOfStringInterpolationContainingPos pos = - SyntaxTraversal.Traverse( - pos, - scope.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - | SynExpr.InterpolatedString(range = range) when Range.rangeContainsPos range pos -> Some range - | _ -> defaultTraverse expr } - ) - - member scope.ClassifyBinding(binding: SynBinding) = - match binding with - | SynBinding(valData = SynValData(memberFlags = None)) -> FSharpGlyph.Delegate - | _ -> FSharpGlyph.Method - - member scope.TryRangeOfNameOfNearestOuterBindingOrMember pos = - let tryGetIdentRangeFromBinding binding = - let glyph = scope.ClassifyBinding binding - - match binding with - | SynBinding(headPat = headPat) -> - match headPat with - | SynPat.LongIdent(longDotId = longIdentWithDots) -> - Some(binding.RangeOfBindingWithRhs, glyph, longIdentWithDots.LongIdent) - | SynPat.As(rhsPat = SynPat.Named(ident = SynIdent(ident, _); isThisVal = false)) - | SynPat.Named(SynIdent(ident, _), false, _, _) -> Some(binding.RangeOfBindingWithRhs, glyph, [ ident ]) - | _ -> None - - let rec walkBinding expr workingRange = - match expr with - - // This lets us dive into sub-expressions that may contain the binding we're after - | SynExpr.Sequential(_, _, expr1, expr2, _) -> - if Range.rangeContainsPos expr1.Range pos then - walkBinding expr1 workingRange - else - walkBinding expr2 workingRange - - | SynExpr.LetOrUse(bindings = bindings; body = bodyExpr) -> - let potentialNestedRange = - bindings - |> List.tryFind (fun binding -> Range.rangeContainsPos binding.RangeOfBindingWithRhs pos) - |> Option.bind tryGetIdentRangeFromBinding - - match potentialNestedRange with - | Some range -> walkBinding bodyExpr range - | None -> walkBinding bodyExpr workingRange - - | _ -> Some workingRange - - let visitor = - { new SyntaxVisitorBase<_>() with - override _.VisitExpr(_, _, defaultTraverse, expr) = defaultTraverse expr - - override _.VisitBinding(_path, defaultTraverse, binding) = - match binding with - | SynBinding(expr = expr) as b when Range.rangeContainsPos b.RangeOfBindingWithRhs pos -> - match tryGetIdentRangeFromBinding b with - | Some range -> walkBinding expr range - | None -> None - | _ -> defaultTraverse binding } - - SyntaxTraversal.Traverse(pos, scope.ParseTree, visitor) - module SyntaxTreeOps = open FSharp.Compiler.Syntax diff --git a/src/FsAutoComplete.Core/FCSPatches.fsi b/src/FsAutoComplete.Core/FCSPatches.fsi index 427831b30..58ef5dc28 100644 --- a/src/FsAutoComplete.Core/FCSPatches.fsi +++ b/src/FsAutoComplete.Core/FCSPatches.fsi @@ -3,10 +3,7 @@ module FsAutoComplete.FCSPatches open FSharp.Compiler.Syntax -open FSharp.Compiler.Text -open FsAutoComplete.UntypedAstUtils open FSharp.Compiler.CodeAnalysis -open FSharp.Compiler.EditorServices type LanguageFeatureShim = new: langFeature: string -> LanguageFeatureShim @@ -28,7 +25,3 @@ module LanguageVersionShim = module SyntaxTreeOps = val synExprContainsError: SynExpr -> bool - -type FSharpParseFileResults with - - member TryRangeOfNameOfNearestOuterBindingOrMember: pos: pos -> option diff --git a/src/FsAutoComplete.Core/InlayHints.fs b/src/FsAutoComplete.Core/InlayHints.fs index c95da3d0e..10750bf22 100644 --- a/src/FsAutoComplete.Core/InlayHints.fs +++ b/src/FsAutoComplete.Core/InlayHints.fs @@ -69,36 +69,6 @@ type Hint = let private isSignatureFile (f: string) = System.IO.Path.GetExtension(UMX.untag f) = ".fsi" -type private FSharp.Compiler.CodeAnalysis.FSharpParseFileResults with - // duplicates + extends the logic in FCS to match bindings of the form `let x: int = 12` - // so that they are considered logically the same as a 'typed' SynPat - member x.IsTypeAnnotationGivenAtPositionPatched pos = - let visitor: SyntaxVisitorBase = - { new SyntaxVisitorBase<_>() with - override _.VisitExpr(_path, _traverseSynExpr, defaultTraverse, expr) = - match expr with - | SynExpr.Typed(_expr, _typeExpr, range) when Position.posEq range.Start pos -> Some range - | _ -> defaultTraverse expr - - override visitor.VisitSimplePats(_path, pat) = - let path = SyntaxNode.SynPat pat :: _path - visitor.VisitPat(path, defaultTraversePat visitor path, pat) - - override visitor.VisitPat(path, defaultTraverse, pat) = - match pat with - | SynPat.Typed(_pat, _targetType, range) when Position.posEq range.Start pos -> Some range - | _ -> defaultTraversePat visitor path pat - - override _.VisitBinding(_path, defaultTraverse, binding) = - match binding with - | SynBinding( - headPat = SynPat.Named(range = patRange) - returnInfo = Some(SynBindingReturnInfo(typeName = SynType.LongIdent _))) -> Some patRange - | _ -> defaultTraverse binding } - - let result = SyntaxTraversal.Traverse(pos, x.ParseTree, visitor) - result.IsSome - let private getFirstPositionAfterParen (str: string) startPos = match str with | null -> -1 @@ -756,56 +726,29 @@ let tryGetExplicitTypeInfo (text: IFSACSourceText, ast: ParsedInput) (pos: Posit /// ``` /// let private getArgRangesOfFunctionApplication (ast: ParsedInput) pos = - SyntaxTraversal.Traverse( - pos, - ast, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, traverseSynExpr, defaultTraverse, expr) = - match expr with - | SynExpr.App(isInfix = false; funcExpr = funcExpr; range = range) when pos = range.Start -> - let isInfixFuncExpr = - match funcExpr with - | SynExpr.App(_, isInfix, _, _, _) -> isInfix - | _ -> false - - if isInfixFuncExpr then - traverseSynExpr funcExpr - else - let rec withoutParens = - function - | SynExpr.Paren(expr = expr) -> withoutParens expr - | expr -> expr - // f a (b,c) - // ^^^^^^^^^ App - // ... func - // ----- arg - // ^^^ App - // . func - // - arg - let rec findArgs expr = - match expr with - | SynExpr.Const(constant = SynConst.Unit) -> [] - | SynExpr.Paren(expr = expr) -> findArgs expr - | SynExpr.App(funcExpr = funcExpr; argExpr = argExpr) -> - let otherArgRanges = findArgs funcExpr - - let argRange = - let argRange = argExpr.Range - - let tupleArgs = - match argExpr |> withoutParens with - | SynExpr.Tuple(exprs = exprs) -> exprs |> List.map (fun e -> e.Range) - | _ -> argRange |> List.singleton - - (argRange, tupleArgs) - - argRange :: otherArgRanges - | _ -> [] - - findArgs expr |> Some - | _ -> defaultTraverse expr } - ) - |> Option.map List.rev + (pos, ast) + ||> ParsedInput.tryPick (fun _path node -> + let rec (|IgnoreParens|) = + function + | SynExpr.Paren(expr = expr) + | expr -> expr + + let rec (|Arg|) = + function + | IgnoreParens(SynExpr.Tuple(isStruct = false; exprs = exprs)) as arg -> + arg.Range, exprs |> List.map (fun e -> e.Range) + | expr -> expr.Range, [ expr.Range ] + + let rec (|Args|) = + function + | IgnoreParens(SynExpr.App(funcExpr = Args args; argExpr = Arg arg)) -> arg :: args + | _ -> [] + + match node with + | SyntaxNode.SynExpr(SynExpr.App(funcExpr = SynExpr.App(isInfix = true; argExpr = Args args); range = m)) + | SyntaxNode.SynExpr(SynExpr.App(funcExpr = SynExpr.App(isInfix = true; funcExpr = Args args); range = m)) + | SyntaxNode.SynExpr(SynExpr.App(isInfix = false; range = m) & Args args) when m.Start = pos -> Some(List.rev args) + | _ -> None) /// Note: No exhausting check. Doesn't check for: /// * is already typed (-> done by getting `ExplicitType`) diff --git a/src/FsAutoComplete.Core/RecordStubGenerator.fs b/src/FsAutoComplete.Core/RecordStubGenerator.fs index 3991ddb63..2958a28f4 100644 --- a/src/FsAutoComplete.Core/RecordStubGenerator.fs +++ b/src/FsAutoComplete.Core/RecordStubGenerator.fs @@ -185,37 +185,26 @@ let formatRecord writer.Dump() -let walkAndFindRecordBinding (pos, input) = - let walker = - { new SyntaxVisitorBase() with - member x.VisitExpr - ( - path: SyntaxVisitorPath, - traverseSynExpr: (SynExpr -> _ option), - defaultTraverse: (SynExpr -> _ option), - synExpr: SynExpr - ) = - match synExpr with - | SynExpr.Record(recordFields = recordFields; copyInfo = copyInfo) -> - Some - { Expr = synExpr - CopyExprOption = copyInfo - FieldExprList = recordFields - LastKnownGoodPosForSymbolLookup = - recordFields - |> List.tryLast - |> Option.map (function - | SynExprRecordField(fieldName = (id, _)) -> id.Range.Start) - |> Option.defaultValue synExpr.Range.Start } - | _ -> defaultTraverse synExpr } - - SyntaxTraversal.Traverse(pos, input, walker) - let tryFindRecordExprInBufferAtPos (codeGenService: ICodeGenerationService) (pos: Position) (document: Document) = asyncOption { let! parseResults = codeGenService.ParseFileInProject(document.FullName) - let! found = walkAndFindRecordBinding (pos, parseResults.ParseTree) + let! found = + (pos, parseResults.ParseTree) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynExpr(SynExpr.Record(recordFields = recordFields; copyInfo = copyInfo) as synExpr) -> + Some + { Expr = synExpr + CopyExprOption = copyInfo + FieldExprList = recordFields + LastKnownGoodPosForSymbolLookup = + recordFields + |> List.tryLast + |> Option.map (fun (SynExprRecordField(fieldName = (id, _))) -> id.Range.Start) + |> Option.defaultValue synExpr.Range.Start } + | _ -> None) + return found } diff --git a/src/FsAutoComplete.Core/UntypedAstUtils.fs b/src/FsAutoComplete.Core/UntypedAstUtils.fs index 387c5cc15..5a59a0374 100644 --- a/src/FsAutoComplete.Core/UntypedAstUtils.fs +++ b/src/FsAutoComplete.Core/UntypedAstUtils.fs @@ -658,41 +658,16 @@ module Completion = | SynType let atPos (pos: Position, ast: ParsedInput) : Context = - let visitor = - { new SyntaxVisitorBase() with - - member x.VisitExpr(path, traverseExpr, defaultTraverse, expr) : Context option = - if Range.rangeContainsPos expr.Range pos then - match expr with - | SynExpr.Const(SynConst.String _, _) -> Some Context.StringLiteral - | SynExpr.InterpolatedString(parts, _, _) -> - parts - |> List.indexed - |> List.tryPick (fun (i, part) -> - let inRangeOfPrevious = - if i = 0 then - false - else - // With no space between FillExpr and }..." of interpolated string, - // there will be a range clash. - match List.item (i - 1) parts with - | SynInterpolatedStringPart.String(_, m) -> Range.rangeContainsPos m pos - | SynInterpolatedStringPart.FillExpr(e, _) -> Range.rangeContainsPos e.Range pos - - match part with - | SynInterpolatedStringPart.String(_, m) when Range.rangeContainsPos m pos && not inRangeOfPrevious -> - Some Context.StringLiteral - | SynInterpolatedStringPart.String _ -> None - | SynInterpolatedStringPart.FillExpr(e, _) when - Range.rangeContainsPos e.Range pos && not inRangeOfPrevious - -> - defaultTraverse e // gotta dive into the expr to see if we're in a literal inside the expr - | SynInterpolatedStringPart.FillExpr _ -> None) - | _ -> defaultTraverse expr - else - None - - member x.VisitType(path, defaultTraverse, synType) : Context option = Some Context.SynType } - - SyntaxTraversal.Traverse(pos, ast, visitor) + (pos, ast) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynType _ -> Some Context.SynType + | _ -> None) + |> Option.orElseWith (fun () -> + ast + |> ParsedInput.tryNode pos + |> Option.bind (fun (node, _path) -> + match node with + | SyntaxNode.SynExpr(SynExpr.Const(SynConst.String _, _)) -> Some Context.StringLiteral + | _ -> None)) |> Option.defaultValue Context.Unknown diff --git a/src/FsAutoComplete/CodeFixes/AddExplicitTypeAnnotation.fs b/src/FsAutoComplete/CodeFixes/AddExplicitTypeAnnotation.fs index ff6cb423a..70e8ea2ef 100644 --- a/src/FsAutoComplete/CodeFixes/AddExplicitTypeAnnotation.fs +++ b/src/FsAutoComplete/CodeFixes/AddExplicitTypeAnnotation.fs @@ -57,21 +57,17 @@ let (|FunctionBindingWithMissingTypes|_|) = /// Expected to be between the start of the leading keyword and the end of the function name. let tryFunctionIdentifier (parseAndCheck: ParseAndCheckResults) textDocument sourceText lineStr cursorPos = let bindingInfo = - SyntaxTraversal.Traverse( - cursorPos, - parseAndCheck.GetAST, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(path, traverseSynExpr, defaultTraverse, expr) = defaultTraverse expr - - member _.VisitBinding(path, defaultTraverse, binding) = - match binding with - | FunctionBindingWithMissingTypes(bindingStartRange, - headPatRangeOpt, - totalParameterCount, - nonTypedParameters) when rangeContainsPos bindingStartRange cursorPos -> - Some(bindingStartRange, headPatRangeOpt, totalParameterCount, nonTypedParameters) - | _ -> defaultTraverse binding } - ) + (cursorPos, parseAndCheck.GetParseResults.ParseTree) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynBinding(FunctionBindingWithMissingTypes(bindingStartRange, + headPatRangeOpt, + totalParameterCount, + nonTypedParameters)) when + rangeContainsPos bindingStartRange cursorPos + -> + Some(bindingStartRange, headPatRangeOpt, totalParameterCount, nonTypedParameters) + | _ -> None) match bindingInfo with | None -> [] diff --git a/src/FsAutoComplete/CodeFixes/AddMissingXmlDocumentation.fs b/src/FsAutoComplete/CodeFixes/AddMissingXmlDocumentation.fs index 9412c68d8..6a6b75bf6 100644 --- a/src/FsAutoComplete/CodeFixes/AddMissingXmlDocumentation.fs +++ b/src/FsAutoComplete/CodeFixes/AddMissingXmlDocumentation.fs @@ -38,64 +38,19 @@ let private tryGetExistingXmlDoc (pos: FSharp.Compiler.Text.Position) (xmlDoc: P None let private tryGetCommentsAndSymbolPos input pos = - - let handleSynBinding defaultTraverse (synBinding: SynBinding) = - let SynBinding(xmlDoc = xmlDoc; headPat = headPat) as s = synBinding - let docAndDocRange = tryGetExistingXmlDoc pos xmlDoc - - match docAndDocRange with - | Some(docLines, docRange) -> - let symbolRange = - match headPat with - | SynPat.LongIdent(longDotId = longDotId) -> longDotId.Range.End - | _ -> s.RangeOfHeadPattern.Start // for use statements - - Some(docLines, docRange, symbolRange, false) - | None -> defaultTraverse synBinding - - SyntaxTraversal.Traverse( - pos, - input, - { new SyntaxVisitorBase<_>() with - - member _.VisitBinding(_, defaultTraverse, synBinding) = handleSynBinding defaultTraverse synBinding - - member _.VisitLetOrUse(_, _, defaultTraverse, bindings, _) = - bindings |> List.tryPick (handleSynBinding defaultTraverse) - - member _.VisitExpr(_, _, defaultTraverse, expr) = defaultTraverse expr - - member _.VisitModuleOrNamespace(_, synModuleOrNamespace) = - match synModuleOrNamespace with - | SynModuleOrNamespace(decls = decls) -> - - let rec findNested decls = - decls - |> List.tryPick (fun d -> - match d with - | SynModuleDecl.NestedModule(moduleInfo = moduleInfo; decls = decls) -> - match moduleInfo with - | _ -> findNested decls - | SynModuleDecl.Types(typeDefns = typeDefns) -> - typeDefns - |> List.tryPick (fun td -> - match td with - | SynTypeDefn(typeRepr = SynTypeDefnRepr.ObjectModel(_, members, _)) -> - members - |> List.tryPick (fun m -> - match m with - | SynMemberDefn.AutoProperty(xmlDoc = xmlDoc; ident = ident) -> - let docAndDocRange = tryGetExistingXmlDoc pos xmlDoc - - match docAndDocRange with - | Some(docLines, docRange) -> Some(docLines, docRange, ident.idRange.End, true) - | _ -> None - | _ -> None) - | _ -> None) - | _ -> None) - - findNested decls } - ) + (pos, input) + ||> ParsedInput.tryPick (fun _path node -> + let (|ExistingXmlDoc|_|) = tryGetExistingXmlDoc pos + + match node with + | SyntaxNode.SynBinding(SynBinding( + xmlDoc = ExistingXmlDoc(docLines, docRange); headPat = SynPat.LongIdent(longDotId = longDotId))) -> + Some(docLines, docRange, longDotId.Range.End, false) + | SyntaxNode.SynBinding(SynBinding(xmlDoc = ExistingXmlDoc(docLines, docRange); headPat = headPat)) -> + Some(docLines, docRange, headPat.Range.Start (* For `use` exprs. *) , false) + | SyntaxNode.SynMemberDefn(SynMemberDefn.AutoProperty(xmlDoc = ExistingXmlDoc(docLines, docRange); ident = ident)) -> + Some(docLines, docRange, ident.idRange.End, true) + | _ -> None) let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = fun codeActionParams -> diff --git a/src/FsAutoComplete/CodeFixes/AddPrivateAccessModifier.fs b/src/FsAutoComplete/CodeFixes/AddPrivateAccessModifier.fs index a9b6bd659..044b535dd 100644 --- a/src/FsAutoComplete/CodeFixes/AddPrivateAccessModifier.fs +++ b/src/FsAutoComplete/CodeFixes/AddPrivateAccessModifier.fs @@ -132,65 +132,62 @@ let private getRangesAndPlacement input pos = | _ -> None) | _ -> None) - let visitor = - { new SyntaxVisitorBase<_>() with - member _.VisitBinding(path, _, synBinding) = - match synBinding with - // explicit Ctor - | SynBinding(valData = SynValData(memberFlags = Some({ MemberKind = SynMemberKind.Constructor }))) -> None - // let bindings, members - | SynBinding(headPat = headPat; kind = SynBindingKind.Normal) as s when - rangeContainsPos s.RangeOfHeadPattern pos - -> - if isLetInsideObjectModel path pos then - None - else - match headPat with - | SynPat.LongIdent(longDotId = longDotId; accessibility = None) -> - let posValidInSynLongIdent = - longDotId.LongIdent - |> List.skip (if longDotId.LongIdent.Length > 1 then 1 else 0) - |> List.exists (fun i -> rangeContainsPos i.idRange pos) - - if not posValidInSynLongIdent then - None - else - let editRange = s.RangeOfHeadPattern.WithEnd s.RangeOfHeadPattern.Start - - match tryGetDeclContainingRange path pos with - | Some r -> Some(editRange, r, Before) - | _ -> None - | SynPat.Named(accessibility = None; isThisVal = false) -> - let editRange = s.RangeOfHeadPattern.WithEnd s.RangeOfHeadPattern.Start + (pos, input) + ||> ParsedInput.tryPick (fun path node -> + match node with + // explicit Ctor + | SyntaxNode.SynBinding(SynBinding( + valData = SynValData(memberFlags = Some({ MemberKind = SynMemberKind.Constructor })))) -> None + | SyntaxNode.SynBinding(SynBinding(headPat = headPat; kind = SynBindingKind.Normal) as s) when + rangeContainsPos s.RangeOfHeadPattern pos + -> + if isLetInsideObjectModel path pos then + None + else + match headPat with + | SynPat.LongIdent(longDotId = longDotId; accessibility = None) -> + let posValidInSynLongIdent = + longDotId.LongIdent + |> List.skip (if longDotId.LongIdent.Length > 1 then 1 else 0) + |> List.exists (fun i -> rangeContainsPos i.idRange pos) + + if not posValidInSynLongIdent then + None + else + let editRange = s.RangeOfHeadPattern.WithEnd s.RangeOfHeadPattern.Start - match tryGetDeclContainingRange path pos with - | Some r -> Some(editRange, r, Before) - | _ -> None - | _ -> None + match tryGetDeclContainingRange path pos with + | Some r -> Some(editRange, r, Before) + | _ -> None + | SynPat.Named(accessibility = None; isThisVal = false) -> + let editRange = s.RangeOfHeadPattern.WithEnd s.RangeOfHeadPattern.Start + + match tryGetDeclContainingRange path pos with + | Some r -> Some(editRange, r, Before) | _ -> None + | _ -> None - member _.VisitModuleOrNamespace(path, synModuleOrNamespace) = - match synModuleOrNamespace with - | SynModuleOrNamespace( - longId = longId - attribs = attribs - accessibility = None - trivia = { LeadingKeyword = SynModuleOrNamespaceLeadingKeyword.Module r }) as mOrN when - longIdentContainsPos longId pos - -> - let editRange = getEditRangeForModule attribs r pos.Line - - if path.Length = 0 then // Top level module - Some(editRange, mOrN.Range, After) - else - match tryGetDeclContainingRange path pos with - | Some r -> Some(editRange, r, After) - | _ -> None - | SynModuleOrNamespace(decls = decls) as mOrN -> - let path = SyntaxNode.SynModuleOrNamespace mOrN :: path - findNested path decls } - - SyntaxTraversal.Traverse(pos, input, visitor) + | SyntaxNode.SynModuleOrNamespace(SynModuleOrNamespace( + longId = longId + attribs = attribs + accessibility = None + trivia = { LeadingKeyword = SynModuleOrNamespaceLeadingKeyword.Module r }) as mOrN) when + longIdentContainsPos longId pos + -> + let editRange = getEditRangeForModule attribs r pos.Line + + if path.Length = 0 then // Top level module + Some(editRange, mOrN.Range, After) + else + match tryGetDeclContainingRange path pos with + | Some r -> Some(editRange, r, After) + | _ -> None + + | SyntaxNode.SynModuleOrNamespace(SynModuleOrNamespace(decls = decls) as mOrN) -> + let path = SyntaxNode.SynModuleOrNamespace mOrN :: path + findNested path decls + + | _ -> None) let fix (getParseResultsForFile: GetParseResultsForFile) (symbolUseWorkspace: SymbolUseWorkspace) : CodeFix = fun codeActionParams -> diff --git a/src/FsAutoComplete/CodeFixes/AdjustConstant.fs b/src/FsAutoComplete/CodeFixes/AdjustConstant.fs index 626ec2beb..43971b361 100644 --- a/src/FsAutoComplete/CodeFixes/AdjustConstant.fs +++ b/src/FsAutoComplete/CodeFixes/AdjustConstant.fs @@ -36,34 +36,27 @@ let private tryFindConstant ast pos = findConst constantRange c | _ -> (range, constant) - SyntaxTraversal.Traverse( - pos, - ast, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = + (pos, ast) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynExpr(SynExpr.Const(constant, range)) + | SyntaxNode.SynPat(SynPat.Const(constant, range)) when rangeContainsPos range pos -> + Some(findConst range constant) + + | SyntaxNode.SynTypeDefn(SynTypeDefn(typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Enum(cases, _), _))) -> + cases + |> List.tryPick (fun (SynEnumCase(valueExpr = expr)) -> + let rec tryFindConst (expr: SynExpr) = match expr with - // without: matches when `pos` in comment after constant - | SynExpr.Const(constant, range) when rangeContainsPos range pos -> findConst range constant |> Some - | _ -> defaultTraverse expr - - member _.VisitEnumDefn(_, cases, _) = - cases - |> List.tryPick (fun (SynEnumCase(valueExpr = expr)) -> - let rec tryFindConst (expr: SynExpr) = - match expr with - | SynExpr.Const(constant, range) when rangeContainsPos range pos -> findConst range constant |> Some - | SynExpr.Paren(expr = expr) -> tryFindConst expr - | SynExpr.App(funcExpr = funcExpr) when rangeContainsPos funcExpr.Range pos -> tryFindConst funcExpr - | SynExpr.App(argExpr = argExpr) when rangeContainsPos argExpr.Range pos -> tryFindConst argExpr - | _ -> None - - tryFindConst expr) - - member _.VisitPat(_, defaultTraverse, synPat) = - match synPat with - | SynPat.Const(constant, range) when rangeContainsPos range pos -> findConst range constant |> Some - | _ -> defaultTraverse synPat } - ) + | SynExpr.Const(constant, range) when rangeContainsPos range pos -> Some(findConst range constant) + | SynExpr.Paren(expr = expr) -> tryFindConst expr + | SynExpr.App(funcExpr = funcExpr) when rangeContainsPos funcExpr.Range pos -> tryFindConst funcExpr + | SynExpr.App(argExpr = argExpr) when rangeContainsPos argExpr.Range pos -> tryFindConst argExpr + | _ -> None + + tryFindConst expr) + + | _ -> None) /// Computes the absolute of `n` /// diff --git a/src/FsAutoComplete/CodeFixes/ChangeDerefBangToValue.fs b/src/FsAutoComplete/CodeFixes/ChangeDerefBangToValue.fs index efc654224..6f2a86eef 100644 --- a/src/FsAutoComplete/CodeFixes/ChangeDerefBangToValue.fs +++ b/src/FsAutoComplete/CodeFixes/ChangeDerefBangToValue.fs @@ -11,22 +11,16 @@ open FSharp.UMX /// adopted from `dotnet/fsharp` -> `FSharp.Compiler.CodeAnalysis.FSharpParseFileResults.TryRangeOfExpressionBeingDereferencedContainingPos` let private tryGetRangeOfDeref input derefPos = - SyntaxTraversal.Traverse( - derefPos, - input, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - | SynExpr.App(_, false, SynExpr.LongIdent(longDotId = SynLongIdent(id = [ funcIdent ])), expr, _) -> - if - funcIdent.idText = "op_Dereference" - && rangeContainsPos funcIdent.idRange derefPos - then - Some(funcIdent.idRange, expr.Range) - else - None - | _ -> defaultTraverse expr } - ) + (derefPos, input) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynExpr(SynExpr.App(_, false, SynExpr.LongIdent(longDotId = SynLongIdent(id = [ funcIdent ])), expr, _)) when + funcIdent.idText = "op_Dereference" + && rangeContainsPos funcIdent.idRange derefPos + -> + Some(funcIdent.idRange, expr.Range) + + | _ -> None) let title = "Use `.Value` instead of dereference operator" diff --git a/src/FsAutoComplete/CodeFixes/ChangeTypeOfNameToNameOf.fs b/src/FsAutoComplete/CodeFixes/ChangeTypeOfNameToNameOf.fs index c70b84b8c..9d74a8266 100644 --- a/src/FsAutoComplete/CodeFixes/ChangeTypeOfNameToNameOf.fs +++ b/src/FsAutoComplete/CodeFixes/ChangeTypeOfNameToNameOf.fs @@ -5,37 +5,8 @@ open FsToolkit.ErrorHandling open FsAutoComplete.CodeFix.Types open FsAutoComplete open FsAutoComplete.LspHelpers -open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Syntax -type FSharpParseFileResults with - - member this.TryRangeOfTypeofWithNameAndTypeExpr pos = - SyntaxTraversal.Traverse( - pos, - this.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_path, _, defaultTraverse, expr) = - match expr with - | SynExpr.DotGet(expr, _, _, range) -> - match expr with - | SynExpr.TypeApp(SynExpr.Ident(ident), _, typeArgs, _, _, _, _) -> - let onlyOneTypeArg = - match typeArgs with - | [] -> false - | [ _ ] -> true - | _ -> false - - if ident.idText = "typeof" && onlyOneTypeArg then - Some - {| NamedIdentRange = typeArgs.Head.Range - FullExpressionRange = range |} - else - defaultTraverse expr - | _ -> defaultTraverse expr - | _ -> defaultTraverse expr } - ) - let title = "Use 'nameof'" let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = @@ -48,7 +19,18 @@ let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = let! tyRes, _line, sourceText = getParseResultsForFile fileName pos let! results = - tyRes.GetParseResults.TryRangeOfTypeofWithNameAndTypeExpr(pos) + (pos, tyRes.GetParseResults.ParseTree) + ||> ParsedInput.tryPick (fun _path node -> + let (|Ident|) (ident: Ident) = ident.idText + + match node with + | SyntaxNode.SynExpr(SynExpr.DotGet( + expr = SynExpr.TypeApp(expr = SynExpr.Ident(Ident "typeof"); typeArgs = [ typeArg ]); range = range)) -> + Some + {| NamedIdentRange = typeArg.Range + FullExpressionRange = range |} + + | _ -> None) |> Result.ofOption (fun _ -> "no typeof expr found") let! typeName = sourceText.GetText results.NamedIdentRange diff --git a/src/FsAutoComplete/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs b/src/FsAutoComplete/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs index cfa3221e0..dd3f89e2d 100644 --- a/src/FsAutoComplete/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs +++ b/src/FsAutoComplete/CodeFixes/ConvertCSharpLambdaToFSharpLambda.fs @@ -32,21 +32,11 @@ let private tryRangeOfParenEnclosingOpEqualsGreaterUsage input pos = Some(argsRange, opRange) | _ -> None - SyntaxTraversal.Traverse( - pos, - input, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - | SynExpr.Paren(InfixAppOfOpEqualsGreater(argsRange, opRange), _, _, _) -> Some(argsRange, opRange) - | _ -> defaultTraverse expr - - member _.VisitBinding(_path, defaultTraverse, binding) = - match binding with - | SynBinding(kind = SynBindingKind.Normal; expr = InfixAppOfOpEqualsGreater(argsRange, opRange)) -> - Some(argsRange, opRange) - | _ -> defaultTraverse binding } - ) + (pos, input) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynExpr(InfixAppOfOpEqualsGreater(argsRange, opRange)) -> Some(argsRange, opRange) + | _ -> None) let fix (getParseResultsForFile: GetParseResultsForFile) (_: GetLineText) : CodeFix = Run.ifDiagnosticByCode diff --git a/src/FsAutoComplete/CodeFixes/ConvertPositionalDUToNamed.fs b/src/FsAutoComplete/CodeFixes/ConvertPositionalDUToNamed.fs index 2341b0b07..f05c08598 100644 --- a/src/FsAutoComplete/CodeFixes/ConvertPositionalDUToNamed.fs +++ b/src/FsAutoComplete/CodeFixes/ConvertPositionalDUToNamed.fs @@ -22,70 +22,9 @@ open FsAutoComplete.CodeFix.Types open Ionide.LanguageServerProtocol.Types open FsAutoComplete open FsAutoComplete.LspHelpers -open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols open FSharp.Compiler.Text.Range -open FsAutoComplete.FCSPatches open FSharp.Compiler.Syntax -open FSharp.Compiler.Syntax.SyntaxTraversal - -type ParseAndCheckResults with - - member x.TryGetPositionalUnionPattern(pos: FcsPos) = - let rec (|UnionNameAndPatterns|_|) = - function - | SynPat.LongIdent( - longDotId = ident - argPats = SynArgPats.Pats [ SynPat.Paren(pat = SynPat.Tuple(elementPats = duFieldPatterns); range = parenRange) ]) when - rangeContainsPos parenRange pos - -> - Some(ident, duFieldPatterns, parenRange) - | SynPat.LongIdent( - longDotId = ident; argPats = SynArgPats.Pats [ SynPat.Paren(pat = singleDUFieldPattern; range = parenRange) ]) when - rangeContainsPos parenRange pos - -> - Some(ident, [ singleDUFieldPattern ], parenRange) - | SynPat.Paren(pat = UnionNameAndPatterns(ident, duFieldPatterns, parenRange)) -> - Some(ident, duFieldPatterns, parenRange) - | _ -> None - - let visitor = - { new SyntaxVisitorBase<_>() with - member x.VisitBinding(path, defaultTraverse, binding) = - match binding with - // DU case with multiple - | SynBinding(headPat = UnionNameAndPatterns(ident, duFieldPatterns, parenRange)) -> - Some(ident, duFieldPatterns, parenRange) - | _ -> defaultTraverse binding - - // I shouldn't have to override my own VisitExpr, but the default traversal doesn't seem to be triggering the `VisitMatchClause` method I've defined below. - // TODO: reevaluate after https://github.com/dotnet/fsharp/pull/12837 merges - member x.VisitExpr(path, traverse, defaultTraverse, expr) = - match expr with - | SynExpr.Match(expr = argExpr; clauses = clauses) -> - let path = SyntaxNode.SynExpr argExpr :: path - - match x.VisitExpr(path, traverse, defaultTraverse, argExpr) with - | Some x -> Some x - | None -> - clauses - |> List.tryPick (function - | SynMatchClause(pat = UnionNameAndPatterns(ident, duFieldPatterns, parenRange)) when - rangeContainsPos parenRange pos - -> - Some(ident, duFieldPatterns, parenRange) - | _ -> None) - | _ -> defaultTraverse expr - - member x.VisitMatchClause(path, defaultTraverse, matchClause) = - match matchClause with - | SynMatchClause(pat = UnionNameAndPatterns(ident, duFieldPatterns, parenRange)) when - rangeContainsPos parenRange pos - -> - Some(ident, duFieldPatterns, parenRange) - | _ -> defaultTraverse matchClause } - - Traverse(pos, x.GetParseResults.ParseTree, visitor) let private (|MatchedFields|UnmatchedFields|NotEnoughFields|) (astFields: SynPat list, unionFields: string list) = let userFieldsCount = astFields.Length @@ -133,7 +72,26 @@ let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = let! parseAndCheck, lineStr, sourceText = getParseResultsForFile filePath fcsPos let! duIdent, duFields, parenRange = - parseAndCheck.TryGetPositionalUnionPattern(fcsPos) + (fcsPos, parseAndCheck.GetAST) + ||> ParsedInput.tryPick (fun path node -> + let rec (|IgnoreParens|) = + function + | SynPat.Paren(pat = IgnoreParens pat) + | pat -> pat + + let (|UnionFields|_|) = + function + | SynPat.Paren(pat = SynPat.Tuple(isStruct = false; elementPats = pats)) -> Some(UnionFields pats) + | SynPat.Paren(pat = pat) -> Some(UnionFields [ pat ]) + | _ -> None + + match node, path with + | SyntaxNode.SynPat(IgnoreParens(SynPat.LongIdent( + longDotId = ident; argPats = SynArgPats.Pats [ SynPat.Paren(range = parenRange) & UnionFields fields ]))), + (SyntaxNode.SynBinding _ :: _ | SyntaxNode.SynMatchClause _ :: _) when rangeContainsPos parenRange fcsPos -> + Some(ident, fields, parenRange) + + | _ -> None) |> Result.ofOption (fun _ -> "Not inside a DU pattern") let! symbolUse = diff --git a/src/FsAutoComplete/CodeFixes/ConvertTripleSlashCommentToXmlTaggedDoc.fs b/src/FsAutoComplete/CodeFixes/ConvertTripleSlashCommentToXmlTaggedDoc.fs index 2bb0f4dee..80edd6ee5 100644 --- a/src/FsAutoComplete/CodeFixes/ConvertTripleSlashCommentToXmlTaggedDoc.fs +++ b/src/FsAutoComplete/CodeFixes/ConvertTripleSlashCommentToXmlTaggedDoc.fs @@ -26,99 +26,34 @@ let private containsPosAndNotEmptyAndNotElaborated (pos: FSharp.Compiler.Text.Po not xmlDoc.IsEmpty && containsPosAndNoSummaryPresent xmlDoc -let private isLowerAstElemWithPreXmlDoc input pos = - SyntaxTraversal.Traverse( - pos, - input, - { new SyntaxVisitorBase<_>() with - member _.VisitBinding(_, defaultTraverse, synBinding) = - match synBinding with - | SynBinding(xmlDoc = xmlDoc) when containsPosAndNotEmptyAndNotElaborated pos xmlDoc -> Some xmlDoc - | _ -> defaultTraverse synBinding - - member _.VisitComponentInfo(_, synComponentInfo) = - match synComponentInfo with - | SynComponentInfo(xmlDoc = xmlDoc) when containsPosAndNotEmptyAndNotElaborated pos xmlDoc -> Some xmlDoc - | _ -> None - - member _.VisitRecordDefn(_, fields, _) = - let isInLine c = - match c with - | SynField(xmlDoc = xmlDoc) when containsPosAndNotEmptyAndNotElaborated pos xmlDoc -> Some xmlDoc - | _ -> None - - fields |> List.tryPick isInLine - - member _.VisitUnionDefn(_, cases, _) = - let isInLine c = - match c with - | SynUnionCase(xmlDoc = xmlDoc) when containsPosAndNotEmptyAndNotElaborated pos xmlDoc -> Some xmlDoc - | _ -> None - - cases |> List.tryPick isInLine - - member _.VisitEnumDefn(_, cases, _) = - let isInLine b = - match b with - | SynEnumCase(xmlDoc = xmlDoc) when containsPosAndNotEmptyAndNotElaborated pos xmlDoc -> Some xmlDoc - | _ -> None - - cases |> List.tryPick isInLine - - member _.VisitLetOrUse(_, _, defaultTraverse, bindings, _) = - let isInLine b = - match b with - | SynBinding(xmlDoc = xmlDoc) when containsPosAndNotEmptyAndNotElaborated pos xmlDoc -> Some xmlDoc - | _ -> defaultTraverse b - - bindings |> List.tryPick isInLine - - member _.VisitExpr(_, _, defaultTraverse, expr) = defaultTraverse expr } // needed for nested let bindings - ) - -let private isModuleOrNamespaceOrAutoPropertyWithPreXmlDoc input pos = - SyntaxTraversal.Traverse( - pos, - input, - { new SyntaxVisitorBase<_>() with - - member _.VisitModuleOrNamespace(_, synModuleOrNamespace) = - match synModuleOrNamespace with - | SynModuleOrNamespace(xmlDoc = xmlDoc) when containsPosAndNotEmptyAndNotElaborated pos xmlDoc -> Some xmlDoc - | SynModuleOrNamespace(decls = decls) -> - - let rec findNested decls = - decls - |> List.tryPick (fun d -> - match d with - | SynModuleDecl.NestedModule(moduleInfo = moduleInfo; decls = decls) -> - match moduleInfo with - | SynComponentInfo(xmlDoc = xmlDoc) when containsPosAndNotEmptyAndNotElaborated pos xmlDoc -> - Some xmlDoc - | _ -> findNested decls - | SynModuleDecl.Types(typeDefns = typeDefns) -> - typeDefns - |> List.tryPick (fun td -> - match td with - | SynTypeDefn(typeRepr = SynTypeDefnRepr.ObjectModel(_, members, _)) -> - members - |> List.tryPick (fun m -> - match m with - | SynMemberDefn.AutoProperty(xmlDoc = xmlDoc) when - containsPosAndNotEmptyAndNotElaborated pos xmlDoc - -> - Some xmlDoc - | _ -> None) - | _ -> None) - | _ -> None) - - findNested decls } - ) - let private isAstElemWithPreXmlDoc input pos = - match isLowerAstElemWithPreXmlDoc input pos with - | Some xml -> Some xml - | _ -> isModuleOrNamespaceOrAutoPropertyWithPreXmlDoc input pos + let (|Unelaborated|_|) xmlDoc = + if containsPosAndNotEmptyAndNotElaborated pos xmlDoc then + Some xmlDoc + else + None + + let (|AnyUnelaborated|_|) getXmlDoc = List.tryPick (getXmlDoc >> (|Unelaborated|_|)) + let field (SynField(xmlDoc = xmlDoc)) = xmlDoc + let unionCase (SynUnionCase(xmlDoc = xmlDoc)) = xmlDoc + let enumCase (SynEnumCase(xmlDoc = xmlDoc)) = xmlDoc + + (pos, input) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynModuleOrNamespace(SynModuleOrNamespace(xmlDoc = Unelaborated xmlDoc)) + | SyntaxNode.SynModule(SynModuleDecl.NestedModule(moduleInfo = SynComponentInfo(xmlDoc = Unelaborated xmlDoc))) + | SyntaxNode.SynMemberDefn(SynMemberDefn.AutoProperty(xmlDoc = Unelaborated xmlDoc)) + | SyntaxNode.SynBinding(SynBinding(xmlDoc = Unelaborated xmlDoc)) + | SyntaxNode.SynTypeDefn(SynTypeDefn(typeInfo = SynComponentInfo(xmlDoc = Unelaborated xmlDoc))) + | SyntaxNode.SynTypeDefn(SynTypeDefn( + typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Record(recordFields = AnyUnelaborated field xmlDoc), _))) + | SyntaxNode.SynTypeDefn(SynTypeDefn( + typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Union(unionCases = AnyUnelaborated unionCase xmlDoc), _))) + | SyntaxNode.SynTypeDefn(SynTypeDefn( + typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Enum(cases = AnyUnelaborated enumCase xmlDoc), _))) -> + Some xmlDoc + | _ -> None) let private collectCommentContents (startPos: FSharp.Compiler.Text.Position) diff --git a/src/FsAutoComplete/CodeFixes/ImplementInterface.fs b/src/FsAutoComplete/CodeFixes/ImplementInterface.fs index 37832168e..94f3a55f6 100644 --- a/src/FsAutoComplete/CodeFixes/ImplementInterface.fs +++ b/src/FsAutoComplete/CodeFixes/ImplementInterface.fs @@ -14,23 +14,18 @@ open FSharp.Compiler.Text /// `pos` is expected to be on the leading `{` (main interface) or `interface` (additional interfaces) /// -> `diagnostic.Range.Start` let private tryFindInterfaceDeclarationInObjectExpression (pos: Position) (ast: ParsedInput) = - SyntaxTraversal.Traverse( - pos, - ast, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - | SynExpr.ObjExpr(objType = ty; bindings = binds; extraImpls = ifaces) -> - ifaces - |> List.tryPick (fun (SynInterfaceImpl(interfaceTy = ty; bindings = binds; range = range)) -> - if Range.rangeContainsPos range pos then - Some(InterfaceData.ObjExpr(ty, binds)) - else - None) - |> Option.orElseWith (fun _ -> Some(InterfaceData.ObjExpr(ty, binds))) - - | _ -> defaultTraverse expr } - ) + (pos, ast) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynExpr(SynExpr.ObjExpr(objType = ty; bindings = binds; extraImpls = ifaces)) -> + ifaces + |> List.tryPick (fun (SynInterfaceImpl(interfaceTy = ty; bindings = binds; range = range)) -> + if Range.rangeContainsPos range pos then + Some(InterfaceData.ObjExpr(ty, binds)) + else + None) + |> Option.orElseWith (fun () -> Some(InterfaceData.ObjExpr(ty, binds))) + | _ -> None) /// `pos`: on corresponding interface identifier /// @@ -43,71 +38,41 @@ let private tryFindInterfaceDeclarationInObjectExpression (pos: Position) (ast: /// * range of `with` keyword if exists /// -> pos for append let private tryFindInterfaceStartAndWith (pos: Position) (ast: ParsedInput) = - SyntaxTraversal.Traverse( - pos, - ast, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - // main interface - | SynExpr.ObjExpr(objType = ty; withKeyword = withRange; newExprRange = startingAtNewRange) when - Range.rangeContainsPos ty.Range pos - -> - // { new IDisposable with } - // ^ - let start = startingAtNewRange.Start + (pos, ast) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynExpr(SynExpr.ObjExpr(objType = ty; withKeyword = withRange; newExprRange = startingAtNewRange)) when + Range.rangeContainsPos ty.Range pos + -> + // { new IDisposable with } + // ^ + let start = startingAtNewRange.Start + Some(start, withRange) + + | SyntaxNode.SynExpr(SynExpr.ObjExpr(extraImpls = ifaces)) -> + ifaces + |> List.tryPick + (fun (SynInterfaceImpl(interfaceTy = ty; withKeyword = withRange; range = startingAtInterfaceRange)) -> + if Range.rangeContainsPos ty.Range pos then + // { new IDisposable with + // member this.Dispose() = () + // interface ICloneable with + // ^ + // } + let start = startingAtInterfaceRange.Start Some(start, withRange) - // secondary interface - | SynExpr.ObjExpr(extraImpls = ifaces) -> - ifaces - |> List.tryPick - (fun (SynInterfaceImpl(interfaceTy = ty; withKeyword = withRange; range = startingAtInterfaceRange)) -> - if Range.rangeContainsPos ty.Range pos then - // { new IDisposable with - // member this.Dispose() = () - // interface ICloneable with - // ^ - // } - let start = startingAtInterfaceRange.Start - Some(start, withRange) - else - None) - |> Option.orElseWith (fun _ -> defaultTraverse expr) - | _ -> defaultTraverse expr - - member _.VisitModuleDecl(_, defaultTraverse, synModuleDecl) = - match synModuleDecl with - | SynModuleDecl.Types(typeDefns, _) -> - let typeDefn = - typeDefns - |> List.tryFind (fun typeDef -> Range.rangeContainsPos typeDef.Range pos) - - match typeDefn with - | Some(SynTypeDefn(typeRepr = typeRepr; members = members)) -> - let tryFindInMemberDefns (members: SynMemberDefns) = - members - |> List.tryPick (function - | SynMemberDefn.Interface(interfaceType = ty; withKeyword = withRange; range = range) when - Range.rangeContainsPos ty.Range pos - -> - // interface IDisposable with - // ^ - let start = range.Start - Some(start, withRange) - | _ -> None) - - match typeRepr with - | SynTypeDefnRepr.ObjectModel(members = members) -> - // in class (-> in typeRepr) - tryFindInMemberDefns members - | _ -> None - |> Option.orElseWith (fun _ -> - // in union, records (-> in members) - tryFindInMemberDefns members) - |> Option.orElseWith (fun _ -> defaultTraverse synModuleDecl) - | _ -> defaultTraverse synModuleDecl - | _ -> defaultTraverse synModuleDecl } - ) + else + None) + + | SyntaxNode.SynMemberDefn(SynMemberDefn.Interface(interfaceType = ty; withKeyword = withRange; range = range)) when + Range.rangeContainsPos ty.Range pos + -> + // interface IDisposable with + // ^ + let start = range.Start + Some(start, withRange) + + | _ -> None) type private InsertionData = { diff --git a/src/FsAutoComplete/CodeFixes/RemovePatternArgument.fs b/src/FsAutoComplete/CodeFixes/RemovePatternArgument.fs index 0b95e9029..0eab3634a 100644 --- a/src/FsAutoComplete/CodeFixes/RemovePatternArgument.fs +++ b/src/FsAutoComplete/CodeFixes/RemovePatternArgument.fs @@ -7,30 +7,9 @@ open FsAutoComplete open FsAutoComplete.LspHelpers open FSharp.Compiler.Syntax open FSharp.Compiler.Text.Range -open FSharp.Compiler.Text let title = "Remove argument" -let tryFindPattern pos input = - let visitor = - { new SyntaxVisitorBase() with - - member _.VisitExpr(path, traverseSynExpr, defaultTraverse, expr) = defaultTraverse expr - - member this.VisitPat(path: SyntaxVisitorPath, defaultTraverse: SynPat -> range option, synPat: SynPat) = - match synPat with - | SynPat.LongIdent(longDotId = synLongIdent; argPats = SynArgPats.Pats(pats); range = m) when - rangeContainsPos synPat.Range pos && not pats.IsEmpty - -> - let mRemove = mkRange m.FileName synLongIdent.Range.End m.End - Some mRemove - | SynPat.Paren(pat = innerPat) -> - let nextPath = SyntaxNode.SynPat(synPat) :: path - this.VisitPat(nextPath, defaultTraverse, innerPat) - | _ -> None } - - SyntaxTraversal.Traverse(pos, input, visitor) - let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = Run.ifDiagnosticByCode (Set.ofList [ "725"; "3191" ]) (fun _ codeActionParams -> asyncResult { @@ -38,17 +17,22 @@ let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = let fcsPos = protocolPosToPos codeActionParams.Range.Start let! parseAndCheck, _, _ = getParseResultsForFile filePath fcsPos - match tryFindPattern fcsPos parseAndCheck.GetAST with - | None -> return [] - | Some removeRange -> - let e = - { Range = fcsRangeToLsp removeRange - NewText = "" } - - return - [ { Edits = [| e |] - File = codeActionParams.TextDocument - Title = title - SourceDiagnostic = None - Kind = FixKind.Refactor } ] + return + (fcsPos, parseAndCheck.GetAST) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynPat(SynPat.LongIdent(longDotId = synLongIdent; argPats = SynArgPats.Pats(_ :: _); range = m)) when + rangeContainsPos m fcsPos + -> + Some(mkRange m.FileName synLongIdent.Range.End m.End) + | _ -> None) + |> Option.toList + |> List.map (fun range -> + { Edits = + [| { Range = fcsRangeToLsp range + NewText = "" } |] + File = codeActionParams.TextDocument + Title = title + SourceDiagnostic = None + Kind = FixKind.Refactor }) }) diff --git a/src/FsAutoComplete/CodeFixes/RemoveRedundantAttributeSuffix.fs b/src/FsAutoComplete/CodeFixes/RemoveRedundantAttributeSuffix.fs index 16f371e5e..14a1c7ad7 100644 --- a/src/FsAutoComplete/CodeFixes/RemoveRedundantAttributeSuffix.fs +++ b/src/FsAutoComplete/CodeFixes/RemoveRedundantAttributeSuffix.fs @@ -17,31 +17,50 @@ let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = let fcsPos = protocolPosToPos codeActionParams.Range.Start let! parseAndCheck, _, _ = getParseResultsForFile filePath fcsPos - let isAttributeWithRedundantSuffix = - SyntaxTraversal.Traverse( - fcsPos, - parseAndCheck.GetParseResults.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitAttributeApplication(path, attributes) = - let attributesWithRedundantSuffix = - attributes.Attributes - |> List.choose (fun a -> - match List.tryLast a.TypeName.LongIdent with - | Some ident when ident.idText.EndsWith("Attribute", StringComparison.Ordinal) -> Some ident - | _ -> None) - - if List.isEmpty attributesWithRedundantSuffix then - None - else - Some attributesWithRedundantSuffix } - ) + return + (fcsPos, parseAndCheck.GetParseResults.ParseTree) + ||> ParsedInput.tryPick (fun _path node -> + let (|RecordFieldAttributes|) = + List.collect (fun (SynField(attributes = attributes)) -> attributes) + + let (|UnionFieldAttributes|) = + List.collect (fun (SynUnionCase(attributes = attributes)) -> attributes) + + let (|EnumFieldAttributes|) = + List.collect (fun (SynEnumCase(attributes = attributes)) -> attributes) - match isAttributeWithRedundantSuffix with - | None -> return [] - | Some redundantSuffixIdents -> - return - redundantSuffixIdents - |> List.map (fun ident -> + let (|Suffixed|): SynAttributes -> Ident list = + List.collect (fun { Attributes = attributes } -> + attributes + |> List.choose (fun attr -> + attr.TypeName.LongIdent + |> List.tryLast + |> Option.filter (fun ident -> ident.idText.EndsWith("Attribute", StringComparison.Ordinal)))) + + match node with + | SyntaxNode.SynModule(SynModuleDecl.Attributes(attributes = Suffixed(_ :: _ as attributes))) + | SyntaxNode.SynTypeDefn(SynTypeDefn(typeInfo = SynComponentInfo(attributes = Suffixed(_ :: _ as attributes)))) + | SyntaxNode.SynTypeDefn(SynTypeDefn( + typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Record( + recordFields = RecordFieldAttributes(Suffixed(_ :: _ as attributes))), + _))) + | SyntaxNode.SynTypeDefn(SynTypeDefn( + typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Union( + unionCases = UnionFieldAttributes(Suffixed(_ :: _ as attributes))), + _))) + | SyntaxNode.SynTypeDefn(SynTypeDefn( + typeRepr = SynTypeDefnRepr.Simple(SynTypeDefnSimpleRepr.Enum( + cases = EnumFieldAttributes(Suffixed(_ :: _ as attributes))), + _))) + | SyntaxNode.SynMemberDefn(SynMemberDefn.AutoProperty(attributes = Suffixed(_ :: _ as attributes))) + | SyntaxNode.SynMemberDefn(SynMemberDefn.AbstractSlot( + slotSig = SynValSig(attributes = Suffixed(_ :: _ as attributes)))) + | SyntaxNode.SynBinding(SynBinding(attributes = Suffixed(_ :: _ as attributes))) + | SyntaxNode.SynPat(SynPat.Attrib(attributes = Suffixed(_ :: _ as attributes))) -> Some attributes + | _ -> None) + |> Option.toList + |> List.collect ( + List.map (fun ident -> let updateText = ident.idText.Replace("Attribute", "") { Edits = @@ -51,4 +70,5 @@ let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = Title = title SourceDiagnostic = None Kind = FixKind.Refactor }) + ) } diff --git a/src/FsAutoComplete/CodeFixes/RemoveUnusedBinding.fs b/src/FsAutoComplete/CodeFixes/RemoveUnusedBinding.fs index db6bd4d4a..cbf73146b 100644 --- a/src/FsAutoComplete/CodeFixes/RemoveUnusedBinding.fs +++ b/src/FsAutoComplete/CodeFixes/RemoveUnusedBinding.fs @@ -1,75 +1,18 @@ module FsAutoComplete.CodeFix.RemoveUnusedBinding -open System open FsToolkit.ErrorHandling open FsAutoComplete.CodeFix.Navigation open FsAutoComplete.CodeFix.Types open Ionide.LanguageServerProtocol.Types open FsAutoComplete open FsAutoComplete.LspHelpers -open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Syntax open FSharp.Compiler.Text - type private ReplacementRangeResult = | FullBinding of bindingRange: Range | Pattern of patternRange: Range -type FSharpParseFileResults with - - member private this.TryRangeOfBindingWithHeadPatternWithPos(diagnosticRange: range) = - SyntaxTraversal.Traverse( - diagnosticRange.Start, - this.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = defaultTraverse expr - - override _.VisitPat(_, defaultTraverse, pat: SynPat) = - // if the diagnostic was for this specific pattern in its entirety, then we're don - if Range.equals pat.Range diagnosticRange then - Some(Pattern diagnosticRange) - else - match pat with - | SynPat.Paren(inner, m) -> - // otherwise if the pattern inside a parens - if Range.rangeContainsRange m diagnosticRange then - // explicitly matches - if - Range.equals inner.Range diagnosticRange - // then return the range of the parens, so the entire pattern gets removed - then - Some(Pattern m) - else - defaultTraverse inner - else - defaultTraverse inner - | pat -> defaultTraverse pat - - override _.VisitBinding(_, defaultTraverse, binding) = - match binding with - | SynBinding(kind = SynBindingKind.Normal; headPat = pat) as binding -> - // walk the patterns in the binding first, to allow the parameter traversal a chance to fire - match defaultTraverse binding with - | None -> - // otherwise if the diagnostic was in this binding's head pattern then do teh replacement - if Range.rangeContainsRange binding.RangeOfHeadPattern diagnosticRange then - Some(FullBinding binding.RangeOfBindingWithRhs) - else - // Check if it's an operator - match pat with - | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) when - id.idText.StartsWith("op_", StringComparison.Ordinal) - -> - if Range.rangeContainsRange id.idRange diagnosticRange then - Some(FullBinding binding.RangeOfBindingWithRhs) - else - defaultTraverse binding - | _ -> defaultTraverse binding - | Some range -> Some range - | _ -> defaultTraverse binding } - ) - let titleParameter = "Remove unused parameter" let titleBinding = "Remove unused binding" @@ -84,7 +27,28 @@ let fix (getParseResults: GetParseResultsForFile) : CodeFix = let! tyres, _line, lines = getParseResults fileName fcsRange.Start let! rangeOfBinding = - tyres.GetParseResults.TryRangeOfBindingWithHeadPatternWithPos(fcsRange) + (fcsRange.Start, tyres.GetParseResults.ParseTree) + ||> ParsedInput.tryPick (fun path node -> + let (|LongIdentRange|) (idents: Ident list) = + (Range.range0, idents) + ||> List.fold (fun acc ident -> Range.unionRanges acc ident.idRange) + + match node, path with + | SyntaxNode.SynPat pat, + SyntaxNode.SynBinding(SynBinding( + kind = SynBindingKind.Normal; headPat = SynPat.Named(range = nameRange) as headPat) as binding) :: _ + | SyntaxNode.SynPat pat, + SyntaxNode.SynBinding(SynBinding( + kind = SynBindingKind.Normal + headPat = SynPat.LongIdent(longDotId = SynLongIdent(id = LongIdentRange nameRange)) as headPat) as binding) :: _ -> + if obj.ReferenceEquals(pat, headPat) && Range.rangeContainsRange nameRange fcsRange then + Some(FullBinding binding.RangeOfBindingWithRhs) + else + None + + | SyntaxNode.SynPat pat, _ when Range.rangeContainsRange pat.Range fcsRange -> Some(Pattern pat.Range) + + | _ -> None) |> Result.ofOption (fun () -> "no binding range found") match rangeOfBinding with diff --git a/src/FsAutoComplete/CodeFixes/RenameUnusedValue.fs b/src/FsAutoComplete/CodeFixes/RenameUnusedValue.fs index 2d4539f7b..9cf2714d0 100644 --- a/src/FsAutoComplete/CodeFixes/RenameUnusedValue.fs +++ b/src/FsAutoComplete/CodeFixes/RenameUnusedValue.fs @@ -19,18 +19,14 @@ let titlePrefix = "Prefix with _" /// -> no (easy) way to get range with accessibility /// -> instead of range to replace, just if there's accessibility let private accessibilityRange (ast: ParsedInput) (pos: Position) = - SyntaxTraversal.Traverse( - pos, - ast, - { new SyntaxVisitorBase<_>() with - member _.VisitPat(_, defaultTraverse, pat) = - match pat with - | SynPat.Named(accessibility = Some(SynAccess.Private(range = accessRange)); range = range) when - Range.rangeContainsPos range pos - -> - Some(accessRange.WithEnd(accessRange.End.WithColumn(accessRange.End.Column + 1))) // add an additional column to remove the 'space' between private and identifier - | _ -> defaultTraverse pat } - ) + (pos, ast) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynPat(SynPat.Named(accessibility = Some(SynAccess.Private(range = accessRange)); range = range)) when + Range.rangeContainsPos range pos + -> + Some(accessRange.WithEnd(accessRange.End.WithColumn(accessRange.End.Column + 1))) // add an additional column to remove the 'space' between private and identifier + | _ -> None) /// a codefix that suggests prepending a _ to unused values let fix (getParseResultsForFile: GetParseResultsForFile) = diff --git a/src/FsAutoComplete/CodeFixes/ToInterpolatedString.fs b/src/FsAutoComplete/CodeFixes/ToInterpolatedString.fs index b834e6a49..6d756c851 100644 --- a/src/FsAutoComplete/CodeFixes/ToInterpolatedString.fs +++ b/src/FsAutoComplete/CodeFixes/ToInterpolatedString.fs @@ -21,7 +21,7 @@ let languageFeature = lazy (LanguageFeatureShim("StringInterpolation")) let specifierRegex = Regex(@"\%(\+|\-)?\.?\d*(b|s|c|d|i|u|x|X|o|B|e|E|f|F|g|G|M|O|A)") -let validFunctionNames = set [| "printf"; "printfn"; "sprintf" |] +let validFunctionNames = set [| nameof printf; nameof printfn; nameof sprintf |] let inline synExprNeedsSpaces synExpr = match synExpr with @@ -32,56 +32,50 @@ let inline synExprNeedsSpaces synExpr = let tryFindSprintfApplication (parseAndCheck: ParseAndCheckResults) (sourceText: IFSACSourceText) lineStr fcsPos = let application = - SyntaxTraversal.Traverse( - fcsPos, - parseAndCheck.GetParseResults.ParseTree, - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(path, traverseSynExpr, defaultTraverse, synExpr) = - match synExpr with - | SynExpr.App(ExprAtomicFlag.NonAtomic, - false, - SynExpr.Ident(functionIdent), - SynExpr.Const(SynConst.String(synStringKind = SynStringKind.Regular), mString), - mApp) -> - // Don't trust the value of SynConst.String, it is already a somewhat optimized version of what the user actually code. - match sourceText.GetText mString with - | Error _ -> None - | Ok formatString -> - if - validFunctionNames.Contains functionIdent.idText - && rangeContainsPos mApp fcsPos - && mApp.StartLine = mApp.EndLine // only support single line for now - then - // Find all the format parameters in the source string - // Things like `%i` or `%s` - let arguments = - specifierRegex.Matches(formatString) |> Seq.cast |> Seq.toList - - if arguments.IsEmpty || path.Length < arguments.Length then - None - else - let xs = - let argumentsInPath = List.take arguments.Length path - - (arguments, argumentsInPath) - ||> List.zip - |> List.choose (fun (regexMatch, node) -> - match node with - | SyntaxNode.SynExpr(SynExpr.App(argExpr = ae)) -> - Some(regexMatch, ae.Range, synExprNeedsSpaces ae) - | _ -> None) - - List.tryLast xs - |> Option.bind (fun (_, mLastArg, _) -> - // Ensure the last argument of the current application is also on the same line. - if mApp.StartLine <> mLastArg.EndLine then - None - else - Some(functionIdent, mString, xs, mLastArg)) - else + (fcsPos, parseAndCheck.GetAST) + ||> ParsedInput.tryPick (fun path node -> + match node with + | SyntaxNode.SynExpr(SynExpr.App(ExprAtomicFlag.NonAtomic, + false, + SynExpr.Ident(functionIdent), + SynExpr.Const(SynConst.String(synStringKind = SynStringKind.Regular), mString), + mApp)) -> + match sourceText.GetText mString with + | Error _ -> None + | Ok formatString -> + if + validFunctionNames.Contains functionIdent.idText + && rangeContainsPos mApp fcsPos + && mApp.StartLine = mApp.EndLine // only support single line for now + then + // Find all the format parameters in the source string + // Things like `%i` or `%s` + let arguments = + specifierRegex.Matches(formatString) |> Seq.cast |> Seq.toList + + if arguments.IsEmpty || path.Length < arguments.Length then + None + else + let xs = + let argumentsInPath = List.take arguments.Length path + + (arguments, argumentsInPath) + ||> List.zip + |> List.choose (fun (regexMatch, node) -> + match node with + | SyntaxNode.SynExpr(SynExpr.App(argExpr = ae)) -> Some(regexMatch, ae.Range, synExprNeedsSpaces ae) + | _ -> None) + + List.tryLast xs + |> Option.bind (fun (_, mLastArg, _) -> + // Ensure the last argument of the current application is also on the same line. + if mApp.StartLine <> mLastArg.EndLine then None - | _ -> defaultTraverse synExpr } - ) + else + Some(functionIdent, mString, xs, mLastArg)) + else + None + | _ -> None) application |> Option.bind (fun (functionIdent, mString, xs, mLastArg) -> diff --git a/src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fs b/src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fs index f3cddb442..154c671be 100644 --- a/src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fs +++ b/src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fs @@ -43,17 +43,15 @@ let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = | _ -> None) |> Result.ofOption (fun () -> "No extended data") - // Find the binding name in the implementation file. - let impVisitor = - { new SyntaxVisitorBase<_>() with - override x.VisitBinding(path, defaultTraverse, SynBinding(headPat = pat)) = - match pat with - | SynPat.LongIdent(longDotId = SynLongIdent(id = [ ident ])) when Range.equals mDiag ident.idRange -> - Some ident - | _ -> None } - let! implBindingIdent = - SyntaxTraversal.Traverse(mDiag.Start, implParseAndCheckResults.GetParseResults.ParseTree, impVisitor) + (mDiag.Start, implParseAndCheckResults.GetParseResults.ParseTree) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynBinding(SynBinding(headPat = SynPat.LongIdent(longDotId = SynLongIdent(id = [ ident ])))) when + Range.equals mDiag ident.idRange + -> + Some ident + | _ -> None) |> Result.ofOption (fun () -> "No binding name found") let endPos = implBindingIdent.idRange.End @@ -71,24 +69,20 @@ let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = extendedDiagnosticData.ImplementationValue.GetValSignatureText(symbolUse.DisplayContext, symbolUse.Range) |> Result.ofOption (fun () -> "No val text found.") - // Find a matching val in the signature file. - let sigVisitor = - { new SyntaxVisitorBase<_>() with - override x.VisitValSig(path, defaultTraverse, SynValSig(range = mValSig)) = - if Range.rangeContainsRange mValSig extendedDiagnosticData.SignatureValue.DeclarationLocation then - Some mValSig - else - None } - let! (sigParseAndCheckResults: ParseAndCheckResults, _sigLine: string, _sigSourceText: IFSACSourceText) = getParseResultsForFile sigFileName extendedDiagnosticData.SignatureValue.DeclarationLocation.End + // Find a matching val in the signature file. let! mVal = - SyntaxTraversal.Traverse( - extendedDiagnosticData.SignatureValue.DeclarationLocation.End, - sigParseAndCheckResults.GetParseResults.ParseTree, - sigVisitor - ) + (extendedDiagnosticData.SignatureValue.DeclarationLocation.End, + sigParseAndCheckResults.GetParseResults.ParseTree) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynValSig(SynValSig(range = mValSig)) when + Range.rangeContainsRange mValSig extendedDiagnosticData.SignatureValue.DeclarationLocation + -> + Some mValSig + | _ -> None) |> Result.ofOption (fun () -> "No val range found in signature file") return diff --git a/src/FsAutoComplete/CodeFixes/UseTripleQuotedInterpolation.fs b/src/FsAutoComplete/CodeFixes/UseTripleQuotedInterpolation.fs index 85ed3428a..5620647ac 100644 --- a/src/FsAutoComplete/CodeFixes/UseTripleQuotedInterpolation.fs +++ b/src/FsAutoComplete/CodeFixes/UseTripleQuotedInterpolation.fs @@ -5,7 +5,8 @@ open FsAutoComplete.CodeFix.Types open Ionide.LanguageServerProtocol.Types open FsAutoComplete open FsAutoComplete.LspHelpers -open FsAutoComplete.FCSPatches +open FSharp.Compiler.Syntax +open FSharp.Compiler.Text.Range let title = "Use triple-quoted string interpolation" @@ -19,7 +20,15 @@ let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix = let! tyRes, _, sourceText = getParseResultsForFile filePath pos - match tyRes.GetParseResults.TryRangeOfStringInterpolationContainingPos pos with + let range = + (pos, tyRes.GetParseResults.ParseTree) + ||> ParsedInput.tryPick (fun _path node -> + match node with + | SyntaxNode.SynExpr(SynExpr.InterpolatedString(range = range)) when rangeContainsPos range pos -> + Some range + | _ -> None) + + match range with | Some range -> let! interpolationText = sourceText.GetText range // skip the leading '$' in the existing single-quoted interpolation diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index 36ad6070e..ed1c9f2de 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -30,6 +30,7 @@ open CliWrap open CliWrap.Buffered open FSharp.Compiler.EditorServices open FSharp.Compiler.Symbols +open FSharp.Compiler.Syntax open Fantomas.Client.Contracts open Fantomas.Client.LSPFantomasService @@ -2045,7 +2046,26 @@ type AdaptiveFSharpLspServer let! parseResults = state.GetParseResults fn |> Async.map Result.toOption let! (fullBindingRange, glyph, bindingIdents) = - parseResults.TryRangeOfNameOfNearestOuterBindingOrMember(protocolPosToPos loc.Range.Start) + let pos = protocolPosToPos loc.Range.Start + + (pos, parseResults.ParseTree) + ||> ParsedInput.tryPickLast (fun _path node -> + let (|BindingClass|) = + function + | SynBinding(valData = SynValData(memberFlags = None)) -> FSharpGlyph.Delegate + | _ -> FSharpGlyph.Method + + match node with + | SyntaxNode.SynBinding(SynBinding(headPat = pat) as b & BindingClass glyph) when + Range.rangeContainsPos b.RangeOfBindingWithRhs pos + -> + match pat with + | SynPat.LongIdent(longDotId = longIdentWithDots) -> + Some(b.RangeOfBindingWithRhs, glyph, longIdentWithDots.LongIdent) + | SynPat.Named(ident = SynIdent(ident, _); isThisVal = false) -> + Some(b.RangeOfBindingWithRhs, glyph, [ ident ]) + | _ -> None + | _ -> None) // We only want to use the last identifiers range because if we have a member like `self.MyMember` // F# Find Usages only works with the last identifier's range so we want to use `MyMember`.