diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..9ef9c78
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,24 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "fsharp-analyzers": {
+ "version": "0.18.0",
+ "commands": [
+ "fsharp-analyzers"
+ ]
+ },
+ "fantomas": {
+ "version": "6.2.3",
+ "commands": [
+ "fantomas"
+ ]
+ },
+ "fsdocs-tool": {
+ "version": "20.0.0-alpha-003",
+ "commands": [
+ "fsdocs"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..4ca9cea
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+root = true
+
+[*.{fs,fsi,fsx}]
+end_of_line = lf
+fsharp_keep_max_number_of_blank_lines = 1
+fsharp_multi_line_lambda_closing_newline = 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
+
+[build.fsx]
+fsharp_blank_lines_around_nested_multiline_expressions = false
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..8ce0230
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,12 @@
+version: 2
+updates:
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+
+ - package-ecosystem: "nuget"
+ directory: "/"
+ schedule:
+ interval: "daily"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..c2edcd0
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,63 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+ DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
+ DOTNET_ROLL_FORWARD_TO_PRERELEASE: 1
+ DOTNET_ROLL_FORWARD: LatestMajor
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+jobs:
+ ci:
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macOS-latest]
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+
+ - name: Build
+ run: dotnet fsi build.fsx
+
+ - name: Analyzers
+ continue-on-error: true
+ run: dotnet fsharp-analyzers --project ./src/Ionide.Analyzers/Ionide.Analyzers.fsproj --project ./tests/Ionide.Analyzers.Tests/Ionide.Analyzers.Tests.fsproj --analyzers-path ./src/Ionide.Analyzers/bin/Release/net6.0 --report ./report.sarif
+ if: matrix.os == 'ubuntu-latest'
+
+ - name: Upload SARIF file
+ uses: github/codeql-action/upload-sarif@v2
+ if: matrix.os == 'ubuntu-latest'
+ with:
+ sarif_file: ./report.sarif
+
+ - name: Upload documentation
+ if: matrix.os == 'ubuntu-latest' && github.ref == 'refs/heads/main'
+ uses: actions/upload-pages-artifact@v2
+ with:
+ path: ./output
+
+ deploy:
+ runs-on: ubuntu-latest
+ needs: ci
+ if: github.ref == 'refs/heads/main'
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v2
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7b6f223
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,492 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+.idea
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp
+
+# fsdocs
+output/
+.fsdocs/
+tmp/
+
+# Analyzers
+*.sarif
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..f7c561e
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Changelog
+
+## Unreleased
+
+* Initial version
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000..c4a55fc
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,42 @@
+
+
+
+ $(MSBuildThisFileDirectory)CHANGELOG.md
+
+ Ionide
+ David Schaefer, Florian Verdonck
+ false
+ true
+
+ Community F# analyzers
+
+ Copyright Ionide © $([System.DateTime]::UtcNow.Year)
+ F#, fsharp, analyzers
+ true
+ true
+ embedded
+ Apache-2.0
+ README.md
+ https://ionide.io/ionide-analyzers/
+ https://github.com/ionide/ionide-analyzers/blob/main/CHANGELOG.md
+ true
+ true
+ FS0025
+ 1182;3390;$(WarnOn)
+ false
+ false
+ NU1603;NETSDK1057
+ true
+
+ true
+ true
+ preview
+ $(OtherFlags) --test:GraphBasedChecking --test:ParallelOptimization --test:ParallelIlxGen
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 0000000..b3b19c0
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,17 @@
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..66602bd
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2023 Ionide
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index 20152e7..74f71ac 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,12 @@
-# ionide-analyzers
+# Ionide.Analyzers
+
+An fsharp community project containing [analyzers](https://ionide.io/FSharp.Analyzers.SDK/).
+
+## Getting started
+
+Run `dotnet fsi build.fsx` to perform a local CI build.
+Otherwise run `dotnet tool restore` and `dotnet restore` to download all the dependencies.
+
+## Running the documentation
+
+Run `dotnet fsi build.fsx -p Docs` to run the documentation locally.
\ No newline at end of file
diff --git a/build.fsx b/build.fsx
new file mode 100644
index 0000000..03c89c3
--- /dev/null
+++ b/build.fsx
@@ -0,0 +1,92 @@
+#r "nuget: Fun.Build, 1.0.2"
+#r "nuget: Fake.IO.FileSystem, 6.0.0"
+
+open System.Text.Json
+open Fake.IO
+open Fake.IO.Globbing.Operators
+open Fun.Build
+
+let cleanDirs globExpr = (!!globExpr) |> Shell.cleanDirs
+
+/// Workaround for https://github.com/dotnet/sdk/issues/35989
+let restoreTools (ctx: Internal.StageContext) =
+ async {
+ let json = File.readAsString ".config/dotnet-tools.json"
+ let jsonDocument = JsonDocument.Parse(json)
+ let root = jsonDocument.RootElement
+ let tools = root.GetProperty("tools")
+
+ let! installs =
+ tools.EnumerateObject()
+ |> Seq.map (fun tool ->
+ let version = tool.Value.GetProperty("version").GetString()
+ ctx.RunCommand $"dotnet tool install %s{tool.Name} --version %s{version}"
+ )
+ |> Async.Sequential
+
+ let failedInstalls =
+ installs
+ |> Array.tryPick (
+ function
+ | Ok _ -> None
+ | Error error -> Some error
+ )
+
+ match failedInstalls with
+ | None -> return 0
+ | Some error ->
+ printfn $"%s{error}"
+ return 1
+ }
+
+pipeline "Build" {
+ workingDir __SOURCE_DIRECTORY__
+ stage "clean" {
+ run (fun _ ->
+ async {
+ cleanDirs "src/**/obj"
+ cleanDirs "src/**/bin"
+ cleanDirs "tests/**/obj"
+ cleanDirs "tests/**/bin"
+ Shell.cleanDir "bin"
+ return 0
+ }
+ )
+ }
+ stage "lint" {
+ run restoreTools
+ run "dotnet fantomas . --check"
+ }
+ stage "restore" { run "dotnet restore" }
+ stage "build" {
+ run "dotnet restore ionide-analyzers.sln"
+ run "dotnet build --no-restore -c Release ionide-analyzers.sln"
+ }
+ stage "test" { run "dotnet test --no-restore --no-build -c Release" }
+ stage "pack" { run "dotnet pack ./src/Ionide.Analyzers/Ionide.Analyzers.fsproj -c Release -o bin" }
+ stage "docs" {
+ envVars
+ [|
+ "DOTNET_ROLL_FORWARD_TO_PRERELEASE", "1"
+ "DOTNET_ROLL_FORWARD", "LatestMajor"
+ |]
+ run "dotnet fsdocs build --properties Configuration=Release"
+ }
+ runIfOnlySpecified false
+}
+
+pipeline "Docs" {
+ workingDir __SOURCE_DIRECTORY__
+ stage "main" {
+ envVars
+ [|
+ "DOTNET_ROLL_FORWARD_TO_PRERELEASE", "1"
+ "DOTNET_ROLL_FORWARD", "LatestMajor"
+ |]
+ run restoreTools
+ run "dotnet fsdocs watch --port 7890"
+ }
+ runIfOnlySpecified true
+}
+
+tryPrintPipelineCommandHelp ()
diff --git a/docs/content/contributions.md b/docs/content/contributions.md
new file mode 100644
index 0000000..437033a
--- /dev/null
+++ b/docs/content/contributions.md
@@ -0,0 +1,71 @@
+---
+title: Contributing
+categoryindex: 1
+index: 1
+category: docs
+---
+
+# Contributing
+
+Hi there! Thank you for considering to contribute to this community project!
+We hope this project can serve as a vessel to proto-type your ideas and bring them easily into your workflow.
+
+The main goal of this project is to have a reference implementation for analyzers built with the [Ionide SDK](https://ionide.io/FSharp.Analyzers.SDK/).
+
+## What kind of contributions do we accept?
+
+We see this project as a collection of **general purpose analyzers for every kind of F# codebase**. The analyzers are ideally based on generally accepted guidance among the community.
+
+If an analyzer is more tailored towards your own use-cases, or has a distinct (controversial) opinion, we might decline your pull request. This is to benefit the out-of-the-box experience.
+We wish to avoid that people turns of certain analyzers from this `NuGet` package, because they are not universally accepted as best practise rules.
+
+The best thing you can do is **pitch your idea before** you start any **implementation**. This is to avoid a mismatch of expectations.
+
+## How do I contribute a new analyzer?
+
+When creating a new analyzer, the typical experience is that you will provide a one-time contribution. We review your PR, we go back and forward over some details, we merge it, and ship a new NuGet package with your work!
+We truly hope to provide you with a great experience while contribution, and also keep a good balance on the maintenance of your change afterwards.
+
+### Technical setup
+
+This is a very typical `dotnet` repository. Run commands like `dotnet tool restore`, `dotnet restore` and `dotnet build` to get going.
+
+Our build script can be invoke with `dotnet fsi build.fsx`.
+Or `dotnet fsi build.fsx -- --help` to view non-default pipelines.
+
+### Your analyzer
+
+We try to split the analyzers up into several categories:
+
+- `hints`
+- `style`
+- `performance`
+- `quality`
+
+Add your analyzer the directory that makes the most sense. Ask us if you are unsure.
+Next start writing your [first analyzer](https://ionide.io/FSharp.Analyzers.SDK/content/Getting%20Started%20Writing.html#First-analyzer).
+
+Please use the *next available code* for your messages, we currently do not have any elaborate system in place for the message codes.
+
+### Your regression tests
+
+Because we want to ensure you analyzer keeps working with every new release, we would like to ask you to provide a series of unit tests. These should cover the most critical use-case of your analyzer.
+Try and create unit tests in a fashion where the tests themselves are stable. If the SDK API changes, we only want to update your analyzer code, and your tests should run fine.
+
+### Your documentation
+
+Each analyzer should have a matching documentation page.
+This is the url we will use to link in the `AnalyzerAttribute` meta data.
+Use the existing pages as a reference.
+
+Run `dotnet fsi build.fsx -p Docs` to run the `fsdocs` tool locally.
+
+### Your changelog entry
+
+We use [KeepAChangelog](https://github.com/ionide/keepachangelog) to determine our next NuGet version.
+Please add [a new entry](https://keepachangelog.com/en/1.1.0/) for your changes.
+
+## When will the next version ship?
+
+Unless, there are technical reason blocking us, we will try and ship your contribution as soon as possible.
+Our CI process should pick up new version from the changelog and push new packages to NuGet.org once the code is on the `main` branch.
diff --git a/docs/content/fsdocs-theme.css b/docs/content/fsdocs-theme.css
new file mode 100644
index 0000000..45de899
--- /dev/null
+++ b/docs/content/fsdocs-theme.css
@@ -0,0 +1,3 @@
+:root {
+ --aside-width: 300px;
+}
\ No newline at end of file
diff --git a/docs/img/favicon.ico b/docs/img/favicon.ico
new file mode 100644
index 0000000..16abdb0
Binary files /dev/null and b/docs/img/favicon.ico differ
diff --git a/docs/img/logo.png b/docs/img/logo.png
new file mode 100644
index 0000000..babbee5
Binary files /dev/null and b/docs/img/logo.png differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..20c813d
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,5 @@
+# Ionide.Analyzers
+
+Welcome to the Ionide Analyzers project.
+
+Learn how to [get started contributing]({{fsdocs-next-page-link}})!
diff --git a/docs/style/002.md b/docs/style/002.md
new file mode 100644
index 0000000..166ffcd
--- /dev/null
+++ b/docs/style/002.md
@@ -0,0 +1,25 @@
+---
+title: SquareBracketArrayAnalyzer
+category: style
+categoryindex: 3
+index: 1
+---
+
+# SquareBracketArrayAnalyzer
+
+## Problem
+
+Using the older array type syntax (`string[]`) is discouraged by the [style guide](https://learn.microsoft.com/en-us/dotnet/fsharp/style-guide/formatting#for-types-prefer-prefix-syntax-for-generics-foot-with-some-specific-exceptions).
+
+```fsharp
+// Triggers analyzer
+let a: string[] = Array.empty
+```
+
+## Fix
+
+The `postfix` syntax is preferred:
+
+```fsharp
+let a: string array = Array.empty
+```
\ No newline at end of file
diff --git a/docs/suggestion/001.md b/docs/suggestion/001.md
new file mode 100644
index 0000000..0aa6e1e
--- /dev/null
+++ b/docs/suggestion/001.md
@@ -0,0 +1,31 @@
+---
+title: CopyAndUpdateRecordChangesAllFields
+category: suggestion
+categoryindex: 2
+index: 1
+---
+# CopyAndUpdateRecordChangesAllFieldsAnalyzer
+
+## Problem
+
+See [fsharp/fslang-suggestions#603](https://github.com/fsharp/fslang-suggestions/issues/603), when you have a record update expression that overrides all fields, it is advised to construct a new instance instead.
+
+```fsharp
+type Point =
+ {
+ X: int
+ Y: int
+ }
+
+let zero = { X = 0; Y = 0 }
+// Triggers analyzer
+let moved = { zero with X = 1; Y = 2 }
+```
+
+## Fix
+
+Remove the `expr with` part:
+
+```fsharp
+let moved = { X = 1; Y = 2 }
+```
\ No newline at end of file
diff --git a/global.json b/global.json
new file mode 100644
index 0000000..1cccce5
--- /dev/null
+++ b/global.json
@@ -0,0 +1,5 @@
+{
+ "sdk": {
+ "version": "8.0.100-rc.2.23502.2"
+ }
+}
\ No newline at end of file
diff --git a/ionide-analyzers.sln b/ionide-analyzers.sln
new file mode 100644
index 0000000..4cf7c2e
--- /dev/null
+++ b/ionide-analyzers.sln
@@ -0,0 +1,36 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A75DEA54-6175-4364-BCAB-23F9BF054006}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Ionide.Analyzers", "src\Ionide.Analyzers\Ionide.Analyzers.fsproj", "{37A4EE72-5FF8-4255-8004-71CFF6193E14}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{EBA500BA-82FE-4E55-82F1-0B2FE5A94E14}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Ionide.Analyzers.Tests", "tests\Ionide.Analyzers.Tests\Ionide.Analyzers.Tests.fsproj", "{93CC938F-1F27-4CE4-9AD4-52D057AB3DC8}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {37A4EE72-5FF8-4255-8004-71CFF6193E14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {37A4EE72-5FF8-4255-8004-71CFF6193E14}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {37A4EE72-5FF8-4255-8004-71CFF6193E14}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {37A4EE72-5FF8-4255-8004-71CFF6193E14}.Release|Any CPU.Build.0 = Release|Any CPU
+ {93CC938F-1F27-4CE4-9AD4-52D057AB3DC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {93CC938F-1F27-4CE4-9AD4-52D057AB3DC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {93CC938F-1F27-4CE4-9AD4-52D057AB3DC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {93CC938F-1F27-4CE4-9AD4-52D057AB3DC8}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {37A4EE72-5FF8-4255-8004-71CFF6193E14} = {A75DEA54-6175-4364-BCAB-23F9BF054006}
+ {93CC938F-1F27-4CE4-9AD4-52D057AB3DC8} = {EBA500BA-82FE-4E55-82F1-0B2FE5A94E14}
+ EndGlobalSection
+EndGlobal
diff --git a/src/Ionide.Analyzers/Ionide.Analyzers.fsproj b/src/Ionide.Analyzers/Ionide.Analyzers.fsproj
new file mode 100644
index 0000000..8a3b467
--- /dev/null
+++ b/src/Ionide.Analyzers/Ionide.Analyzers.fsproj
@@ -0,0 +1,19 @@
+
+
+
+ net6.0
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ionide.Analyzers/Style/SquareBracketArrayAnalyzer.fs b/src/Ionide.Analyzers/Style/SquareBracketArrayAnalyzer.fs
new file mode 100644
index 0000000..e984704
--- /dev/null
+++ b/src/Ionide.Analyzers/Style/SquareBracketArrayAnalyzer.fs
@@ -0,0 +1,39 @@
+module Ionide.Analyzers.Style.SquareBracketArrayAnalyzer
+
+open FSharp.Compiler.Text
+open FSharp.Compiler.Syntax
+open FSharp.Analyzers.SDK
+open FSharp.Analyzers.SDK.ASTCollecting
+
+[]
+let squareBracketArrayAnalyzer: Analyzer =
+ fun (context: CliContext) ->
+ async {
+ let ts = ResizeArray()
+
+ let collector =
+ { new SyntaxCollectorBase() with
+ override x.WalkType(t: SynType) =
+ match t with
+ | SynType.Array _ -> ts.Add t.Range
+ | _ -> ()
+ }
+
+ walkAst collector context.ParseFileResults.ParseTree
+
+ return
+ ts
+ |> Seq.map (fun m ->
+ {
+ Type = "SquareBracketArrayAnalyzer"
+ Message = "Prefer postfix syntax for arrays."
+ Code = "IONIDE-002"
+ Severity = Info
+ Range = m
+ Fixes = []
+ }
+ )
+ |> Seq.toList
+ }
diff --git a/src/Ionide.Analyzers/Suggestion/CopyAndUpdateRecordChangesAllFieldsAnalyzer.fs b/src/Ionide.Analyzers/Suggestion/CopyAndUpdateRecordChangesAllFieldsAnalyzer.fs
new file mode 100644
index 0000000..2a0a8c6
--- /dev/null
+++ b/src/Ionide.Analyzers/Suggestion/CopyAndUpdateRecordChangesAllFieldsAnalyzer.fs
@@ -0,0 +1,65 @@
+module Ionide.Analyzers.Suggestion.CopyAndUpdateRecordChangesAllFieldsAnalyzer
+
+open FSharp.Analyzers.SDK
+open FSharp.Analyzers.SDK.ASTCollecting
+open FSharp.Analyzers.SDK.TASTCollecting
+open FSharp.Compiler.Symbols
+open FSharp.Compiler.Text
+open FSharp.Compiler.Syntax
+
+type UpdateRecord = SynExprRecordField list * range
+
+[]
+let copyAndUpdateRecordChangesAllFieldsAnalyzer: Analyzer =
+ fun (context: CliContext) ->
+ async {
+ let untypedRecordUpdates =
+ let xs = ResizeArray()
+
+ let collector =
+ { new SyntaxCollectorBase() with
+ override x.WalkExpr(e: SynExpr) =
+ match e with
+ | SynExpr.Record(copyInfo = Some _; recordFields = fields) -> xs.Add(fields, e.Range)
+ | _ -> ()
+ }
+
+ walkAst collector context.ParseFileResults.ParseTree
+ Seq.toList xs
+
+ let messages = ResizeArray untypedRecordUpdates.Length
+
+ let tastCollector =
+ { new TypedTreeCollectorBase() with
+ override x.WalkNewRecord (mRecord: range) (recordType: FSharpType) =
+ let matchingUnTypedNode =
+ untypedRecordUpdates
+ |> List.tryFind (fun (_, mExpr) -> Range.equals mExpr mRecord)
+
+ match matchingUnTypedNode with
+ | None -> ()
+ | Some(fields, mExpr) ->
+
+ if not recordType.TypeDefinition.IsFSharpRecord then
+ ()
+ else if recordType.TypeDefinition.FSharpFields.Count = fields.Length then
+ messages.Add
+ {
+ Type = "CopyAndUpdateRecordChangesAllFieldsAnalyzer analyzer"
+ Message =
+ "All record fields of record are being updated. Consider creating a new instance instead."
+ Code = "IONIDE-001"
+ Severity = Severity.Hint
+ Range = mExpr
+ Fixes = []
+ }
+ }
+
+ match context.TypedTree with
+ | None -> ()
+ | Some typedTree -> typedTree.Declarations |> List.iter (walkTast tastCollector)
+
+ return Seq.toList messages
+ }
diff --git a/tests/Ionide.Analyzers.Tests/Ionide.Analyzers.Tests.fsproj b/tests/Ionide.Analyzers.Tests/Ionide.Analyzers.Tests.fsproj
new file mode 100644
index 0000000..aafe5f8
--- /dev/null
+++ b/tests/Ionide.Analyzers.Tests/Ionide.Analyzers.Tests.fsproj
@@ -0,0 +1,28 @@
+
+
+
+ net8.0
+
+ false
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Ionide.Analyzers.Tests/Program.fs b/tests/Ionide.Analyzers.Tests/Program.fs
new file mode 100644
index 0000000..176a7b6
--- /dev/null
+++ b/tests/Ionide.Analyzers.Tests/Program.fs
@@ -0,0 +1,4 @@
+module Program =
+
+ []
+ let main _ = 0
diff --git a/tests/Ionide.Analyzers.Tests/Style/SquareBracketArrayAnalyzerTests.fs b/tests/Ionide.Analyzers.Tests/Style/SquareBracketArrayAnalyzerTests.fs
new file mode 100644
index 0000000..0b82bb0
--- /dev/null
+++ b/tests/Ionide.Analyzers.Tests/Style/SquareBracketArrayAnalyzerTests.fs
@@ -0,0 +1,32 @@
+module Ionide.Analyzers.Tests.Style.SquareBracketArrayAnalyzerTests
+
+open NUnit.Framework
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Analyzers.SDK.Testing
+open Ionide.Analyzers.Style.SquareBracketArrayAnalyzer
+
+let mutable projectOptions: FSharpProjectOptions = FSharpProjectOptions.zero
+
+[]
+let Setup () =
+ task {
+ let! opts = mkOptionsFromProject "net7.0" []
+
+ projectOptions <- opts
+ }
+
+[]
+let ``string array in binding`` () =
+ async {
+ let source =
+ """
+module M
+
+let a (b: string[]) = ()
+ """
+
+ let ctx = getContext projectOptions source
+ let! msgs = squareBracketArrayAnalyzer ctx
+ Assert.IsNotEmpty msgs
+ Assert.IsTrue(Assert.messageContains "Prefer postfix syntax for arrays." msgs[0])
+ }
diff --git a/tests/Ionide.Analyzers.Tests/Suggestion/CopyAndUpdateRecordChangesAllFieldsAnalyzerTests.fs b/tests/Ionide.Analyzers.Tests/Suggestion/CopyAndUpdateRecordChangesAllFieldsAnalyzerTests.fs
new file mode 100644
index 0000000..c8247fa
--- /dev/null
+++ b/tests/Ionide.Analyzers.Tests/Suggestion/CopyAndUpdateRecordChangesAllFieldsAnalyzerTests.fs
@@ -0,0 +1,69 @@
+module Ionide.Analyzers.Tests.Suggestion.CopyAndUpdateRecordChangesAllFieldsAnalyzerTests
+
+open NUnit.Framework
+open FSharp.Compiler.CodeAnalysis
+open FSharp.Analyzers.SDK.Testing
+open Ionide.Analyzers.Suggestion.CopyAndUpdateRecordChangesAllFieldsAnalyzer
+
+let mutable projectOptions: FSharpProjectOptions = FSharpProjectOptions.zero
+
+[]
+let Setup () =
+ task {
+ let! opts = mkOptionsFromProject "net7.0" []
+
+ projectOptions <- opts
+ }
+
+[]
+let ``single record field`` () =
+ async {
+ let source =
+ """
+module M
+
+type R = { A: int }
+let a = { A = 1 }
+let updated = { a with A = 2 }
+ """
+
+ let ctx = getContext projectOptions source
+ let! msgs = copyAndUpdateRecordChangesAllFieldsAnalyzer ctx
+ Assert.IsNotEmpty msgs
+ Assert.IsTrue(Assert.messageContains "All record fields of record are being updated" msgs[0])
+ }
+
+[]
+let ``multiple record field`` () =
+ async {
+ let source =
+ """
+module M
+
+type R = { A: int; B:int; C:int }
+let a = { A = 1; B = 2; C = 3 }
+let updated = { a with A = 2; B = 4; C = 5 }
+ """
+
+ let ctx = getContext projectOptions source
+ let! msgs = copyAndUpdateRecordChangesAllFieldsAnalyzer ctx
+ Assert.IsNotEmpty msgs
+ Assert.IsTrue(Assert.messageContains "All record fields of record are being updated" msgs[0])
+ }
+
+[]
+let ``multiple record field, neg`` () =
+ async {
+ let source =
+ """
+module M
+
+type R = { A: int; B:int; C:int }
+let a = { A = 1; B = 2; C = 3 }
+let updated = { a with A = 2; B = 4 }
+ """
+
+ let ctx = getContext projectOptions source
+ let! msgs = copyAndUpdateRecordChangesAllFieldsAnalyzer ctx
+ Assert.IsEmpty msgs
+ }