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
10 changes: 9 additions & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@
"version": "20.0.0",
"commands": [
"fsdocs"
]
],
"rollForward": false
},
"fantomas": {
"version": "7.0.3",
"commands": [
"fantomas"
],
"rollForward": false
}
}
}
69 changes: 68 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
@@ -1,14 +1,81 @@
# EditorConfig is awesome:
http://EditorConfig.org

# top-most EditorConfig file
root = true

[*.{fs,fsi,fsx}]
# Default settings:
# A newline ending every file
# Use 4 spaces as indentation
[*]
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
end_of_line = lf

[*.{fs,fsi,fsx,config}]
# https://fsprojects.github.io/fantomas/docs/end-users/Configuration.html
charset = utf-8
trim_trailing_whitespace = true
max_line_length=100
fsharp_keep_max_number_of_blank_lines = 1
fsharp_multi_line_lambda_closing_newline = true
fsharp_bar_before_discriminated_union_declaration = true
fsharp_alternative_long_member_definitions = true
fsharp_align_function_signature_to_indentation = true
fsharp_experimental_keep_indent_in_branch = true
fsharp_bar_before_discriminated_union_declaration = true
fsharp_multiline_bracket_style = aligned
fsharp_max_array_or_list_number_of_items = 1
fsharp_array_or_list_multiline_formatter = number_of_items
fsharp_max_infix_operator_expression = 10




# Visual Studio Solution Files
[*.sln]
indent_style = tab

# XML project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,sfproj}]
indent_size = 2

# XML config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2

# Markdown Files
[*.{md,mdx}]
trim_trailing_whitespace = false

# Bash Files
[*.{sh}]
end_of_line = lf

# Batch Files
[*.{cmd,bat}]
end_of_line = crlf

# Powershell Files
[*.{ps1, psm1}]
end_of_line = crlf

# Paket files
[paket.*]
trim_trailing_whitespace = true
indent_size = 2

[*.paket.references]
trim_trailing_whitespace = true
indent_size = 2


# YAML Files
[*.{yml,yaml}]
indent_size = 2
indent_style = space

[build.fsx]
fsharp_blank_lines_around_nested_multiline_expressions = false
3 changes: 3 additions & 0 deletions .fantomasignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
**/bin/**/*.fs
**/obj/**/*.fs
# Ignore AssemblyInfo files
AssemblyInfo.fs
6 changes: 6 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# This file contains a list of git hashes of revisions to be ignored by git
# These revisions are considered "unimportant" in
# that they are unlikely to be what you are interested in when blaming.
# Like formatting with Fantomas
# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view
# Add formatting commits here
30 changes: 25 additions & 5 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
# Automatically normalize line endings
# Auto detect text files
* text=auto

# Always use lf for F# files
*.fs text eol=lf
*.fsx text eol=lf
*.fsi text eol=lf
# Custom for Visual Studio
*.cs diff=csharp text=auto eol=lf
*.vb diff=csharp text=auto eol=lf
*.fs diff=csharp text=auto eol=lf
*.fsi diff=csharp text=auto eol=lf
*.fsx diff=csharp text=auto eol=lf
*.sln text eol=crlf merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
*.sh text eol=lf

# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
46 changes: 46 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!--
This file allows overriding of properties for all projects in the directory.
See https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-by-directory?view=vs-2022#directorybuildprops-and-directorybuildtargets
-->
<Project>
<ItemGroup>
<!-- Dotnet Watch to know about this file -->
<Watch Include="$(MSBuildThisFileFullPath)"/>
</ItemGroup>

