Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions src/Compiler/Service/FSharpParseFileResults.fs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,21 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput,
let result = SyntaxTraversal.Traverse(pos, input, visitor)
result.IsSome

member _.IsTypeName(range: range) =
let visitor =
{ new SyntaxVisitorBase<_>() with
member _.VisitModuleDecl(_, _, synModuleDecl) =
match synModuleDecl with
| SynModuleDecl.Types (typeDefns, _) ->
typeDefns
|> Seq.exists (fun (SynTypeDefn (typeInfo, _, _, _, _, _)) -> typeInfo.Range = range)
|> Some
| _ -> None
}

let result = SyntaxTraversal.Traverse(range.Start, input, visitor)
result |> Option.contains true

member _.TryRangeOfFunctionOrMethodBeingApplied pos =
let rec getIdentRangeForFuncExprInApp traverseSynExpr expr pos =
match expr with
Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Service/FSharpParseFileResults.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type public FSharpParseFileResults =
/// Determines if the given position is inside a function or method application.
member IsPosContainedInApplication: pos: pos -> bool

/// Determines if the range points to a type name in the type definition.
member IsTypeName: range: range -> bool

/// Attempts to find the range of a function or method that is being applied. Also accounts for functions in pipelines.
member TryRangeOfFunctionOrMethodBeingApplied: pos: pos -> range option

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2080,6 +2080,7 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Void InvalidateConfiguration(FSharp.
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsBindingALambdaAtPosition(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsPosContainedInApplication(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsPositionContainedInACurriedParameter(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsTypeName(FSharp.Compiler.Text.Range)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsTypeAnnotationGivenAtPosition(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean ParseHadErrors
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean get_ParseHadErrors()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2080,6 +2080,7 @@ FSharp.Compiler.CodeAnalysis.FSharpChecker: Void InvalidateConfiguration(FSharp.
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsBindingALambdaAtPosition(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsPosContainedInApplication(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsPositionContainedInACurriedParameter(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsTypeName(FSharp.Compiler.Text.Range)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean IsTypeAnnotationGivenAtPosition(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean ParseHadErrors
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Boolean get_ParseHadErrors()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,40 @@

namespace Microsoft.VisualStudio.FSharp.Editor

open System
open System.Composition
open System.Threading.Tasks
open System.Collections.Immutable

open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes

open CancellableTasks

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.AddMissingEqualsToTypeDefinition); Shared>]
type internal AddMissingEqualsToTypeDefinitionCodeFixProvider() =
inherit CodeFixProvider()

static let title = SR.AddMissingEqualsToTypeDefinition()
override _.FixableDiagnosticIds = ImmutableArray.Create("FS3360")

override _.RegisterCodeFixesAsync context : Task =
asyncMaybe {

let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
override _.FixableDiagnosticIds = ImmutableArray.Create "FS3360"

let mutable pos = context.Span.Start - 1
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this

// This won't ever actually happen, but eh why not
do! Option.guard (pos > 0)
interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! range = context.GetErrorRangeAsync()

let mutable ch = sourceText.[pos]
let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof AddMissingEqualsToTypeDefinitionCodeFixProvider)

while pos > 0 && Char.IsWhiteSpace(ch) do
pos <- pos - 1
ch <- sourceText.[pos]
if parseResults.IsTypeName range then
return None

do
context.RegisterFsharpFix(
CodeFix.AddMissingEqualsToTypeDefinition,
title,
// 'pos + 1' is here because 'pos' is now the position of the first non-whitespace character.
// Using just 'pos' will creat uncompilable code.
[| TextChange(TextSpan(pos + 1, 0), " =") |]
)
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
else
return
Some
{
Name = CodeFix.AddMissingEqualsToTypeDefinition
Message = title
Changes = [ TextChange(TextSpan(context.Span.Start, 0), "= ") ]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,45 @@

namespace Microsoft.VisualStudio.FSharp.Editor

open System
open System.Composition
open System.Threading.Tasks
open System.Collections.Immutable
open System.Text.RegularExpressions

open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes

open CancellableTasks

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.ChangePrefixNegationToInfixSubtraction); Shared>]
type internal ChangePrefixNegationToInfixSubtractionodeFixProvider() =
type internal ChangePrefixNegationToInfixSubtractionCodeFixProvider() =
inherit CodeFixProvider()

static let title = SR.ChangePrefixNegationToInfixSubtraction()

override _.FixableDiagnosticIds = ImmutableArray.Create("FS0003")

override _.RegisterCodeFixesAsync context : Task =
asyncMaybe {
let! sourceText = context.Document.GetTextAsync(context.CancellationToken)

let mutable pos = context.Span.End + 1

// This won't ever actually happen, but eh why not
do! Option.guard (pos < sourceText.Length)

let mutable ch = sourceText.[pos]

while pos < sourceText.Length && Char.IsWhiteSpace(ch) do
pos <- pos + 1
ch <- sourceText.[pos]

// Bail if this isn't a negation
do! Option.guard (ch = '-')
do context.RegisterFsharpFix(CodeFix.ChangePrefixNegationToInfixSubtraction, title, [| TextChange(TextSpan(pos, 1), "- ") |])
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0003"

override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! sourceText = context.GetSourceTextAsync()

// in a line like "... x -1 ...",
// squiggly goes for "x", not for "-", hence we search for "-"
let remainingText = $"{sourceText.GetSubText(context.Span.End)}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$"{}" should never be a replacement for .ToString() in such case.
It's both worse performance wise as well as worse for readability (reader has to stop and figure out why the hell is string interpolation needed here).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correction, even worse I guess - the previous code was carefully reading one character at a time from the Roslyn's SourceText.
The new code allocates for the full substring from position till end of the document, even though the algorithm itself only cares about immediate whitespaces after the position.

Copy link
Contributor Author

@psfinaki psfinaki Jul 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability is debatable, performance - I guess not. I will fix this in the followup soon!

let pattern = @"^\s+(-)"

match Regex.Match(remainingText, pattern) with
| m when m.Success ->
let spacePlace = context.Span.End + m.Groups[1].Index + 1

return
Some
{
Name = CodeFix.ChangePrefixNegationToInfixSubtraction
Message = title
Changes = [ TextChange(TextSpan(spacePlace, 0), " ") ]
}
| _ -> return None
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,38 @@
namespace Microsoft.VisualStudio.FSharp.Editor

open System.Composition
open System.Threading.Tasks
open System.Collections.Immutable

open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes

open CancellableTasks

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.ChangeRefCellDerefToNotExpression); Shared>]
type internal ChangeRefCellDerefToNotExpressionCodeFixProvider [<ImportingConstructor>] () =
inherit CodeFixProvider()

static let title = SR.UseNotForNegation()

override _.FixableDiagnosticIds = ImmutableArray.Create("FS0001")

override this.RegisterCodeFixesAsync context : Task =
asyncMaybe {
let document = context.Document

let! parseResults =
document.GetFSharpParseResultsAsync(nameof (ChangeRefCellDerefToNotExpressionCodeFixProvider))
|> liftAsync
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0001"

let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this

let errorRange =
RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText)
interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof ChangeRefCellDerefToNotExpressionCodeFixProvider)

let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start
let! derefSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, derefRange)
let! sourceText = context.GetSourceTextAsync()
let! errorRange = context.GetErrorRangeAsync()

do context.RegisterFsharpFix(CodeFix.ChangeRefCellDerefToNotExpression, title, [| TextChange(derefSpan, "not ") |])
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
return
parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start
|> Option.bind (fun range -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range))
|> Option.map (fun span ->
{
Name = CodeFix.ChangeRefCellDerefToNotExpression
Message = title
Changes = [ TextChange(span, "not ") ]
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,36 @@
namespace Microsoft.VisualStudio.FSharp.Editor

open System.Composition
open System.Threading.Tasks
open System.Collections.Immutable

open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes

open CancellableTasks

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.ConvertToNotEqualsEqualityExpression); Shared>]
type internal ConvertToNotEqualsEqualityExpressionCodeFixProvider() =
inherit CodeFixProvider()

static let title = SR.ConvertToNotEqualsEqualityExpression()

override _.FixableDiagnosticIds = ImmutableArray.Create("FS0043")

override this.RegisterCodeFixesAsync context : Task =
asyncMaybe {
let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
let text = sourceText.GetSubText(context.Span).ToString()

// We're converting '!=' into '<>', a common new user mistake.
// If this is an FS00043 that is anything other than that, bail out
do! Option.guard (text = "!=")
do context.RegisterFsharpFix(CodeFix.ConvertToNotEqualsEqualityExpression, title, [| TextChange(context.Span, "<>") |])
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0043"

override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! text = context.GetSquigglyTextAsync()

if text <> "!=" then
return None
else
return
Some
{
Name = CodeFix.ConvertToNotEqualsEqualityExpression
Message = title
Changes = [ TextChange(context.Span, "<>") ]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,36 @@
namespace Microsoft.VisualStudio.FSharp.Editor

open System.Composition
open System.Threading.Tasks
open System.Collections.Immutable

open Microsoft.CodeAnalysis.Text
open Microsoft.CodeAnalysis.CodeFixes

open CancellableTasks

[<ExportCodeFixProvider(FSharpConstants.FSharpLanguageName, Name = CodeFix.ConvertToSingleEqualsEqualityExpression); Shared>]
type internal ConvertToSingleEqualsEqualityExpressionCodeFixProvider() =
inherit CodeFixProvider()

static let title = SR.ConvertToSingleEqualsEqualityExpression()

override _.FixableDiagnosticIds = ImmutableArray.Create("FS0043")

override this.RegisterCodeFixesAsync context : Task =
asyncMaybe {
let! sourceText = context.Document.GetTextAsync(context.CancellationToken)
let text = sourceText.GetSubText(context.Span).ToString()

// We're converting '==' into '=', a common new user mistake.
// If this is an FS00043 that is anything other than that, bail out
do! Option.guard (text = "==")
do context.RegisterFsharpFix(CodeFix.ConvertToSingleEqualsEqualityExpression, title, [| TextChange(context.Span, "=") |])
}
|> Async.Ignore
|> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)
override _.FixableDiagnosticIds = ImmutableArray.Create "FS0043"

override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this

interface IFSharpCodeFixProvider with
member _.GetCodeFixIfAppliesAsync context =
cancellableTask {
let! text = context.GetSquigglyTextAsync()

if text <> "==" then
return None
else
return
Some
{
Name = CodeFix.ConvertToSingleEqualsEqualityExpression
Message = title
Changes = [ TextChange(context.Span, "=") ]
}
}
Loading