Skip to content
Draft
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
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="NUnit" Version="3.14.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="5.0.0" />
<PackageVersion Include="Sarif.Sdk" Version="4.5.4" />
<PackageVersion Include="System.Reactive" Version="6.0.1" />
<PackageVersion Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions src/FSharpLint.Console/FSharpLint.Console.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@

<ItemGroup>
<Compile Include="Output.fs" />
<Compile Include="Sarif.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Argu" />
<PackageReference Include="FSharp.Core" />
<PackageReference Include="Sarif.Sdk" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 11 additions & 0 deletions src/FSharpLint.Console/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,17 @@ type internal FileType =
type private ToolArgs =
| [<AltCommandLine("-f")>] Format of OutputFormat
| [<CliPrefix(CliPrefix.None)>] Lint of ParseResults<LintArgs>
| [<Unique>] Report of string
| [<Unique>] Code_Root of string
| Version
with
interface IArgParserTemplate with
member this.Usage =
match this with
| Format _ -> "Output format of the linter."
| Lint _ -> "Runs FSharpLint against a file or a collection of files."
| Report _ -> "Write the result messages to a (sarif) report file."
| Code_Root _ -> "Root of the current code repository, used in the sarif report to construct the relative file path. The current working directory is used by default."
| Version -> "Prints current version."

// TODO: investigate erroneous warning on this type definition
Expand Down Expand Up @@ -91,13 +95,20 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
output.WriteError str
exitCode <- -1

let reportPath = arguments.TryGetResult Report
let codeRoot = arguments.TryGetResult Code_Root

match arguments.GetSubCommand() with
| Lint lintArgs ->

let handleLintResult = function
| LintResult.Success(warnings) ->
String.Format(Resources.GetString("ConsoleFinished"), List.length warnings)
|> output.WriteInfo

reportPath
|> Option.iter (fun report -> Sarif.writeReport warnings codeRoot report output)

if not (List.isEmpty warnings) then exitCode <- -1
| LintResult.Failure(failure) ->
handleError failure.Description
Expand Down
110 changes: 110 additions & 0 deletions src/FSharpLint.Console/Sarif.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
module internal Sarif

open System
open System.IO
open Microsoft.CodeAnalysis.Sarif
open Microsoft.CodeAnalysis.Sarif.Writers
open FSharpLint.Framework
open FSharpLint.Console.Output

let writeReport (results: Suggestion.LintWarning list) (codeRoot: string option) (report: string) (logger: IOutput) =
try
let codeRoot =
match codeRoot with
| None -> Directory.GetCurrentDirectory() |> Uri
| Some root -> Path.GetFullPath root |> Uri

// Construct full path to ensure path separators are normalized.
let report = Path.GetFullPath report
// Ensure the parent directory exists
let reportFile = FileInfo(report)
reportFile.Directory.Create()

let driver =
ToolComponent(
Name = "FSharpLint.Console",
InformationUri = Uri("https://fsprojects.github.io/FSharpLint/"),
Version = string<Version> (System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)
)

let tool = Tool(Driver = driver)
let run = Run(Tool = tool)

use sarifLogger =
new SarifLogger(
report,
logFilePersistenceOptions =
(FilePersistenceOptions.PrettyPrint ||| FilePersistenceOptions.ForceOverwrite),
run = run,
levels = BaseLogger.ErrorWarningNote,
kinds = BaseLogger.Fail,
closeWriterOnDispose = true
)

sarifLogger.AnalysisStarted()

for analyzerResult in results do
let helpUri = $"https://fsprojects.github.io/FSharpLint/how-tos/rules/%s{analyzerResult.RuleIdentifier}.html"

let reportDescriptor =
ReportingDescriptor(
Id = analyzerResult.RuleIdentifier,
HelpUri = Uri(helpUri),
Name = analyzerResult.RuleName
)

(*
analyzerResult.ShortDescription
|> Option.iter (fun shortDescription ->
reportDescriptor.ShortDescription <-
MultiformatMessageString(shortDescription, shortDescription, dict [])
)
*)

(*
result.Level <-
match analyzerResult.Message.Severity with
| Severity.Info -> FailureLevel.Note
| Severity.Hint -> FailureLevel.Note
| Severity.Warning -> FailureLevel.Warning
| Severity.Error -> FailureLevel.Error
*)

let msg = Message(Text = analyzerResult.Details.Message)

let artifactLocation =
ArtifactLocation(
Uri = codeRoot.MakeRelativeUri(Uri(analyzerResult.Details.Range.FileName))
)

let region =
Region(
StartLine = analyzerResult.Details.Range.StartLine,
StartColumn = analyzerResult.Details.Range.StartColumn + 1,
EndLine = analyzerResult.Details.Range.EndLine,
EndColumn = analyzerResult.Details.Range.EndColumn + 1
)

let physicalLocation =
PhysicalLocation(
ArtifactLocation = artifactLocation,
Region = region
)

let location = Location(PhysicalLocation = physicalLocation)

let result =
Result(
RuleId = reportDescriptor.Id,
Level = FailureLevel.Warning,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

If there aren't different levels for different rules, should the default here be Warning or Note?

Locations = [| location |],
Message = msg
)

sarifLogger.Log(reportDescriptor, result, System.Nullable())

sarifLogger.AnalysisStopped(RuntimeConditions.None)

sarifLogger.Dispose()
with ex ->
logger.WriteError($"Could not write sarif to %s{report}: %s{ex.Message}")
Loading