<PropertyGroup>
<_BuildProjBaseIntermediateOutputPath>$(MSBuildThisFileDirectory)build/obj/</_BuildProjBaseIntermediateOutputPath>
<_DotnetToolManifestFile>$(MSBuildThisFileDirectory).config/dotnet-tools.json</_DotnetToolManifestFile>
<_DotnetToolRestoreOutputFile>$(_BuildProjBaseIntermediateOutputPath)/dotnet-tool-restore-$(NETCoreSdkVersion)-$(OS)</_DotnetToolRestoreOutputFile>
<_DotnetFantomasOutputFile>$(BaseIntermediateOutputPath)dotnet-fantomas-msbuild-$(NETCoreSdkVersion)-$(OS)</_DotnetFantomasOutputFile>
</PropertyGroup>

<!-- Make sure that dotnet tools are restored before restoring any project -->
<Target Name="ToolRestore" BeforeTargets="Restore;CollectPackageReferences" Inputs="$(_DotnetToolManifestFile)" Outputs="$(_DotnetToolRestoreOutputFile)">
<Exec Command="dotnet tool restore" WorkingDirectory="$(MSBuildThisFileDirectory)" StandardOutputImportance="High" StandardErrorImportance="High" />
<MakeDir Directories="$(_BuildProjBaseIntermediateOutputPath)"/>
<Touch Files="$(_DotnetToolRestoreOutputFile)" AlwaysCreate="True" ForceTouch="True" />
</Target>

<!-- Make sure that files are formatted before building -->
<Target Name="FormatFSharp" Condition=" '$(MSBuildProjectExtension)' == '.fsproj' AND '$(DesignTimeBuild)' != 'true' " BeforeTargets="BeforeBuild" Inputs="@(Compile)" Outputs="$(_DotnetFantomasOutputFile)" >
<Exec Command="dotnet fantomas $(MSBuildProjectDirectory)" StandardOutputImportance="High" StandardErrorImportance="High" WorkingDirectory="$(MSBuildThisFileDirectory)" ContinueOnError="WarnAndContinue" />
<Touch Files="$(_DotnetFantomasOutputFile)" AlwaysCreate="True" ForceTouch="True" />
</Target>

<!-- Only format once per project -->
<!-- https://learn.microsoft.com/en-us/visualstudio/msbuild/run-target-exactly-once?view=vs-2022 -->
<Target Name="FormatFSharpBeforeOuterBuild"
DependsOnTargets="FormatFSharp"
BeforeTargets="DispatchToInnerBuilds"
/>

<Target Name="FormatFSharpBeforeInnerBuild"
BeforeTargets="BeforeBuild">
<MSBuild Projects="$(MSBuildProjectFullPath)"
Targets="FormatFSharp"
RemoveProperties="TargetFramework" />
</Target>


</Project>
7 changes: 4 additions & 3 deletions build.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ let buildStage =

pipeline "Build" {
restoreStage
// TODO: can uncomment this after .NET SDK 9.0.101 releases
// stage "lint" { run "dotnet fantomas . --check" }
stage "lint" { run "dotnet fantomas . --check" }
stage "build" { run "dotnet build -c Release --no-restore -maxCpuCount" }
stage "test" {
purgeBinLogCache ()
Expand All @@ -31,7 +30,9 @@ pipeline "Build" {
run
"dotnet run --project src/FSharp.Analyzers.Cli/FSharp.Analyzers.Cli.fsproj -- --project ./samples/OptionAnalyzer/OptionAnalyzer.fsproj --analyzers-path ./artifacts/bin/OptionAnalyzer/release --verbosity d --binlog-path temp/binlogs"
}
stage "docs" { run "dotnet fsdocs build --properties Configuration=Release --eval --clean --strict" }
stage "docs" {
run "dotnet fsdocs build --properties Configuration=Release --eval --clean --strict"
}
runIfOnlySpecified false
}

Expand Down
53 changes: 40 additions & 13 deletions docs/content/Dual Analyzer.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,34 +37,49 @@ let private topologicallySortedOpenStatementsAnalyzer
let allOpenStatements = ResizeArray<string list * range>()

let (|LongIdentAsString|) (lid: SynLongIdent) =
lid.LongIdent |> List.map (fun ident -> ident.idText)
lid.LongIdent
|> List.map (fun ident -> ident.idText)

let walker =
{ new SyntaxCollectorBase() with
override _.WalkSynModuleSigDecl(_, decl: SynModuleSigDecl) =
match decl with
| SynModuleSigDecl.Open(
target = SynOpenDeclTarget.ModuleOrNamespace(longId = LongIdentAsString value; range = mOpen)) ->
target = SynOpenDeclTarget.ModuleOrNamespace(
longId = LongIdentAsString value; range = mOpen)) ->
allOpenStatements.Add(value, mOpen)
| _ -> ()

override _.WalkSynModuleDecl(_, decl: SynModuleDecl) =
match decl with
| SynModuleDecl.Open(
target = SynOpenDeclTarget.ModuleOrNamespace(longId = LongIdentAsString value; range = mOpen)) ->
target = SynOpenDeclTarget.ModuleOrNamespace(
longId = LongIdentAsString value; range = mOpen)) ->
allOpenStatements.Add(value, mOpen)
| _ -> ()
}

