@@ -11,6 +11,7 @@ open Microsoft.CodeAnalysis.Text
1111open FSharp.Compiler .Syntax
1212open FSharp.Compiler .SyntaxTrivia
1313open FSharp.Compiler .Text
14+ open FSharp.Compiler .Tokenization
1415
1516/// A place in front of which a declaration used by the selected expression can be inserted.
1617[<RequireQualifiedAccess; NoComparison; NoEquality>]
@@ -76,25 +77,107 @@ let restOfLineIsClosers (sourceText: SourceText) (position: pos) =
7677 code
7778 |> Seq.forall ( fun c -> Char.IsWhiteSpace c || c = ')' || c = ']' || c = '}' || c = '|' )
7879
79- let linesInsideLiterals ( parseTree : ParsedInput ) =
80- ( HashSet< int>(), parseTree)
81- ||> ParsedInput.fold ( fun lines _ node ->
82- match node with
83- | SyntaxNode.SynExpr( SynExpr.Const( range = m))
84- | SyntaxNode.SynExpr( SynExpr.InterpolatedString( range = m))
85- | SyntaxNode.SynPat( SynPat.Const( range = m)) ->
86- for line in m.StartLine + 1 .. m.EndLine do
87- lines.Add( Line.toZ line) |> ignore
88- | _ -> ()
80+ /// Whether a line lexed from this state starts inside a string literal.
81+ let private continuesString ( state : FSharpTokenizerColorState ) =
82+ match state with
83+ | FSharpTokenizerColorState.InitialState
84+ | FSharpTokenizerColorState.Token
85+ | FSharpTokenizerColorState.IfDefSkip
86+ | FSharpTokenizerColorState.EndLineThenSkip
87+ | FSharpTokenizerColorState.EndLineThenToken
88+ | FSharpTokenizerColorState.Comment
89+ | FSharpTokenizerColorState.SingleLineComment
90+ | FSharpTokenizerColorState.StringInComment
91+ | FSharpTokenizerColorState.VerbatimStringInComment
92+ | FSharpTokenizerColorState.TripleQuoteStringInComment -> false
93+ // String, VerbatimString, TripleQuoteString and the extended interpolated string state, which the public enum does not name.
94+ | _ -> true
95+
96+ let private isSkipped ( state : FSharpTokenizerLexState ) =
97+ match FSharpLineTokenizer.ColorStateOfLexState state with
98+ | FSharpTokenizerColorState.IfDefSkip
99+ | FSharpTokenizerColorState.EndLineThenSkip -> true
100+ | _ -> false
101+
102+ let private startsWithDirective ( code : ReadOnlySpan < char >) ( keyword : string ) =
103+ code.StartsWith( keyword.AsSpan(), StringComparison.Ordinal)
104+ && ( code.Length = keyword.Length
105+ || Char.IsWhiteSpace code[ keyword.Length]
106+ || code[ keyword.Length] = '/' )
107+
108+ /// Whether the line is a directive the lexer recognizes while it skips an inactive branch.
109+ let private isSkippedDirective ( line : string ) =
110+ let code = line.AsSpan() .TrimStart()
111+
112+ startsWithDirective code " #if"
113+ || startsWithDirective code " #elif"
114+ || startsWithDirective code " #else"
115+ || startsWithDirective code " #endif"
116+
117+ let private scanLine ( tokenizer : FSharpSourceTokenizer ) ( line : string ) ( state : FSharpTokenizerLexState ) =
118+ let lineTokenizer = tokenizer.CreateLineTokenizer line
119+
120+ let rec loop state =
121+ match lineTokenizer.ScanToken state with
122+ | Some _, next -> loop next
123+ | None, next -> next
124+
125+ loop state
126+
127+ /// Lines inside multi-line string literals of the branches of ` #if ` the defines leave inactive. Each run of inactive
128+ /// lines between two directives is lexed as the code it is when its branch is taken.
129+ let private linesInsideInactiveLiterals ( sourceText : SourceText ) ( defines : string list ) ( langVersion : string ) ( lines : HashSet < int >) =
130+ let fileTokenizer = FSharpSourceTokenizer( defines, None, Some langVersion)
131+ let branchTokenizer = FSharpSourceTokenizer([], None, Some langVersion)
132+ let mutable fileState = FSharpTokenizerLexState.Initial
133+ let mutable branchState = FSharpTokenizerLexState.Initial
134+
135+ for line in sourceText.Lines do
136+ let text = line.ToString()
137+
138+ if isSkipped fileState && not ( isSkippedDirective text) then
139+ if continuesString ( FSharpLineTokenizer.ColorStateOfLexState branchState) then
140+ lines.Add line.LineNumber |> ignore
141+
142+ branchState <- scanLine branchTokenizer text branchState
143+ else
144+ branchState <- FSharpTokenizerLexState.Initial
145+
146+ fileState <- scanLine fileTokenizer text fileState
147+
148+ /// Lines inside multi-line string literals, including those in branches of ` #if ` the defines leave inactive.
149+ let linesInsideLiterals ( sourceText : SourceText ) ( parseTree : ParsedInput ) ( defines : string list ) ( langVersion : string ) =
150+ let lines =
151+ ( HashSet< int>(), parseTree)
152+ ||> ParsedInput.fold ( fun lines _ node ->
153+ match node with
154+ | SyntaxNode.SynExpr( SynExpr.Const( range = m))
155+ | SyntaxNode.SynExpr( SynExpr.InterpolatedString( range = m))
156+ | SyntaxNode.SynPat( SynPat.Const( range = m)) ->
157+ for line in m.StartLine + 1 .. m.EndLine do
158+ lines.Add( Line.toZ line) |> ignore
159+ | _ -> ()
160+
161+ lines)
162+
163+ let directives =
164+ match parseTree with
165+ | ParsedInput.ImplFile file -> file.Trivia.ConditionalDirectives
166+ | ParsedInput.SigFile file -> file.Trivia.ConditionalDirectives
167+
168+ if not directives.IsEmpty then
169+ linesInsideInactiveLiterals sourceText defines langVersion lines
89170
90- lines)
171+ lines
91172
92173let usedNames ( parseTree : ParsedInput ) =
93174 ( HashSet< string>( StringComparer.Ordinal), parseTree)
94175 ||> ParsedInput.fold ( fun names _ node ->
95176 match node with
96177 | SyntaxNode.SynExpr( SynExpr.Ident ident)
97178 | SyntaxNode.SynExpr( SynExpr.LongIdent( longDotId = SynLongIdent( id = ident :: _)))
179+ | SyntaxNode.SynExpr( SynExpr.LongIdentSet( longDotId = SynLongIdent( id = ident :: _)))
180+ | SyntaxNode.SynExpr( SynExpr.NamedIndexedPropertySet( longDotId = SynLongIdent( id = ident :: _)))
98181 | SyntaxNode.SynPat( SynPat.Named( ident = SynIdent( ident, _)))
99182 | SyntaxNode.SynPat( SynPat.LongIdent( longDotId = SynLongIdent( id = ident :: _))) -> names.Add ident.idText |> ignore
100183 | _ -> ()
0 commit comments