@@ -92,192 +92,6 @@ module Commands =
9292 let fantomasLogger = LogProvider.getLoggerByName " Fantomas"
9393 let commandsLogger = LogProvider.getLoggerByName " Commands"
9494
95- /// Extracts the case name(s) from an active pattern name string.
96- /// For partial patterns like "|CaseName|_|" returns "CaseName"
97- /// For full patterns like "|Even|Odd|" returns the first case name
98- let extractActivePatternCaseName ( name : string ) : string =
99- let parts = name.Split( '|' ) |> Array.filter ( fun s -> s <> " " && s <> " _" )
100- if parts.Length > 0 then parts.[ 0 ] else name
101-
102- /// Extracts all case names from an active pattern display name.
103- /// For "(|ParseInt|_|)" returns ["ParseInt"]
104- /// For "(|Even|Odd|)" returns ["Even"; "Odd"]
105- let extractActivePatternCaseNames ( displayName : string ) : string list =
106- displayName.TrimStart( '|' , '(' ) .TrimEnd( '|' , '_' , ')' ) .Split( '|' )
107- |> Array.filter ( fun s -> not ( String.IsNullOrWhiteSpace( s)))
108- |> Array.toList
109-
110- /// Finds usages of partial active pattern cases by walking the AST.
111- ///
112- /// Returns syntactic ranges from the AST where the specified case names appear in pattern matches.
113- /// For qualified identifiers (e.g., `MyModule.ParseInt`), returns the full qualified range to match FCS behavior.
114- /// Recursively traverses all pattern forms, including nested and composite patterns (tuples, lists, or/and/as patterns, etc.).
115- ///
116- /// Note: Only syntactic (AST) ranges are returned; semantic information is not considered.
117- let findPartialActivePatternCaseUsages ( caseNames : string list ) ( parseResults : FSharpParseFileResults ) : range list =
118- let rec walkPat ( pat : SynPat ) =
119- seq {
120- match pat with
121- | SynPat.LongIdent( longDotId = synLongIdent; argPats = args) ->
122- // Check if this is a potential case usage
123- match synLongIdent with
124- | SynLongIdent( id = idents) ->
125- match idents with
126- | [] -> ()
127- | [ singleIdent ] when List.contains singleIdent.idText caseNames ->
128- // Single identifier that matches a case name
129- yield singleIdent.idRange
130- | multipleIdents ->
131- // Qualified identifier (e.g., MyModule.ParseInt)
132- let lastIdent = List.last multipleIdents
133-
134- if List.contains lastIdent.idText caseNames then
135- // Return the full qualified range to match what FCS returns
136- yield synLongIdent.Range
137-
138- // Recursively check arguments
139- match args with
140- | SynArgPats.Pats pats -> yield ! List.collect ( walkPat >> Seq.toList) pats
141- | SynArgPats.NamePatPairs( pats = pairs; range = _) ->
142- yield ! List.collect ( fun ( NamePatPairField ( pat = pat )) -> walkPat pat |> Seq.toList) pairs
143-
144- | SynPat.Paren( pat, _) -> yield ! walkPat pat
145- | SynPat.Tuple( elementPats = pats) -> yield ! List.collect ( walkPat >> Seq.toList) pats
146- | SynPat.ArrayOrList(_, pats, _) -> yield ! List.collect ( walkPat >> Seq.toList) pats
147- | SynPat.Ands( pats, _) -> yield ! List.collect ( walkPat >> Seq.toList) pats
148- | SynPat.Or( lhsPat = pat1; rhsPat = pat2) ->
149- yield ! walkPat pat1
150- yield ! walkPat pat2
151- | SynPat.As( lpat, rpat, _) ->
152- yield ! walkPat lpat
153- yield ! walkPat rpat
154- | SynPat.Typed( pat, _, _) -> yield ! walkPat pat
155- | SynPat.Attrib( pat, _, _) -> yield ! walkPat pat
156- | SynPat.ListCons( lpat, rpat, _, _) ->
157- yield ! walkPat lpat
158- yield ! walkPat rpat
159- | _ -> ()
160- }
161-
162- let rec walkExpr ( expr : SynExpr ) =
163- let walkMatchClauses clauses =
164- seq {
165- for clause in clauses do
166- match clause with
167- | SynMatchClause( pat = pat; resultExpr = resultExpr) ->
168- yield ! walkPat pat
169- yield ! walkExpr resultExpr
170- }
171-
172- seq {
173- match expr with
174- | SynExpr.Match( expr = matchExpr; clauses = clauses) ->
175- yield ! walkExpr matchExpr
176- yield ! walkMatchClauses clauses
177- | SynExpr.MatchBang( expr = matchExpr; clauses = clauses) ->
178- yield ! walkExpr matchExpr
179- yield ! walkMatchClauses clauses
180- | SynExpr.TryWith( tryExpr = tryExpr; withCases = clauses) ->
181- yield ! walkExpr tryExpr
182- yield ! walkMatchClauses clauses
183- | SynExpr.MatchLambda( matchClauses = clauses) -> yield ! walkMatchClauses clauses
184- | SynExpr.Lambda( args = args; body = body) ->
185- match args with
186- | SynSimplePats.SimplePats( pats = pats) ->
187- for spat in pats do
188- match spat with
189- | SynSimplePat.Typed( pat, _, _)
190- | SynSimplePat.Attrib( pat, _, _) ->
191- match pat with
192- | SynSimplePat.Id( ident, _, _, _, _, _) when List.contains ident.idText caseNames -> yield ident.idRange
193- | _ -> ()
194- | SynSimplePat.Id( ident, _, _, _, _, _) when List.contains ident.idText caseNames -> yield ident.idRange
195- | _ -> ()
196-
197- yield ! walkExpr body
198- // Continue walking nested expressions
199- | SynExpr.App( funcExpr = e1; argExpr = e2) ->
200- yield ! walkExpr e1
201- yield ! walkExpr e2
202- | SynExpr.LetOrUse( bindings = bindings; body = body) ->
203- // Walk the binding expressions
204- for binding in bindings do
205- match binding with
206- | SynBinding( expr = expr) -> yield ! walkExpr expr
207-
208- // Walk the body
209- yield ! walkExpr body
210- | SynExpr.Sequential( expr1 = e1; expr2 = e2) ->
211- yield ! walkExpr e1
212- yield ! walkExpr e2
213- | SynExpr.IfThenElse( ifExpr = e1; thenExpr = e2; elseExpr = e3opt) ->
214- yield ! walkExpr e1
215- yield ! walkExpr e2
216-
217- match e3opt with
218- | Some e3 -> yield ! walkExpr e3
219- | None -> ()
220- | SynExpr.Paren( expr, _, _, _) -> yield ! walkExpr expr
221- | SynExpr.Typed( expr = expr) -> yield ! walkExpr expr
222- // Computation expressions (seq, async, task, etc.)
223- | SynExpr.ComputationExpr( expr = expr) -> yield ! walkExpr expr
224- // Array or list expressions
225- | SynExpr.ArrayOrList( exprs = exprs) -> yield ! List.collect ( walkExpr >> Seq.toList) exprs
226- | SynExpr.ArrayOrListComputed( expr = expr) -> yield ! walkExpr expr
227- // For loops
228- | SynExpr.For( doBody = body) -> yield ! walkExpr body
229- | SynExpr.ForEach( enumExpr = enumExpr; bodyExpr = body) ->
230- yield ! walkExpr enumExpr
231- yield ! walkExpr body
232- // While loop
233- | SynExpr.While( whileExpr = whileExpr; doExpr = doExpr) ->
234- yield ! walkExpr whileExpr
235- yield ! walkExpr doExpr
236- // Tuples
237- | SynExpr.Tuple( exprs = exprs) -> yield ! List.collect ( walkExpr >> Seq.toList) exprs
238- // Record expressions
239- | SynExpr.Record( copyInfo = copyInfo; recordFields = fields) ->
240- match copyInfo with
241- | Some( expr, _) -> yield ! walkExpr expr
242- | None -> ()
243-
244- for ( SynExprRecordField( expr = exprOpt)) in fields do
245- match exprOpt with
246- | Some expr -> yield ! walkExpr expr
247- | None -> ()
248- | _ -> ()
249- }
250-
251- let rec walkDecl ( decl : SynModuleDecl ) =
252- seq {
253- match decl with
254- | SynModuleDecl.Let( bindings = bindings) ->
255- for binding in bindings do
256- match binding with
257- | SynBinding( expr = expr) -> yield ! walkExpr expr
258- | SynModuleDecl.Expr( expr, _) -> yield ! walkExpr expr
259- | SynModuleDecl.NestedModule( decls = decls) -> yield ! List.collect ( walkDecl >> Seq.toList) decls
260- | SynModuleDecl.Types( typeDefs, _) ->
261- for typeDef in typeDefs do
262- match typeDef with
263- | SynTypeDefn( members = members) ->
264- for memb in members do
265- match memb with
266- | SynMemberDefn.Member( SynBinding( expr = expr), _) -> yield ! walkExpr expr
267- | SynMemberDefn.LetBindings( bindings, _, _, _) ->
268- for SynBinding( expr = expr) in bindings do
269- yield ! walkExpr expr
270- | _ -> ()
271- | _ -> ()
272- }
273-
274- match parseResults.ParseTree with
275- | ParsedInput.ImplFile( ParsedImplFileInput( contents = modules)) ->
276- modules
277- |> List.collect ( fun ( SynModuleOrNamespace ( decls = decls )) -> decls |> List.collect ( walkDecl >> Seq.toList))
278- |> List.distinctBy ( fun r -> r.Start, r.End)
279- | _ -> []
280-
28195 let addFile ( fsprojPath : string ) fileVirtPath =
28296 async {
28397 try
@@ -936,25 +750,7 @@ module Commands =
936750 asyncResult {
937751 let symbol = symbolUse.Symbol
938752
939- let symbolNameCore =
940- match symbol with
941- | : ? FSharpActivePatternCase as apc ->
942- // For active pattern cases, extract just the case name without bars
943- // apc.Name may include bars like "|LetterOrDigit|_|" for partial patterns
944- if apc.Name.StartsWith( " |" ) then
945- extractActivePatternCaseName apc.Name
946- else
947- apc.Name
948- | : ? FSharpMemberOrFunctionOrValue as mfv when mfv .IsActivePattern ->
949- // For active pattern functions, extract just the case name for partial patterns
950- // For full patterns like "(|Even|Odd|)", use DisplayNameCore as-is
951- let displayName = symbol.DisplayNameCore
952-
953- if displayName.Contains( " |_|" ) then
954- extractActivePatternCaseName displayName
955- else
956- displayName
957- | _ -> symbol.DisplayNameCore
753+ let symbolNameCore = symbol.DisplayNameCore
958754
959755 let tryAdjustRanges ( text : IFSACSourceText , ranges : seq < Range >) =
960756 let ranges = ranges |> Seq.map ( fun range -> range.NormalizeDriveLetterCasing())
@@ -983,73 +779,13 @@ module Commands =
983779 let! ct = Async.CancellationToken
984780 let symbolUses = tyRes.GetCheckResults.GetUsesOfSymbolInFile( symbol, ct)
985781
986- let ( symbolUses : _ seq ), additionalRangesForPartialPatterns =
987- let baseFiltered : _ seq =
988- if includeDeclarations then
989- symbolUses
990- else
991- symbolUses |> Seq.filter ( fun u -> not u.IsFromDefinition)
992-
993- // For Active Pattern Cases, FCS returns all cases in the pattern, not just the specific one
994- // We need to filter to only the symbol that matches our query
995- // BUT: if querying from the Active Pattern declaration itself (FSharpMemberOrFunctionOrValue),
996- // we also want to find the case usages
997- match symbolUse.Symbol with
998- | : ? FSharpActivePatternCase as apc ->
999- // Querying from a specific case - filter to just that case
1000- let filtered =
1001- baseFiltered
1002- |> Seq.filter ( fun u ->
1003- match u.Symbol with
1004- | : ? FSharpActivePatternCase as foundApc -> foundApc .Name = apc .Name
1005- | _ -> false )
1006-
1007- // For partial active patterns in .fsx files, FCS may not return case usages
1008- // We walk the AST and deduplicate with what FCS found
1009- let isPartialPattern = apc.Group.IsTotal |> not
1010-
1011- let caseUsageRanges =
1012- if isPartialPattern then
1013- findPartialActivePatternCaseUsages [ apc.Name ] tyRes.GetParseResults
1014- else
1015- // Complete patterns - FCS handles these correctly
1016- []
1017-
1018- filtered, caseUsageRanges
1019- | : ? FSharpMemberOrFunctionOrValue as mfv when
1020- mfv.IsActivePattern
1021- || ( mfv.DisplayName.StartsWith( " (|" ) && mfv.DisplayName.EndsWith( " |)" ))
1022- ->
1023- // Querying from the active pattern function declaration
1024- // For partial active patterns like (|ParseInt|_|), include case usages in match expressions
1025- // For complete active patterns like (|Even|Odd|), only return the function declaration
1026- // Note: IsActivePattern is true for direct definitions, but let-bound values need DisplayName check
1027-
1028- let patternDisplayName = mfv.DisplayName
1029- // DisplayName includes parens: "(|ParseInt|_|)" so we need to check for "|_|)" not just "|_|"
1030- let isPartialActivePattern = patternDisplayName.Contains( " |_|" )
1031-
1032- if not isPartialActivePattern then
1033- // For complete patterns, only return the pattern function declaration
1034- baseFiltered, []
1035- else
1036- // For partial patterns, find case usages by walking the AST
1037- let caseNames = extractActivePatternCaseNames patternDisplayName
1038-
1039- let caseUsageRanges =
1040- findPartialActivePatternCaseUsages caseNames tyRes.GetParseResults
1041-
1042- // For partial patterns, FCS doesn't include case usages in pattern matches
1043- // We found them by walking the AST, so we return them as additional ranges
1044- baseFiltered, caseUsageRanges
1045- | _ -> baseFiltered, []
1046-
1047- let ranges =
1048- let baseRanges = symbolUses |> Seq.map ( fun u -> u.Range)
1049- // Add any additional ranges we found from walking the AST for partial active patterns
1050- // Deduplicate based on range start and end positions
1051- Seq.append baseRanges ( Seq.ofList additionalRangesForPartialPatterns)
1052- |> Seq.distinctBy ( fun r -> r.Start, r.End)
782+ let symbolUses : _ seq =
783+ if includeDeclarations then
784+ symbolUses
785+ else
786+ symbolUses |> Seq.filter ( fun u -> not u.IsFromDefinition)
787+
788+ let ranges = symbolUses |> Seq.map ( fun u -> u.Range)
1053789 // Note: tryAdjustRanges is designed to only be able to fail iff `errorOnFailureToFixRange` is `true`
1054790 let! ranges = tryAdjustRanges ( text, ranges)
1055791 let ranges = dict [ ( text.FileName, Seq.toArray ranges) ]
0 commit comments