Skip to content
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

Analyzer & code fix for ~IDE0047~ FS3583: remove unnecessary parentheses #16079

Merged
merged 93 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
f7d4b36
Add analyzer & code fix for IDE0047
brianrourkeboll Oct 4, 2023
aa17921
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 4, 2023
8207085
Consistency
brianrourkeboll Oct 5, 2023
f7403f4
Consolidate lambda branches
brianrourkeboll Oct 5, 2023
9113205
Try-finally tweak
brianrourkeboll Oct 5, 2023
7377e41
Fix
brianrourkeboll Oct 5, 2023
37142b6
Fantomas
brianrourkeboll Oct 5, 2023
f51569a
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 5, 2023
a5aa829
Explain
brianrourkeboll Oct 5, 2023
a31049b
More Fantomas
brianrourkeboll Oct 5, 2023
92bcc2f
Pass in getTextAtRange for № literals
brianrourkeboll Oct 5, 2023
c4528ee
Fantomas
brianrourkeboll Oct 5, 2023
a176f26
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 5, 2023
aa04c18
Structs
brianrourkeboll Oct 6, 2023
f4bcdc7
Don't need that
brianrourkeboll Oct 6, 2023
34f037b
Spaces
brianrourkeboll Oct 6, 2023
5a6c260
getTextAtRange → getSourceLineStr
brianrourkeboll Oct 8, 2023
9a1f313
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 8, 2023
dea8ffc
Singleton
brianrourkeboll Oct 8, 2023
9bd1097
brianrourkeboll Oct 8, 2023
48d172b
Better
brianrourkeboll Oct 8, 2023
919dc4d
Streamline
brianrourkeboll Oct 9, 2023
7f71794
One more
brianrourkeboll Oct 9, 2023
591c5df
Don't need that here
brianrourkeboll Oct 9, 2023
f06cdd5
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 16, 2023
8e493e1
Simplify lambda traversal
brianrourkeboll Oct 16, 2023
5059bf2
Add comment
brianrourkeboll Oct 16, 2023
e5c281d
Make private
brianrourkeboll Oct 16, 2023
fb951ab
Fix infix op tests
brianrourkeboll Oct 16, 2023
a82be7c
Add some more tests
brianrourkeboll Oct 16, 2023
178bdc5
Be (somewhat) more systematic about precedence, &c.
brianrourkeboll Oct 16, 2023
4da7680
Handle dangling nested exprs (`match`, `if`, &c.)
brianrourkeboll Oct 17, 2023
9d10c9d
Consolidate SynPat logic
brianrourkeboll Oct 17, 2023
bf7c62a
Comments
brianrourkeboll Oct 17, 2023
bbf91df
Space after backticks
brianrourkeboll Oct 17, 2023
6decef7
Parens in interp hole
brianrourkeboll Oct 17, 2023
cbdd095
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 17, 2023
b30c503
Submodule
brianrourkeboll Oct 17, 2023
02edd60
Move extension method to common module
brianrourkeboll Oct 19, 2023
ed7aa1e
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 19, 2023
19da092
Expose raverseAll internally for clarity
brianrourkeboll Oct 19, 2023
1271c6e
Remove redundant arrow
brianrourkeboll Oct 19, 2023
e30168b
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 19, 2023
a2c9fe2
Move diagnostic creation logic into its own file
brianrourkeboll Oct 19, 2023
58d08d1
Fantomas
brianrourkeboll Oct 19, 2023
a8e44ca
Remove comment
brianrourkeboll Oct 19, 2023
5a62e22
Apply Fantomas to tests
brianrourkeboll Oct 19, 2023
2181148
Include paren diagnostics in builder capacity
brianrourkeboll Oct 19, 2023
2df60d9
Update surface area
brianrourkeboll Oct 19, 2023
a906b35
Remove unnecessary parens in DocumentDiagnosticAnalyzer tests
brianrourkeboll Oct 19, 2023
e1d8a7b
Add in-memory cache like SimplifyNameDiagnosticAnalyzer's
brianrourkeboll Oct 20, 2023
5f542cb
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 20, 2023
caeceb8
Update misleading comment
brianrourkeboll Oct 21, 2023
8a62a50
Ish
brianrourkeboll Oct 21, 2023
cc2c0fb
Might as well include the other example
brianrourkeboll Oct 21, 2023
daf39fa
The prefix op rules are tricky…
brianrourkeboll Oct 21, 2023
caccbf9
Use existing helper method
brianrourkeboll Oct 23, 2023
a9ec3f1
More correct dangling expr logic
brianrourkeboll Oct 23, 2023
1eeaa5d
A few more tests for dangling exprs
brianrourkeboll Oct 23, 2023
b758378
Fix incomplete exprs
brianrourkeboll Oct 23, 2023
dec772c
Missed that
brianrourkeboll Oct 23, 2023
43605d0
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 23, 2023
d2f1666
Structness is immaterial here
brianrourkeboll Oct 23, 2023
4494ccd
Fantomas
brianrourkeboll Oct 23, 2023
addc8d6
Remove semi-misleading word
brianrourkeboll Oct 24, 2023
a7cca0f
Add example to doc comment
brianrourkeboll Oct 24, 2023
c96ec3a
Move ROS extensions to separate file in FCS
brianrourkeboll Oct 24, 2023
dab4696
Use FS3583 instead of IDE0047
brianrourkeboll Oct 24, 2023
a57de4f
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 24, 2023
9958126
Add example to comment
brianrourkeboll Oct 24, 2023
ba48167
Fix find/replace in comments
brianrourkeboll Oct 25, 2023
73a55b7
Do the faster check first
brianrourkeboll Oct 25, 2023
80067ea
Pretty sure we don't actually need that
brianrourkeboll Oct 25, 2023
a7e969f
Fix (nonfunctional) copy/paste bug
brianrourkeboll Oct 25, 2023
31440be
Add tests for unmatched parens
brianrourkeboll Oct 25, 2023
3710278
Consolidate affixed infix tests for speed
brianrourkeboll Oct 25, 2023
eb15dd0
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 25, 2023
b077e09
Fantomas
brianrourkeboll Oct 25, 2023
1d6db65
Address a few more corner cases
brianrourkeboll Oct 26, 2023
a2b70f9
Add basic ServiceAnalysis tests
brianrourkeboll Oct 26, 2023
3800ee2
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 26, 2023
cc04486
Remove redundant assertions
brianrourkeboll Oct 29, 2023
dfdcab3
Move some framework-y code to the framework file
brianrourkeboll Oct 29, 2023
13c2840
Remove incorrect test
brianrourkeboll Oct 29, 2023
953b580
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 29, 2023
d49b21e
Don't need
brianrourkeboll Oct 30, 2023
53b80ad
A few more tests
brianrourkeboll Oct 30, 2023
e622dd2
Add a few attribute-related tests
brianrourkeboll Oct 30, 2023
deb313f
Add test for new operator introduced in #15923
brianrourkeboll Oct 30, 2023
7b4a347
Vars instead of consts
brianrourkeboll Oct 30, 2023
518af84
Merge branch 'main' of https://github.com/dotnet/fsharp into parens
brianrourkeboll Oct 30, 2023
19637f3
Add a few clarifying comments
brianrourkeboll Oct 30, 2023
4025df7
Update tests/FSharp.Compiler.Service.Tests/UnnecessaryParenthesesTest…
psfinaki Oct 31, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,066 changes: 1,066 additions & 0 deletions src/Compiler/Service/ServiceAnalysis.fs

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions src/Compiler/Service/ServiceAnalysis.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace FSharp.Compiler.EditorServices

