@@ -2,18 +2,189 @@ module FsAutoComplete.NestedLanguages
22
33open FsToolkit.ErrorHandling
44open FSharp.Compiler .Syntax
5- open FSharp.Compiler .Syntax .SyntaxTraversal
65open FSharp.Compiler .Text
6+ open FSharp.Compiler .CodeAnalysis
7+ open FSharp.Compiler .Symbols
8+
9+ #nowarn " 57" // from-end slicing
710
811type private StringParameter =
912 { methodIdent: LongIdent
10- parameterRange: Range }
13+ parameterRange: Range
14+ rangesToRemove: Range []
15+ parameterPosition: int }
16+
17+ let discoverRangesToRemoveForInterpolatedString ( list : SynInterpolatedStringPart list ) =
18+ list
19+ |> List.choose ( function
20+ | SynInterpolatedStringPart.FillExpr( fillExpr = e) -> Some e.Range
21+ | _ -> None)
22+ |> List.toArray
23+
24+ let private (| Ident | _ |) ( e : SynExpr ) =
25+ match e with
26+ | SynExpr.LongIdent( longDotId = SynLongIdent( id = ident)) -> Some ident
27+ | _ -> None
28+
29+ let rec private (| IsApplicationWithStringParameters | _ |) ( e : SynExpr ) : option < StringParameter []> =
30+ match e with
31+ // lines inside a binding
32+ // let doThing () =
33+ // c.M("<div>")
34+ // c.M($"<div>{1 + 1}")
35+ // "<div>" |> c.M
36+ // $"<div>{1 + 1}" |> c.M
37+ | SynExpr.Sequential( expr1 = e1; expr2 = e2) ->
38+ [| match e1 with
39+ | IsApplicationWithStringParameters( stringParameter) -> yield ! stringParameter
40+ | _ -> ()
41+
42+ match e2 with
43+ | IsApplicationWithStringParameters( stringParameter) -> yield ! stringParameter
44+ | _ -> () |]
45+ // TODO: check if the array would be empty and return none
46+ |> Some
47+
48+ // method call with string parameter - c.M("<div>")
49+ | SynExpr.App(
50+ funcExpr = Ident( ident); argExpr = SynExpr.Paren( expr = SynExpr.Const( SynConst.String( text, kind, range), _)))
51+ // method call with string parameter - c.M "<div>"
52+ | SynExpr.App( funcExpr = Ident( ident); argExpr = SynExpr.Const( SynConst.String( text, kind, range), _)) ->
53+ Some(
54+ [| { methodIdent = ident
55+ parameterRange = range
56+ rangesToRemove = [||]
57+ parameterPosition = 0 } |]
58+ )
59+ // method call with interpolated string parameter - c.M $"<div>{1 + 1}"
60+ | SynExpr.App(
61+ funcExpr = SynExpr.LongIdent( longDotId = SynLongIdent( id = ident))
62+ argExpr = SynExpr.Paren( expr = SynExpr.InterpolatedString( contents = parts; range = range)))
63+ // method call with interpolated string parameter - c.M($"<div>{1 + 1}")
64+ | SynExpr.App(
65+ funcExpr = SynExpr.LongIdent( longDotId = SynLongIdent( id = ident))
66+ argExpr = SynExpr.InterpolatedString( contents = parts; range = range)) ->
67+ let rangesToRemove = discoverRangesToRemoveForInterpolatedString parts
68+
69+ Some(
70+ [| { methodIdent = ident
71+ parameterRange = range
72+ rangesToRemove = rangesToRemove
73+ parameterPosition = 0 } |]
74+ )
75+ // piped method call with string parameter - "<div>" |> c.M
76+ // piped method call with interpolated parameter - $"<div>{1 + 1}" |> c.M
77+ // method call with multiple string or interpolated string parameters (this also covers the case when not all parameters of the member are strings)
78+ // c.M("<div>", true) and/or c.M(true, "<div>")
79+ // piped method call with multiple string or interpolated string parameters (this also covers the case when not all parameters of the member are strings)
80+ // let binding that is a string value that has the stringsyntax attribute on it - [<StringSyntax("html")>] let html = "<div />"
81+ // all of the above but with literals
82+ | _ -> None
1183
84+ /// <summary></summary>
1285type private StringParameterFinder () =
13- inherit SyntaxVisitorBase< StringParameter[]>()
86+ inherit SyntaxCollectorBase()
87+
88+ let languages = ResizeArray< StringParameter>()
89+
90+ override _.WalkBinding ( SynBinding ( expr = expr)) =
91+ match expr with
92+ | IsApplicationWithStringParameters( stringParameters) -> languages.AddRange stringParameters
93+ | _ -> ()
94+
95+ override _.WalkSynModuleDecl ( decl ) =
96+ match decl with
97+ | SynModuleDecl.Expr( expr = IsApplicationWithStringParameters( stringParameters)) ->
98+ languages.AddRange stringParameters
99+ | _ -> ()
100+
101+ member _.NestedLanguages = languages.ToArray()
102+
103+
104+ let private findParametersForParseTree ( p : ParsedInput ) =
105+ let walker = StringParameterFinder()
106+ walkAst walker p
107+ walker.NestedLanguages
108+
109+ let private (| IsStringSyntax | _ |) ( a : FSharpAttribute ) =
110+ match a.AttributeType.FullName with
111+ | " System.Diagnostics.CodeAnalysis.StringSyntaxAttribute" ->
112+ match a.ConstructorArguments |> Seq.tryHead with
113+ | Some(_ ty, languageValue) -> Some( languageValue :?> string)
114+ | _ -> None
115+ | _ -> None
116+
117+ type NestedLanguageDocument = { Language: string ; Ranges: Range [] }
118+
119+ let rangeMinusRanges ( totalRange : Range ) ( rangesToRemove : Range []) : Range [] =
120+ match rangesToRemove with
121+ | [||] -> [| totalRange |]
122+ | _ ->
123+ let mutable returnVal = ResizeArray()
124+ let mutable currentStart = totalRange.Start
125+
126+ for r in rangesToRemove do
127+ returnVal.Add( Range.mkRange totalRange.FileName currentStart r.Start)
128+ currentStart <- r.End
129+
130+ returnVal.Add( Range.mkRange totalRange.FileName currentStart totalRange.End)
131+ returnVal.ToArray()
132+
133+ let private parametersThatAreStringSyntax
134+ (
135+ parameters : StringParameter [],
136+ checkResults : FSharpCheckFileResults ,
137+ text : IFSACSourceText
138+ ) : Async < NestedLanguageDocument []> =
139+ async {
140+ let returnVal = ResizeArray()
141+
142+ for p in parameters do
143+ let precedingParts , lastPart = p.methodIdent.[ 0 ..^ 1 ], p.methodIdent[^ 0 ]
144+ let endOfFinalTextToken = lastPart.idRange.End
145+
146+ match text.GetLine( endOfFinalTextToken) with
147+ | None -> ()
148+ | Some lineText ->
149+
150+ match
151+ checkResults.GetSymbolUseAtLocation(
152+ endOfFinalTextToken.Line,
153+ endOfFinalTextToken.Column,
154+ lineText,
155+ precedingParts |> List.map ( fun i -> i.idText)
156+ )
157+ with
158+ | None -> ()
159+ | Some usage ->
160+
161+ let sym = usage.Symbol
162+ // todo: keep MRU map of symbols to parameters and MRU of parameters to stringsyntax status
163+
164+ match sym with
165+ | : ? FSharpMemberOrFunctionOrValue as mfv ->
166+ let allParameters = mfv.CurriedParameterGroups |> Seq.collect id |> Seq.toArray
167+ let fsharpP = allParameters |> Seq.item p.parameterPosition
168+
169+ match fsharpP.Attributes |> Seq.tryPick (| IsStringSyntax|_|) with
170+ | Some language ->
171+ returnVal.Add
172+ { Language = language
173+ Ranges = rangeMinusRanges p.parameterRange p.rangesToRemove }
174+ | None -> ()
175+ | _ -> ()
176+
177+ return returnVal.ToArray()
178+ }
14179
15180/// to find all of the nested language highlights, we're going to do the following:
16181/// * find all of the interpolated strings or string literals in the file that are in parameter-application positions
17182/// * get the method calls happening at those positions to check if that method has the StringSyntaxAttribute
18183/// * if so, return a) the language in the StringSyntaxAttribute, and b) the range of the interpolated string
19- let findNestedLanguages ( tyRes : ParseAndCheckResults ) = async { return [||] }
184+ let findNestedLanguages ( tyRes : ParseAndCheckResults , text : IFSACSourceText ) : NestedLanguageDocument [] Async =
185+ async {
186+ // get all string constants
187+ let potentialParameters = findParametersForParseTree tyRes.GetAST
188+ let! actualStringSyntaxParameters = parametersThatAreStringSyntax ( potentialParameters, tyRes.GetCheckResults, text)
189+ return actualStringSyntaxParameters
190+ }
0 commit comments