Skip to content

Commit

Permalink
Main to nightly (#1289)
Browse files Browse the repository at this point in the history
* Shift multiline paren contents less aggressively (#1242)

* Shift multiline paren contents less aggressively

* Make it actually work

* Disambiguate AsSpan overload

* Add some code fixes for type mismatch. (#1250)

* Migrate FAKE to Fun.Build (#1256)

* Migrate FAKE to Fun.Build

* Add default Build pipeline.

* Purge it with fire (#1255)

* Bump analyzers and Fantomas (#1257)

* Add empty, disabled tests for go-to-def on C# symbol scenario

* fix unicode characters in F# compiler diagnostic messages (#1265)

* fix unicode chars in F# compiler diagnostic messages

* fix typo in ShadowedTimeouts focused tests

* fixup! fix unicode chars in F# compiler diagnostic messages

* remove focused tests...

* remove debug prints

Co-authored-by: Jimmy Byrd <jimmybyrd@gmail.com>

---------

Co-authored-by: Jimmy Byrd <jimmybyrd@gmail.com>

* - remove an ignored call to protocolRangeToRange (#1266)

- remove an ignored instance of StreamJsonRpcTracingStrategy

* Place XML doc lines before any attribute lists (#1267)

* Don't generate params for explicit getters/setters as they are flagged invalid by the compiler (#1268)

* bump ProjInfo to the next version to get support for loading broken projects and loading traversal projects (#1270)

* only allow one GetProjectOptionsFromScript at a time (#1275)

ionide/ionide-vscode-fsharp#2005

* changelog for v0.72.0

* changelog for v0.72.1

* Use actualRootPath instead of p.RootPath when peeking workspaces. (#1278)

This fix the issue where rootUri was ignored when using AutomaticWorkspaceInit.

* changelog for v0.72.2

* Add support for Cancel a Work Done Progress  (#1274)

* Add support for Cancel WorkDoneProgress

* Fix up saving cancellation

* package the tool for .NET 8 as well (#1281)

* update changelogs

* Adds basic OTel Metric support to fsautocomplete (#1283)

* Some parens fixes (#1286)

See dotnet/fsharp#16901

* Keep parens around outlaw `match` exprs (where the first `|` is
  leftward of the start of the `match` keyword).

* Ignore single-line comments when determining offsides lines.

* Don't add a space when removing parens when doing so would result in
  reparsing an infix op as a prefix op.

* Run LSP tests sequenced (#1287)

I'm tired of all the weird failures because of parallelism

---------

Co-authored-by: Brian Rourke Boll <brianrourkeboll@users.noreply.github.com>
Co-authored-by: Florian Verdonck <florian.verdonck@outlook.com>
Co-authored-by: Krzysztof Cieślak <krzysztof_cieslak@windowslive.com>
Co-authored-by: MrLuje <ogmniluje@gmail.com>
Co-authored-by: dawe <dawedawe@posteo.de>
Co-authored-by: Chet Husk <baronfel@users.noreply.github.com>
Co-authored-by: oupson <31827294+oupson@users.noreply.github.com>
Co-authored-by: Chet Husk <chusk3@gmail.com>
  • Loading branch information
9 people authored May 9, 2024
1 parent 08b8711 commit 7795214
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 32 deletions.
13 changes: 13 additions & 0 deletions src/FsAutoComplete.Core/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,19 @@ type ReadOnlySpanExtensions =

if found then i else -1

[<Extension>]
static member IndexOfAnyExcept(span: ReadOnlySpan<char>, values: ReadOnlySpan<char>) =
let mutable i = 0
let mutable found = false

while not found && i < span.Length do
if values.IndexOf span[i] < 0 then
found <- true
else
i <- i + 1

if found then i else -1

[<Extension>]
static member LastIndexOfAnyExcept(span: ReadOnlySpan<char>, value0: char, value1: char) =
let mutable i = span.Length - 1
Expand Down
3 changes: 3 additions & 0 deletions src/FsAutoComplete.Core/Utils.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ type ReadOnlySpanExtensions =
[<Extension>]
static member IndexOfAnyExcept: span: ReadOnlySpan<char> * value0: char * value1: char -> int

[<Extension>]
static member IndexOfAnyExcept: span: ReadOnlySpan<char> * values: ReadOnlySpan<char> -> int

[<Extension>]
static member LastIndexOfAnyExcept: span: ReadOnlySpan<char> * value0: char * value1: char -> int
#endif
Expand Down
116 changes: 85 additions & 31 deletions src/FsAutoComplete/CodeFixes/RemoveUnnecessaryParentheses.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,35 @@ open FsAutoComplete.CodeFix.Types
open FsToolkit.ErrorHandling
open FsAutoComplete
open FsAutoComplete.LspHelpers
open FSharp.Compiler.Text

let title = "Remove unnecessary parentheses"

[<AutoOpen>]
module private Patterns =
let inline toPat f x = if f x then ValueSome() else ValueNone

/// Starts with //.
[<return: Struct>]
let (|StartsWithSingleLineComment|_|) (s: string) =
if s.AsSpan().TrimStart(' ').StartsWith("//".AsSpan()) then
ValueSome StartsWithSingleLineComment
else
ValueNone

/// Starts with match, e.g.,
///
/// (match … with
/// | … -> …)
[<return: Struct>]
let (|StartsWithMatch|_|) (s: string) =
let s = s.AsSpan().TrimStart ' '

if s.StartsWith("match".AsSpan()) && (s.Length = 5 || s[5] = ' ') then
ValueSome StartsWithMatch
else
ValueNone

[<AutoOpen>]
module Char =
[<return: Struct>]
Expand Down Expand Up @@ -90,8 +112,8 @@ let fix (getFileLines: GetFileLines) : CodeFix =
| None -> id

let (|ShiftLeft|NoShift|ShiftRight|) (sourceText: IFSACSourceText) =
let startLineNo = range.StartLine
let endLineNo = range.EndLine
let startLineNo = Line.toZ range.StartLine
let endLineNo = Line.toZ range.EndLine

if startLineNo = endLineNo then
NoShift
Expand All @@ -105,11 +127,17 @@ let fix (getFileLines: GetFileLines) : CodeFix =
match line.AsSpan(startCol).IndexOfAnyExcept(' ', ')') with
| -1 -> loop innerOffsides (lineNo + 1) 0
| i ->
match innerOffsides with
| NoneYet -> loop (FirstLine(i + startCol)) (lineNo + 1) 0
| FirstLine innerOffsides -> loop (FollowingLine(innerOffsides, i + startCol)) (lineNo + 1) 0
| FollowingLine(firstLine, innerOffsides) ->
loop (FollowingLine(firstLine, min innerOffsides (i + startCol))) (lineNo + 1) 0
match line[i + startCol ..] with
| StartsWithMatch
| StartsWithSingleLineComment -> loop innerOffsides (lineNo + 1) 0
| _ ->
match innerOffsides with
| NoneYet -> loop (FirstLine(i + startCol)) (lineNo + 1) 0

| FirstLine innerOffsides -> loop (FollowingLine(innerOffsides, i + startCol)) (lineNo + 1) 0

| FollowingLine(firstLine, innerOffsides) ->
loop (FollowingLine(firstLine, min innerOffsides (i + startCol))) (lineNo + 1) 0
else
innerOffsides

Expand All @@ -133,24 +161,27 @@ let fix (getFileLines: GetFileLines) : CodeFix =

let newText =
let (|ShouldPutSpaceBefore|_|) (s: string) =
// ……(……)
// ↑↑ ↑
(sourceText.TryGetChar(range.Start.IncColumn -1), sourceText.TryGetChar range.Start)
||> Option.map2 (fun twoBefore oneBefore ->
match twoBefore, oneBefore, s[0] with
| _, _, ('\n' | '\r') -> None
| '[', '|', (Punctuation | LetterOrDigit) -> None
| _, '[', '<' -> Some ShouldPutSpaceBefore
| _, ('(' | '[' | '{'), _ -> None
| _, '>', _ -> Some ShouldPutSpaceBefore
| ' ', '=', _ -> Some ShouldPutSpaceBefore
| _, '=', ('(' | '[' | '{') -> None
| _, '=', (Punctuation | Symbol) -> Some ShouldPutSpaceBefore
| _, LetterOrDigit, '(' -> None
| _, (LetterOrDigit | '`'), _ -> Some ShouldPutSpaceBefore
| _, (Punctuation | Symbol), (Punctuation | Symbol) -> Some ShouldPutSpaceBefore
| _ -> None)
|> Option.flatten
match s with
| StartsWithMatch -> None
| _ ->
// ……(……)
// ↑↑ ↑
(sourceText.TryGetChar(range.Start.IncColumn -1), sourceText.TryGetChar range.Start)
||> Option.map2 (fun twoBefore oneBefore ->
match twoBefore, oneBefore, s[0] with
| _, _, ('\n' | '\r') -> None
| '[', '|', (Punctuation | LetterOrDigit) -> None
| _, '[', '<' -> Some ShouldPutSpaceBefore
| _, ('(' | '[' | '{'), _ -> None
| _, '>', _ -> Some ShouldPutSpaceBefore
| ' ', '=', _ -> Some ShouldPutSpaceBefore
| _, '=', ('(' | '[' | '{') -> None
| _, '=', (Punctuation | Symbol) -> Some ShouldPutSpaceBefore
| _, LetterOrDigit, '(' -> None
| _, (LetterOrDigit | '`'), _ -> Some ShouldPutSpaceBefore
| _, (Punctuation | Symbol), (Punctuation | Symbol) -> Some ShouldPutSpaceBefore
| _ -> None)
|> Option.flatten

let (|ShouldPutSpaceAfter|_|) (s: string) =
// (……)…
Expand All @@ -160,22 +191,45 @@ let fix (getFileLines: GetFileLines) : CodeFix =
match s[s.Length - 1], endChar with
| '>', ('|' | ']') -> Some ShouldPutSpaceAfter
| _, (')' | ']' | '[' | '}' | '.' | ';' | ',' | '|') -> None
| _, ('+' | '-' | '%' | '&' | '!' | '~') -> None
| (Punctuation | Symbol), (Punctuation | Symbol | LetterOrDigit) -> Some ShouldPutSpaceAfter
| LetterOrDigit, LetterOrDigit -> Some ShouldPutSpaceAfter
| _ -> None)

let (|WouldTurnInfixIntoPrefix|_|) (s: string) =
// (……)…
// ↑ ↑
sourceText.TryGetChar(range.End.IncColumn 1)
|> Option.bind (fun endChar ->
match s[s.Length - 1], endChar with
| (Punctuation | Symbol), ('+' | '-' | '%' | '&' | '!' | '~') ->
match sourceText.GetLine range.End with
| None -> None
| Some line ->
// (……)+…
// ↑
match line.AsSpan(range.EndColumn).IndexOfAnyExcept("*/%-+:^@><=!|$.?".AsSpan()) with
| -1 -> None
| i when line[range.EndColumn + i] <> ' ' -> Some WouldTurnInfixIntoPrefix
| _ -> None
| _ -> None)

match adjusted with
| ShouldPutSpaceBefore & ShouldPutSpaceAfter -> " " + adjusted + " "
| ShouldPutSpaceBefore -> " " + adjusted
| ShouldPutSpaceAfter -> adjusted + " "
| adjusted -> adjusted
| WouldTurnInfixIntoPrefix -> ValueNone
| ShouldPutSpaceBefore & ShouldPutSpaceAfter -> ValueSome(" " + adjusted + " ")
| ShouldPutSpaceBefore -> ValueSome(" " + adjusted)
| ShouldPutSpaceAfter -> ValueSome(adjusted + " ")
| adjusted -> ValueSome adjusted

return
[ { Edits = [| { Range = d.Range; NewText = newText } |]
newText
|> ValueOption.map (fun newText ->
{ Edits = [| { Range = d.Range; NewText = newText } |]
File = codeActionParams.TextDocument
Title = title
SourceDiagnostic = Some d
Kind = FixKind.Fix } ]
Kind = FixKind.Fix })
|> ValueOption.toList

| _notParens -> return []
})
63 changes: 62 additions & 1 deletion test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3437,7 +3437,68 @@ let private removeUnnecessaryParenthesesTests state =
longFunctionName
longVarName1
longVarName2
""" ])
"""

testCaseAsync "Handles outlaw match exprs"
<| CodeFix.check
server
"""
3 > (match x with
| 1
| _ -> 3)$0
"""
(Diagnostics.expectCode "FSAC0004")
selector
"""
3 > match x with
| 1
| _ -> 3
"""

testCaseAsync "Handles even more outlaw match exprs"
<| CodeFix.check
server
"""
3 > ( match x with
| 1
| _ -> 3)$0
"""
(Diagnostics.expectCode "FSAC0004")
selector
"""
3 > match x with
| 1
| _ -> 3
"""

testCaseAsync "Handles single-line comments"
<| CodeFix.check
server
"""
3 > (match x with
// Lol.
| 1
| _ -> 3)$0
"""
(Diagnostics.expectCode "FSAC0004")
selector
"""
3 > match x with
// Lol.
| 1
| _ -> 3
"""

testCaseAsync "Keep parens when removal would cause reparse of infix as prefix"
<| CodeFix.checkNotApplicable
server
"""
""+(Unchecked.defaultof<string>)$0+""
"""
(Diagnostics.expectCode "FSAC0004")
selector

])

let tests textFactory state =
testSequenced <|
Expand Down

0 comments on commit 7795214

Please sign in to comment.