-
Notifications
You must be signed in to change notification settings - Fork 833
Analyzer & code fix for ~IDE0047~ FS3583: remove unnecessary parentheses #16079
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 20 commits
Commits
Show all changes
93 commits
Select commit
Hold shift + click to select a range
f7d4b36
Add analyzer & code fix for IDE0047
brianrourkeboll aa17921
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll 8207085
Consistency
brianrourkeboll f7403f4
Consolidate lambda branches
brianrourkeboll 9113205
Try-finally tweak
brianrourkeboll 7377e41
Fix
brianrourkeboll 37142b6
Fantomas
brianrourkeboll f51569a
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll a5aa829
Explain
brianrourkeboll a31049b
More Fantomas
brianrourkeboll 92bcc2f
Pass in getTextAtRange for № literals
brianrourkeboll c4528ee
Fantomas
brianrourkeboll a176f26
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll aa04c18
Structs
brianrourkeboll f4bcdc7
Don't need that
brianrourkeboll 34f037b
Spaces
brianrourkeboll 5a6c260
getTextAtRange → getSourceLineStr
brianrourkeboll 9a1f313
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll dea8ffc
Singleton
brianrourkeboll 9bd1097
↓
brianrourkeboll 48d172b
Better
brianrourkeboll 919dc4d
Streamline
brianrourkeboll 7f71794
One more
brianrourkeboll 591c5df
Don't need that here
brianrourkeboll f06cdd5
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll 8e493e1
Simplify lambda traversal
brianrourkeboll 5059bf2
Add comment
brianrourkeboll e5c281d
Make private
brianrourkeboll fb951ab
Fix infix op tests
brianrourkeboll a82be7c
Add some more tests
brianrourkeboll 178bdc5
Be (somewhat) more systematic about precedence, &c.
brianrourkeboll 4da7680
Handle dangling nested exprs (`match`, `if`, &c.)
brianrourkeboll 9d10c9d
Consolidate SynPat logic
brianrourkeboll bf7c62a
Comments
brianrourkeboll bbf91df
Space after backticks
brianrourkeboll 6decef7
Parens in interp hole
brianrourkeboll cbdd095
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll b30c503
Submodule
brianrourkeboll 02edd60
Move extension method to common module
brianrourkeboll ed7aa1e
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll 19da092
Expose raverseAll internally for clarity
brianrourkeboll 1271c6e
Remove redundant arrow
brianrourkeboll e30168b
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll a2c9fe2
Move diagnostic creation logic into its own file
brianrourkeboll 58d08d1
Fantomas
brianrourkeboll a8e44ca
Remove comment
brianrourkeboll 5a62e22
Apply Fantomas to tests
brianrourkeboll 2181148
Include paren diagnostics in builder capacity
brianrourkeboll 2df60d9
Update surface area
brianrourkeboll a906b35
Remove unnecessary parens in DocumentDiagnosticAnalyzer tests
brianrourkeboll e1d8a7b
Add in-memory cache like SimplifyNameDiagnosticAnalyzer's
brianrourkeboll 5f542cb
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll caeceb8
Update misleading comment
brianrourkeboll 8a62a50
Ish
brianrourkeboll cc2c0fb
Might as well include the other example
brianrourkeboll daf39fa
The prefix op rules are tricky…
brianrourkeboll caccbf9
Use existing helper method
brianrourkeboll a9ec3f1
More correct dangling expr logic
brianrourkeboll 1eeaa5d
A few more tests for dangling exprs
brianrourkeboll b758378
Fix incomplete exprs
brianrourkeboll dec772c
Missed that
brianrourkeboll 43605d0
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll d2f1666
Structness is immaterial here
brianrourkeboll 4494ccd
Fantomas
brianrourkeboll addc8d6
Remove semi-misleading word
brianrourkeboll a7cca0f
Add example to doc comment
brianrourkeboll c96ec3a
Move ROS extensions to separate file in FCS
brianrourkeboll dab4696
Use FS3583 instead of IDE0047
brianrourkeboll a57de4f
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll 9958126
Add example to comment
brianrourkeboll ba48167
Fix find/replace in comments
brianrourkeboll 73a55b7
Do the faster check first
brianrourkeboll 80067ea
Pretty sure we don't actually need that
brianrourkeboll a7e969f
Fix (nonfunctional) copy/paste bug
brianrourkeboll 31440be
Add tests for unmatched parens
brianrourkeboll 3710278
Consolidate affixed infix tests for speed
brianrourkeboll eb15dd0
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll b077e09
Fantomas
brianrourkeboll 1d6db65
Address a few more corner cases
brianrourkeboll a2b70f9
Add basic ServiceAnalysis tests
brianrourkeboll 3800ee2
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll cc04486
Remove redundant assertions
brianrourkeboll dfdcab3
Move some framework-y code to the framework file
brianrourkeboll 13c2840
Remove incorrect test
brianrourkeboll 953b580
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll d49b21e
Don't need
brianrourkeboll 53b80ad
A few more tests
brianrourkeboll e622dd2
Add a few attribute-related tests
brianrourkeboll deb313f
Add test for new operator introduced in #15923
brianrourkeboll 7b4a347
Vars instead of consts
brianrourkeboll 518af84
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll 19637f3
Add a few clarifying comments
brianrourkeboll 4025df7
Update tests/FSharp.Compiler.Service.Tests/UnnecessaryParenthesesTest…
psfinaki File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
psfinaki marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
189 changes: 189 additions & 0 deletions
189
vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. | ||
|
|
||
| namespace Microsoft.VisualStudio.FSharp.Editor | ||
|
|
||
| open System | ||
| open System.Collections.Generic | ||
| open System.Collections.Immutable | ||
| open System.Composition | ||
|
|
||
| open Microsoft.CodeAnalysis.CodeFixes | ||
| open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics | ||
| open Microsoft.CodeAnalysis.Text | ||
|
|
||
| open CancellableTasks | ||
|
|
||
| #if !NET7_0_OR_GREATER | ||
| open System.Runtime.CompilerServices | ||
|
|
||
| [<Sealed; AbstractClass; Extension>] | ||
| type ReadOnlySpanExtensions = | ||
| [<Extension>] | ||
| static member IndexOfAnyExcept(span: ReadOnlySpan<char>, value0: char, value1: char) = | ||
| let mutable i = 0 | ||
| let mutable found = false | ||
|
|
||
| while not found && i < span.Length do | ||
| let c = span[i] | ||
|
|
||
| if c <> value0 && c <> value1 then | ||
| found <- true | ||
| else | ||
| i <- i + 1 | ||
|
|
||
| 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 | ||
| #endif | ||
brianrourkeboll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| [<AutoOpen>] | ||
| module private Patterns = | ||
| let inline toPat f x = if f x then ValueSome() else ValueNone | ||
|
|
||
| [<return: Struct>] | ||
| let inline (|LetterOrDigit|_|) c = toPat Char.IsLetterOrDigit c | ||
|
|
||
| [<return: Struct>] | ||
| let inline (|Punctuation|_|) c = toPat Char.IsPunctuation c | ||
|
|
||
| [<return: Struct>] | ||
| let inline (|Symbol|_|) c = toPat Char.IsSymbol c | ||
|
|
||
| module private SourceText = | ||
| let containsSensitiveIndentation (span: TextSpan) (sourceText: SourceText) = | ||
| let startLinePosition = sourceText.Lines.GetLinePosition span.Start | ||
| let endLinePosition = sourceText.Lines.GetLinePosition span.End | ||
| let startLine = startLinePosition.Line | ||
| let startCol = startLinePosition.Character | ||
| let endLine = endLinePosition.Line | ||
|
|
||
| if startLine = endLine then | ||
psfinaki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| false | ||
| else | ||
| let rec loop offsides lineNo startCol = | ||
| if lineNo <= endLine then | ||
| let line = sourceText.Lines[ lineNo ].ToString() | ||
|
|
||
| match offsides with | ||
| | ValueNone -> | ||
| let i = line.AsSpan(startCol).IndexOfAnyExcept(' ', ')') | ||
|
|
||
| if i >= 0 then | ||
| loop (ValueSome(i + startCol)) (lineNo + 1) 0 | ||
| else | ||
| loop offsides (lineNo + 1) 0 | ||
|
|
||
| | ValueSome offsidesCol -> | ||
| let i = line.AsSpan(0, min offsidesCol line.Length).IndexOfAnyExcept(' ', ')') | ||
| i <= offsidesCol || loop offsides (lineNo + 1) 0 | ||
| else | ||
| false | ||
|
|
||
| loop ValueNone startLine startCol | ||
|
|
||
| [<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.RemoveUnnecessaryParentheses); Shared; Sealed>] | ||
| type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [<ImportingConstructor>] () = | ||
| inherit CodeFixProvider() | ||
|
|
||
| static let title = SR.RemoveUnnecessaryParentheses() | ||
| static let fixableDiagnosticIds = ImmutableArray.Create "IDE0047" // TODO: FSharpIDEDiagnosticIds.RemoveUnnecessaryParentheses | ||
|
|
||
| /// IDE0047: Remove unnecessary parentheses. | ||
| /// | ||
| /// https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0047-ide0048 | ||
brianrourkeboll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| override _.FixableDiagnosticIds = fixableDiagnosticIds | ||
|
|
||
| override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this | ||
|
|
||
| override this.GetFixAllProvider() = | ||
| this.RegisterFsharpFixAll(fun diagnostics -> | ||
| // There may be pairs of diagnostics with nested spans | ||
psfinaki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // for which it would be valid to apply either but not both. | ||
| let builder = ImmutableArray.CreateBuilder diagnostics.Length | ||
|
|
||
| let spans = | ||
| SortedSet | ||
| { new IComparer<TextSpan> with | ||
| member _.Compare(x, y) = | ||
| if x.IntersectsWith y then 0 else x.CompareTo y | ||
| } | ||
|
|
||
| for i in 0 .. diagnostics.Length - 1 do | ||
| let diagnostic = diagnostics[i] | ||
|
|
||
| if spans.Add diagnostic.Location.SourceSpan then | ||
| builder.Add diagnostic | ||
|
|
||
| builder.ToImmutable()) | ||
|
|
||
| interface IFSharpCodeFixProvider with | ||
| member _.GetCodeFixIfAppliesAsync context = | ||
| assert (context.Span.Length >= 3) // (…) | ||
|
|
||
| cancellableTask { | ||
| let! sourceText = context.Document.GetTextAsync context.CancellationToken | ||
brianrourkeboll marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| let txt = sourceText.ToString(TextSpan(context.Span.Start, context.Span.Length)) | ||
|
|
||
| let firstChar = txt[0] | ||
| let lastChar = txt[txt.Length - 1] | ||
|
|
||
| match firstChar, lastChar with | ||
| | '(', ')' -> | ||
| let (|ShouldPutSpaceBefore|_|) (s: string) = | ||
| // "……(……)" | ||
| // ↑↑ ↑ | ||
| match sourceText[max (context.Span.Start - 2) 0], sourceText[max (context.Span.Start - 1) 0], s[1] with | ||
| | _, _, ('\n' | '\r') -> None | ||
| | _, ('(' | '[' | '{'), _ -> None | ||
| | _, '>', _ -> Some ShouldPutSpaceBefore | ||
| | ' ', '=', _ -> Some ShouldPutSpaceBefore | ||
| | _, '=', ('(' | '[' | '{') -> None | ||
| | _, '=', (Punctuation | Symbol) -> Some ShouldPutSpaceBefore | ||
| | _, LetterOrDigit, '(' -> None | ||
| | _, LetterOrDigit, _ -> Some ShouldPutSpaceBefore | ||
| | _, (Punctuation | Symbol), (Punctuation | Symbol) -> Some ShouldPutSpaceBefore | ||
| | _ -> | ||
| if SourceText.containsSensitiveIndentation context.Span sourceText then | ||
| Some ShouldPutSpaceBefore | ||
| else | ||
| None | ||
|
|
||
| let (|ShouldPutSpaceAfter|_|) (s: string) = | ||
| // "(……)…" | ||
| // ↑ ↑ | ||
| match s[s.Length - 2], sourceText[min context.Span.End (sourceText.Length - 1)] with | ||
| | _, (')' | ']' | '}' | '.' | ';') -> None | ||
| | (Punctuation | Symbol), (Punctuation | Symbol | LetterOrDigit) -> Some ShouldPutSpaceAfter | ||
| | LetterOrDigit, LetterOrDigit -> Some ShouldPutSpaceAfter | ||
| | _ -> None | ||
|
|
||
| let newText = | ||
| match txt with | ||
| | ShouldPutSpaceBefore & ShouldPutSpaceAfter -> " " + txt[1 .. txt.Length - 2] + " " | ||
| | ShouldPutSpaceBefore -> " " + txt[1 .. txt.Length - 2] | ||
| | ShouldPutSpaceAfter -> txt[1 .. txt.Length - 2] + " " | ||
| | _ -> txt[1 .. txt.Length - 2] | ||
|
|
||
| return | ||
| ValueSome | ||
| { | ||
| Name = CodeFix.RemoveUnnecessaryParentheses | ||
| Message = title | ||
| Changes = [ TextChange(context.Span, newText) ] | ||
| } | ||
|
|
||
| | notParens -> | ||
| System.Diagnostics.Debug.Fail $"%A{notParens} <> ('(', ')')" | ||
| return ValueNone | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.