open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Syntax
open FSharp.Compiler.Text

module public UnusedOpens =
Expand Down Expand Up @@ -31,3 +32,12 @@ module public UnusedDeclarations =

/// Get all unused declarations in a file
val getUnusedDeclarations: checkFileResults: FSharpCheckFileResults * isScriptFile: bool -> Async<seq<range>>

module public UnnecessaryParentheses =

/// Gets the ranges of all unnecessary pairs of parentheses in a file.
///
/// Note that this may include pairs of nested ranges each of whose
/// lack of necessity depends on the other's presence, such
/// that it is valid to remove either set of parentheses but not both.
psfinaki marked this conversation as resolved.
Show resolved Hide resolved
val getUnnecessaryParentheses: getSourceLineStr: (int -> string) -> parsedInput: ParsedInput -> Async<range seq>
34 changes: 27 additions & 7 deletions src/Compiler/Service/ServiceParseTreeWalk.fs
psfinaki marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,15 @@ module SyntaxTraversal =
ignore debugObj
None

/// traverse an implementation file walking all the way down to SynExpr or TypeAbbrev at a particular location
///
let Traverse (pos: pos, parseTree, visitor: SyntaxVisitorBase<'T>) =
/// <summary>
/// Traverse an implementation file until <paramref name="pick"/> returns <c>Some value</c>.
/// </summary>
let traverseUntil
(pick: pos -> range -> obj -> (range * (unit -> 'T option)) list -> 'T option)
(pos: pos)
(visitor: SyntaxVisitorBase<'T>)
(parseTree: ParsedInput)
: 'T option =
let pick x = pick pos x

let rec traverseSynModuleDecl origPath (decl: SynModuleDecl) =
Expand Down Expand Up @@ -575,10 +581,17 @@ module SyntaxTraversal =

if ok.IsSome then ok else traverseSynExpr synExpr

| SynExpr.Lambda (args = SynSimplePats.SimplePats (pats = pats); body = synExpr) ->
match traverseSynSimplePats path pats with
| None -> traverseSynExpr synExpr
| x -> x
| SynExpr.Lambda (parsedData = parsedData) ->
[
match parsedData with
| Some (pats, body) ->
for pat in pats do
yield dive pat pat.Range traversePat

yield dive body body.Range traverseSynExpr
| None -> ()
]
|> pick expr

| SynExpr.MatchLambda (matchClauses = synMatchClauseList) ->
synMatchClauseList
Expand Down Expand Up @@ -719,6 +732,7 @@ module SyntaxTraversal =
| SynPat.Ands (ps, _)
| SynPat.Tuple (elementPats = ps)
| SynPat.ArrayOrList (_, ps, _) -> ps |> List.tryPick (traversePat path)
| SynPat.Record (fieldPats = fieldPats) -> fieldPats |> List.tryPick (fun (_, _, p) -> traversePat path p)
| SynPat.Attrib (p, attributes, m) ->
match traversePat path p with
| None -> attributeApplicationDives path attributes |> pick m attributes
Expand All @@ -731,6 +745,7 @@ module SyntaxTraversal =
match traversePat path p with
| None -> traverseSynType path ty
| x -> x
| SynPat.QuoteExpr (expr, _) -> traverseSynExpr path expr
| _ -> None

visitor.VisitPat(origPath, defaultTraverse, pat)
Expand Down Expand Up @@ -1077,3 +1092,8 @@ module SyntaxTraversal =
l
|> List.map (fun x -> dive x x.Range (traverseSynModuleOrNamespaceSig []))
|> pick fileRange l

/// traverse an implementation file walking all the way down to SynExpr or TypeAbbrev at a particular location
///
let Traverse (pos: pos, parseTree, visitor: SyntaxVisitorBase<'T>) =
traverseUntil pick pos visitor parseTree
7 changes: 7 additions & 0 deletions src/Compiler/Service/ServiceParseTreeWalk.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,11 @@ module public SyntaxTraversal =
val internal pick:
pos: pos -> outerRange: range -> debugObj: obj -> diveResults: (range * (unit -> 'a option)) list -> 'a option

val internal traverseUntil:
pick: (pos -> range -> obj -> (range * (unit -> 'T option)) list -> 'T option) ->
pos: pos ->
visitor: SyntaxVisitorBase<'T> ->
parseTree: ParsedInput ->
'T option

val Traverse: pos: pos * parseTree: ParsedInput * visitor: SyntaxVisitorBase<'T> -> 'T option
17 changes: 12 additions & 5 deletions vsintegration/src/FSharp.Editor/CodeFixes/CodeFixHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ module internal CodeFixExtensions =

[<AutoOpen>]
module IFSharpCodeFixProviderExtensions =
type IFSharpCodeFixProvider with
// Cache this no-op delegate.
brianrourkeboll marked this conversation as resolved.
Show resolved Hide resolved
let private registerCodeFix =
Action<CodeActions.CodeAction, ImmutableArray<Diagnostic>>(fun _ _ -> ())

// this is not used anywhere, it's just needed to create the context
static member private Action =
Action<CodeActions.CodeAction, ImmutableArray<Diagnostic>>(fun _ _ -> ())
type IFSharpCodeFixProvider with

member private provider.FixAllAsync (fixAllCtx: FixAllContext) (doc: Document) (allDiagnostics: ImmutableArray<Diagnostic>) =
cancellableTask {
Expand All @@ -163,7 +163,7 @@ module IFSharpCodeFixProviderExtensions =
// a proper fix is needed.
|> Seq.distinctBy (fun d -> d.Id, d.Location)
|> Seq.map (fun diag ->
let context = CodeFixContext(doc, diag, IFSharpCodeFixProvider.Action, token)
let context = CodeFixContext(doc, diag, registerCodeFix, token)
provider.GetCodeFixIfAppliesAsync context)
|> CancellableTask.whenAll

Expand Down Expand Up @@ -194,3 +194,10 @@ module IFSharpCodeFixProviderExtensions =
FixAllProvider.Create(fun fixAllCtx doc allDiagnostics ->
provider.FixAllAsync fixAllCtx doc allDiagnostics
|> CancellableTask.start fixAllCtx.CancellationToken)

member provider.RegisterFsharpFixAll filter =
brianrourkeboll marked this conversation as resolved.
Show resolved Hide resolved
FixAllProvider.Create(fun fixAllCtx doc allDiagnostics ->
let filteredDiagnostics = filter allDiagnostics

provider.FixAllAsync fixAllCtx doc filteredDiagnostics
|> CancellableTask.start fixAllCtx.CancellationToken)
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// 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 private 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
#endif

[<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 =
/// Returns true if the given span contains an expression
/// whose indentation would be made invalid if the open paren
/// were removed (because the offside line would be shifted), e.g.,
///
/// // Valid.
/// (let x = 2
/// x)
///
/// // Invalid.
/// ←let x = 2
/// x◌
///
/// // Valid.
/// ◌let x = 2
/// x◌
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.
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.
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
| _ when SourceText.containsSensitiveIndentation context.Span sourceText -> Some ShouldPutSpaceBefore
| _ -> 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
}
3 changes: 3 additions & 0 deletions vsintegration/src/FSharp.Editor/Common/Constants.fs
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,6 @@ module internal CodeFix =

[<Literal>]
let RemoveSuperfluousCapture = "RemoveSuperfluousCapture"

[<Literal>]
let RemoveUnnecessaryParentheses = "RemoveUnnecessaryParentheses"
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics

open FSharp.Compiler.Diagnostics
open FSharp.Compiler.EditorServices
open CancellableTasks
open Microsoft.VisualStudio.FSharp.Editor.Telemetry

Expand Down Expand Up @@ -108,7 +109,40 @@ type internal FSharpDocumentDiagnosticAnalyzer [<ImportingConstructor>] () =
for diagnostic in checkResults.Diagnostics do
errors.Add(diagnostic) |> ignore

if errors.Count = 0 then
let! unnecessaryParentheses =
match diagnosticType with
| DiagnosticsType.Semantic -> CancellableTask.singleton ImmutableArray.Empty
| DiagnosticsType.Syntax ->
cancellableTask {
let fsharpSourceText = sourceText.ToFSharpSourceText()

let! unnecessaryParentheses =
UnnecessaryParentheses.getUnnecessaryParentheses
(FSharp.Compiler.Text.Line.toZ >> fsharpSourceText.GetLineString)
parseResults.ParseTree

let descriptor =
let title = "Parentheses can be removed."

DiagnosticDescriptor(
"IDE0047",
title,
title,
"Style",
DiagnosticSeverity.Hidden,
isEnabledByDefault = true,
description = null,
helpLinkUri = null
)

return
unnecessaryParentheses
|> Seq.map (fun range ->
Diagnostic.Create(descriptor, RoslynHelpers.RangeToLocation(range, sourceText, document.FilePath)))
|> Seq.toImmutableArray
}

if errors.Count = 0 && unnecessaryParentheses.IsEmpty then
return ImmutableArray.Empty
else
let iab = ImmutableArray.CreateBuilder(errors.Count)
Expand All @@ -135,6 +169,7 @@ type internal FSharpDocumentDiagnosticAnalyzer [<ImportingConstructor>] () =
let location = Location.Create(filePath, correctedTextSpan, linePositionSpan)
iab.Add(RoslynHelpers.ConvertError(diagnostic, location))

iab.AddRange unnecessaryParentheses
return iab.ToImmutable()
}

Expand Down
Loading
Loading