ASTCollecting.walkAst walker untypedTree

allOpenStatements |> Seq.toList
allOpenStatements
|> Seq.toList

let isSystemOpenStatement (openStatement: string list, mOpen: range) =
let isFromBCL () =
let line = sourceText.GetLineString(mOpen.EndLine - 1)

match checkResults.GetSymbolUseAtLocation(mOpen.EndLine, mOpen.EndColumn, line, openStatement) with
let line =
sourceText.GetLineString(
mOpen.EndLine
- 1
)

match
checkResults.GetSymbolUseAtLocation(
mOpen.EndLine,
mOpen.EndColumn,
line,
openStatement
)
with
| Some symbolUse ->
match symbolUse.Symbol.Assembly.FileName with
| None -> false
Expand All @@ -73,19 +88,25 @@ let private topologicallySortedOpenStatementsAnalyzer
assemblyPath.ToLower().Contains "microsoft.netcore.app.ref"
| _ -> false

openStatement.[0].StartsWith("System") && isFromBCL ()
openStatement.[0].StartsWith("System")
&& isFromBCL ()

let nonSystemOpens = allOpenStatements |> List.skipWhile isSystemOpenStatement
let nonSystemOpens =
allOpenStatements
|> List.skipWhile isSystemOpenStatement

return
nonSystemOpens
|> List.filter isSystemOpenStatement
|> List.map (fun (openStatement, mOpen) ->
let openStatementText = openStatement |> String.concat "."
let openStatementText =
openStatement
|> String.concat "."

{
Type = "Unsorted System open statement"
Message = $"%s{openStatementText} was found after non System namespaces where opened!"
Message =
$"%s{openStatementText} was found after non System namespaces where opened!"
Code = "SOT001"
Severity = Severity.Warning
Range = mOpen
Expand All @@ -96,15 +117,21 @@ let private topologicallySortedOpenStatementsAnalyzer

[<CliAnalyzer "Topologically sorted open statements">]
let cliAnalyzer (ctx: CliContext) : Async<Message list> =
topologicallySortedOpenStatementsAnalyzer ctx.SourceText ctx.ParseFileResults.ParseTree ctx.CheckFileResults
topologicallySortedOpenStatementsAnalyzer
ctx.SourceText
ctx.ParseFileResults.ParseTree
ctx.CheckFileResults

[<EditorAnalyzer "Topologically sorted open statements">]
let editorAnalyzer (ctx: EditorContext) : Async<Message list> =
match ctx.CheckFileResults with
// The editor might not have any check results for a given file. So we don't return any messages.
| None -> async.Return []
| Some checkResults ->
topologicallySortedOpenStatementsAnalyzer ctx.SourceText ctx.ParseFileResults.ParseTree checkResults
topologicallySortedOpenStatementsAnalyzer
ctx.SourceText
ctx.ParseFileResults.ParseTree
checkResults

(**
Both analyzers will follow the same code path: the console application will always have the required data, while the editor needs to be more careful.
Expand Down
Loading