From 7795214a878c7d13dfd23a9c9da6014ca16cc89f Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Wed, 8 May 2024 23:10:49 -0400 Subject: [PATCH] Main to nightly (#1289) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --------- Co-authored-by: Jimmy Byrd * - 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) https://github.com/ionide/ionide-vscode-fsharp/issues/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 https://github.com/dotnet/fsharp/pull/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 Co-authored-by: Florian Verdonck Co-authored-by: Krzysztof Cieślak Co-authored-by: MrLuje Co-authored-by: dawe Co-authored-by: Chet Husk Co-authored-by: oupson <31827294+oupson@users.noreply.github.com> Co-authored-by: Chet Husk --- src/FsAutoComplete.Core/Utils.fs | 13 ++ src/FsAutoComplete.Core/Utils.fsi | 3 + .../CodeFixes/RemoveUnnecessaryParentheses.fs | 116 +++++++++++++----- .../CodeFixTests/Tests.fs | 63 +++++++++- 4 files changed, 163 insertions(+), 32 deletions(-) diff --git a/src/FsAutoComplete.Core/Utils.fs b/src/FsAutoComplete.Core/Utils.fs index 70346bcc7..3d4065c6e 100644 --- a/src/FsAutoComplete.Core/Utils.fs +++ b/src/FsAutoComplete.Core/Utils.fs @@ -517,6 +517,19 @@ type ReadOnlySpanExtensions = if found then i else -1 + [] + static member IndexOfAnyExcept(span: ReadOnlySpan, values: ReadOnlySpan) = + 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 + [] static member LastIndexOfAnyExcept(span: ReadOnlySpan, value0: char, value1: char) = let mutable i = span.Length - 1 diff --git a/src/FsAutoComplete.Core/Utils.fsi b/src/FsAutoComplete.Core/Utils.fsi index 2d2bd94bc..c2b106c2c 100644 --- a/src/FsAutoComplete.Core/Utils.fsi +++ b/src/FsAutoComplete.Core/Utils.fsi @@ -185,6 +185,9 @@ type ReadOnlySpanExtensions = [] static member IndexOfAnyExcept: span: ReadOnlySpan * value0: char * value1: char -> int + [] + static member IndexOfAnyExcept: span: ReadOnlySpan * values: ReadOnlySpan -> int + [] static member LastIndexOfAnyExcept: span: ReadOnlySpan * value0: char * value1: char -> int #endif diff --git a/src/FsAutoComplete/CodeFixes/RemoveUnnecessaryParentheses.fs b/src/FsAutoComplete/CodeFixes/RemoveUnnecessaryParentheses.fs index fbc5f97ab..318ba0367 100644 --- a/src/FsAutoComplete/CodeFixes/RemoveUnnecessaryParentheses.fs +++ b/src/FsAutoComplete/CodeFixes/RemoveUnnecessaryParentheses.fs @@ -7,6 +7,7 @@ open FsAutoComplete.CodeFix.Types open FsToolkit.ErrorHandling open FsAutoComplete open FsAutoComplete.LspHelpers +open FSharp.Compiler.Text let title = "Remove unnecessary parentheses" @@ -14,6 +15,27 @@ let title = "Remove unnecessary parentheses" module private Patterns = let inline toPat f x = if f x then ValueSome() else ValueNone + /// Starts with //. + [] + let (|StartsWithSingleLineComment|_|) (s: string) = + if s.AsSpan().TrimStart(' ').StartsWith("//".AsSpan()) then + ValueSome StartsWithSingleLineComment + else + ValueNone + + /// Starts with match, e.g., + /// + /// (match … with + /// | … -> …) + [] + let (|StartsWithMatch|_|) (s: string) = + let s = s.AsSpan().TrimStart ' ' + + if s.StartsWith("match".AsSpan()) && (s.Length = 5 || s[5] = ' ') then + ValueSome StartsWithMatch + else + ValueNone + [] module Char = [] @@ -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 @@ -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 @@ -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) = // (……)… @@ -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 [] }) diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs index 33b18e362..5f3acbc98 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs @@ -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)$0+"" + """ + (Diagnostics.expectCode "FSAC0004") + selector + + ]) let tests textFactory state = testSequenced <|