├── .config └── dotnet-tools.json ├── .devcontainer ├── Dockerfile ├── devcontainer.json └── settings.vscode.json ├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .paket └── Paket.Restore.targets ├── .travis.yml ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── Directory.Build.props ├── LICENSE.md ├── NpgsqlFSharpAnalyzer.sln ├── README.md ├── RELEASE_NOTES.md ├── appveyor.yml ├── build.cmd ├── build.fsx ├── build.sh ├── docker-compose.yml ├── paket.dependencies ├── paket.lock ├── sql.gif ├── src ├── Directory.Build.props ├── FParsec │ ├── AssemblyInfo.fs │ ├── CharParsers.fs │ ├── CharParsers.fsi │ ├── Emit.fs │ ├── Error.fs │ ├── Error.fsi │ ├── FParsec.fsproj │ ├── Internals.fs │ ├── Primitives.fs │ ├── Primitives.fsi │ ├── Range.fs │ ├── StaticMapping.fs │ └── StaticMapping.fsi ├── FParsecCS │ ├── Buffer.cs │ ├── CaseFoldTable.cs │ ├── CharSet.cs │ ├── CharStream.cs │ ├── CharStreamLT.cs │ ├── Cloning.cs │ ├── ErrorMessage.cs │ ├── ErrorMessageList.cs │ ├── Errors.cs │ ├── FParsecCS.csproj │ ├── FastGenericEqualityERComparer.cs │ ├── HexFloat.cs │ ├── IdentifierValidator.cs │ ├── ManyChars.cs │ ├── OperatorPrecedenceParser.cs │ ├── Position.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Reply.cs │ ├── StringBuffer.cs │ ├── Strings.cs │ ├── Text.cs │ └── UnmanagedMemoryPool.cs ├── NpgsqlFSharpAnalyzer.Core │ ├── AssemblyInfo.fs │ ├── InformationSchema.fs │ ├── NpgsqlFSharpAnalyzer.Core.fsproj │ ├── SqlAnalysis.fs │ ├── SqlAnalyzer.fs │ ├── SyntacticAnalysis.fs │ └── Types.fs ├── NpgsqlFSharpAnalyzer │ ├── AssemblyInfo.fs │ ├── NpgsqlFSharpAnalyzer.fsproj │ ├── SqlAnalyzer.fs │ └── paket.references ├── NpgsqlFSharpParser │ ├── AssemblyInfo.fs │ ├── NpgsqlFSharpParser.fsproj │ ├── Parser.fs │ └── Types.fs ├── NpgsqlFSharpVs │ ├── ContentTypeNames.cs │ ├── FsLintVsPackage.cs │ ├── LintChecker.cs │ ├── LintCheckerProvider.cs │ ├── LintError.cs │ ├── LintProjectInfo.cs │ ├── LintTagger.cs │ ├── NpgsqlFSharpVs.csproj │ ├── Options │ │ └── FsLintOptionsPage.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Resources │ │ ├── GettingStarted.html │ │ ├── License.txt │ │ ├── ReleaseNotes.html │ │ └── logo.png │ ├── SubscriptionManager.cs │ ├── SuggestionPreview.xaml │ ├── SuggestionPreview.xaml.cs │ ├── Suggestions │ │ ├── LintActionsSource.cs │ │ ├── LintFixAction.cs │ │ ├── LintSuggestionProvider.cs │ │ ├── LintSuppressAction.cs │ │ └── LintSuppressBy.cs │ ├── Table │ │ ├── LintTableSnapshotFactory.cs │ │ └── LintingErrorsSnapshot.cs │ ├── paket.references │ └── source.extension.vsixmanifest ├── ParserTestsWithNet48 │ ├── App.config │ ├── ParserTestsWithNet48.csproj │ ├── Program.cs │ └── Properties │ │ └── AssemblyInfo.cs └── Ubik │ ├── AssemblyInfo.fs │ ├── Program.fs │ ├── Project.fs │ └── Ubik.fsproj └── tests ├── Directory.Build.props ├── NpgsqlFSharpAnalyzer.Tests ├── Analyzer.fs ├── AssemblyInfo.fs ├── Main.fs ├── NpgsqlFSharpAnalyzer.Tests.fsproj ├── ParseDeclareTests.fs ├── ParseDeleteTests.fs ├── ParseInsertTests.fs ├── ParseSelectTests.fs ├── ParseSetTests.fs ├── ParseUpdateTests.fs └── Tests.fs └── examples ├── Directory.Build.props └── hashing ├── AssemblyInfo.fs ├── MultipleNestedModules.fs ├── MultipleNestedModules.fsx ├── SprintfBlock.fs ├── castingNonNullableStaysNonNullable.fs ├── detectingDynamicListsInTransactions.fs ├── detectingEmptyParameterSet.fs ├── errorsInTransactions.fs ├── examples.fsproj ├── executingQueryInsteadOfNonQuery.fs ├── insertQueryWithNonNullableParameter.fs ├── parameterTypeMismatch.fs ├── readAttemptIntegerTypeMismatch.fs ├── readingTextArray.fs ├── readingUuidArray.fs ├── selectWithInnerJoins.fs ├── selectWithNonNullableColumnComparison.fs ├── semanticAnalysis-missingColumn.fs ├── semanticAnalysis-missingParameter.fs ├── semanticAnalysis-redundantParameters.fs ├── semanticAnalysis-typeMismatch.fs ├── syntacticAnalysis-literalStrings.fs ├── syntacticAnalysis.fs ├── syntacticAnalysisExecuteScalar.fs ├── syntacticAnalysisFromLambdaBody.fs ├── syntacticAnalysisFromSingleCaseUnion.fs ├── syntacticAnalysisProcessedList.fs ├── syntacticAnalysisSimpleQuery.fs ├── syntaxAnalysis-detectingSkipAnalysis.fs ├── syntaxAnalysis-usingTransactions.fs ├── syntaxAnalysisReferencingQueryDoesNotGiveError.fs ├── topLevelExpressionIsDetected.fs ├── updateQueryWithParametersInAssignments.fs ├── usingIntArrayParameter.fs ├── usingLiteralQueriesWithTransactions.fs └── usingProcessedParameters.fs /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "sourcelink": { 6 | "version": "3.1.1", 7 | "commands": [ 8 | "sourcelink" 9 | ] 10 | }, 11 | "dotnet-reportgenerator-globaltool": { 12 | "version": "4.2.15", 13 | "commands": [ 14 | "reportgenerator" 15 | ] 16 | }, 17 | "fake-cli": { 18 | "version": "5.20.4", 19 | "commands": [ 20 | "fake" 21 | ] 22 | }, 23 | "fcswatch-cli": { 24 | "version": "0.7.14", 25 | "commands": [ 26 | "fcswatch" 27 | ] 28 | }, 29 | "paket": { 30 | "version": "5.257.0", 31 | "commands": [ 32 | "paket" 33 | ] 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fsharp:10.2.3-netcore 2 | 3 | # Copy endpoint specific user settings into container to specify 4 | # .NET Core should be used as the runtime. 5 | COPY settings.vscode.json /root/.vscode-remote/data/Machine/settings.json 6 | 7 | # Install git, process tools 8 | RUN apt-get update && apt-get -y install git procps 9 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MiniScaffold", 3 | "dockerFile": "Dockerfile", 4 | "appPort": [8080], 5 | "extensions": [ 6 | "ionide.ionide-fsharp", 7 | "ms-vscode.csharp", 8 | "editorconfig.editorconfig", 9 | "ionide.ionide-paket", 10 | "ionide.ionide-fake" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.devcontainer/settings.vscode.json: -------------------------------------------------------------------------------- 1 | { 2 | "FSharp.fsacRuntime":"netcore" 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: 2 | http://EditorConfig.org 3 | 4 | # top-most EditorConfig file 5 | root = true 6 | 7 | # Default settings: 8 | # A newline ending every file 9 | # Use 4 spaces as indentation 10 | [*] 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{fs,fsi,fsx,config}] 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | 19 | [paket.*] 20 | trim_trailing_whitespace = true 21 | indent_size = 2 22 | 23 | [*.paket.references] 24 | trim_trailing_whitespace = true 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp text=auto eol=lf 6 | *.vb diff=csharp text=auto eol=lf 7 | *.fs diff=csharp text=auto eol=lf 8 | *.fsi diff=csharp text=auto eol=lf 9 | *.fsx diff=csharp text=auto eol=lf 10 | *.sln text eol=crlf merge=union 11 | *.csproj merge=union 12 | *.vbproj merge=union 13 | *.fsproj merge=union 14 | *.dbproj merge=union 15 | *.sh text eol=lf 16 | 17 | # Standard to msysgit 18 | *.doc diff=astextplain 19 | *.DOC diff=astextplain 20 | *.docx diff=astextplain 21 | *.DOCX diff=astextplain 22 | *.dot diff=astextplain 23 | *.DOT diff=astextplain 24 | *.pdf diff=astextplain 25 | *.PDF diff=astextplain 26 | *.rtf diff=astextplain 27 | *.RTF diff=astextplain 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please insert a description of your problem or question. 4 | 5 | ## Error messages, screenshots 6 | 7 | Please add any error logs or screenshots if available. 8 | 9 | ## Failing test, failing GitHub repo, or reproduction steps 10 | 11 | Please add either a failing test, a GitHub repo of the problem or detailed reproduction steps. 12 | 13 | ## Expected Behavior 14 | 15 | Please define what you would expect the behavior to be like. 16 | 17 | ## Known workarounds 18 | 19 | Please provide a description of any known workarounds. 20 | 21 | ## Other information 22 | 23 | * Operating System: 24 | - [ ] windows [insert version here] 25 | - [ ] macOs [insert version] 26 | - [ ] linux [insert flavor/version here] 27 | * Platform 28 | - [ ] dotnet core 29 | - [ ] dotnet full 30 | - [ ] mono 31 | * Branch or release version: 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Proposed Changes 2 | 3 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. 4 | 5 | ## Types of changes 6 | 7 | What types of changes does your code introduce to BinaryDefense.FSharp.Analyzers? 8 | _Put an `x` in the boxes that apply_ 9 | 10 | - [ ] Bugfix (non-breaking change which fixes an issue) 11 | - [ ] New feature (non-breaking change which adds functionality) 12 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 13 | 14 | 15 | ## Checklist 16 | 17 | _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ 18 | 19 | - [ ] Build and tests pass locally 20 | - [ ] I have added tests that prove my fix is effective or that my feature works (if appropriate) 21 | - [ ] I have added necessary documentation (if appropriate) 22 | 23 | ## Further comments 24 | 25 | If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | packages/ 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.pfx 193 | *.publishsettings 194 | node_modules/ 195 | orleans.codegen.cs 196 | 197 | # Since there are multiple workflows, uncomment next line to ignore bower_components 198 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 199 | #bower_components/ 200 | 201 | # RIA/Silverlight projects 202 | Generated_Code/ 203 | 204 | # Backup & report files from converting an old project file 205 | # to a newer Visual Studio version. Backup files are not needed, 206 | # because we have git ;-) 207 | _UpgradeReport_Files/ 208 | Backup*/ 209 | UpgradeLog*.XML 210 | UpgradeLog*.htm 211 | 212 | # SQL Server files 213 | *.mdf 214 | *.ldf 215 | 216 | # Business Intelligence projects 217 | *.rdl.data 218 | *.bim.layout 219 | *.bim_*.settings 220 | 221 | # Microsoft Fakes 222 | FakesAssemblies/ 223 | 224 | # GhostDoc plugin setting file 225 | *.GhostDoc.xml 226 | 227 | # Node.js Tools for Visual Studio 228 | .ntvs_analysis.dat 229 | 230 | # Visual Studio 6 build log 231 | *.plg 232 | 233 | # Visual Studio 6 workspace options file 234 | *.opt 235 | 236 | # Visual Studio LightSwitch build output 237 | **/*.HTMLClient/GeneratedArtifacts 238 | **/*.DesktopClient/GeneratedArtifacts 239 | **/*.DesktopClient/ModelManifest.xml 240 | **/*.Server/GeneratedArtifacts 241 | **/*.Server/ModelManifest.xml 242 | _Pvt_Extensions 243 | 244 | # Paket dependency manager 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | 254 | TestResults.xml 255 | 256 | # NuGet packages distributables 257 | dist/ 258 | 259 | # Ionide cache 260 | .ionide/ 261 | 262 | # Test coverage files 263 | coverage.xml 264 | coverage.*.xml 265 | 266 | # Paket tool store 267 | .paket/.store 268 | .paket/paket 269 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | sudo: required 3 | dist: xenial 4 | 5 | dotnet: 3.0.100 6 | addons: 7 | apt: 8 | packages: 9 | - dotnet-sdk-2.2 10 | os: 11 | - linux 12 | 13 | script: 14 | - ./build.sh 15 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ionide.ionide-paket", 4 | "ionide.ionide-fsharp", 5 | "ionide.ionide-fake", 6 | "ms-vscode.csharp", 7 | "editorConfig.editorConfig" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "WARNING01": "*********************************************************************************", 12 | "WARNING02": "The C# extension was unable to automatically decode projects in the current", 13 | "WARNING03": "workspace to create a runnable launch.json file. A template launch.json file has", 14 | "WARNING04": "been created as a placeholder.", 15 | "WARNING05": "", 16 | "WARNING06": "If OmniSharp is currently unable to load your project, you can attempt to resolve", 17 | "WARNING07": "this by restoring any missing project dependencies (example: run 'dotnet restore')", 18 | "WARNING08": "and by fixing any reported errors from building the projects in your workspace.", 19 | "WARNING09": "If this allows OmniSharp to now load your project then --", 20 | "WARNING10": " * Delete this file", 21 | "WARNING11": " * Open the Visual Studio Code command palette (View->Command Palette)", 22 | "WARNING12": " * run the command: '.NET: Generate Assets for Build and Debug'.", 23 | "WARNING13": "", 24 | "WARNING14": "If your project requires a more complex launch configuration, you may wish to delete", 25 | "WARNING15": "this configuration and pick a different template using the 'Add Configuration...'", 26 | "WARNING16": "button at the bottom of this file.", 27 | "WARNING17": "*********************************************************************************", 28 | "preLaunchTask": "build", 29 | "program": "${workspaceFolder}/bin/Debug//.dll", 30 | "args": [], 31 | "cwd": "${workspaceFolder}", 32 | "console": "internalConsole", 33 | "stopAtEntry": false 34 | }, 35 | { 36 | "name": ".NET Core Attach", 37 | "type": "coreclr", 38 | "request": "attach", 39 | "processId": "${command:pickProcess}" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "tab.unfocusedActiveBorder": "#fff0" 4 | } 5 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | f#, fsharp 5 | https://github.com/TheAngryByrd/BinaryDefense.FSharp.Analyzers 6 | https://github.com/TheAngryByrd/BinaryDefense.FSharp.Analyzers/blob/master/LICENSE.md 7 | false 8 | git 9 | TheAngryByrd 10 | https://github.com/TheAngryByrd/BinaryDefense.FSharp.Analyzers 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | ### 3.26.1 - 2021-05-06 2 | * Fix nuget packaging 3 | 4 | ### 3.26.0 - 2021-05-06 5 | * Allow the SQL parser to handle comments and update to latest FSharp Analyzers SDK 6 | 7 | ### 3.25.0 - 2021-04-26 8 | * Improvements to the SQL parser 9 | 10 | ### 3.24.0 - 2021-04-09 11 | * fixes dynamically applied parameters with more complex expressions 12 | 13 | ### 3.23.0 - 2021-03-26 14 | * Detect typed let bindings 15 | 16 | ### 3.22.1 - 2021-02-09 17 | * Let the analyzer continue its work when it comes across enum types 18 | 19 | ### 3.22.0 - 2020-12-08 20 | * Detect queries within sequential expressions or statements 21 | 22 | ### 3.21.0 - 2020-12-08 23 | * Detect queries within lambda expressions wrapped in single case unions 24 | 25 | ### 3.20.0 - 2020-12-08 26 | * Correctly retain selected column non-nullability when casted or aliased to another type 27 | 28 | ### 3.18.0 - 2020-12-06 29 | * Analyze SQL blocks from within lambda expressions 30 | 31 | ### 3.17.0 - 2020-12-06 32 | * Support for datetimeOffset and datetimeOffsetOrNone when reading columns of type timestamptz 33 | 34 | ### 3.16.0 - 2020-12-06 35 | * Analyze top level do expressions 36 | 37 | ### 3.15.0 - 2020-09-15 38 | * Analyze transaction parameter sets 39 | * Allow for literal queries on transactions 40 | 41 | ### 3.14.0 - 2020-09-07 42 | * Analyze transaction queries 43 | 44 | ### 3.13.0 - 2020-09-04 45 | * The ability to suppress warning messages generated by the analyzer 46 | 47 | ### 3.12.1 - 2020-08-31 48 | * Remove NpgsqlFSharpAnalyzer.Core nuget package reference from the analyzer 49 | 50 | ### 3.12.0 - 2020-08-29 51 | * Parameter nullability inference for parsable queries 52 | * Detecting the missing columns which are required for INSERT queries 53 | * Better error messages when reading from a result set which doesn't return any columns 54 | 55 | ### 3.11.0 - 2020-08-18 56 | * Even better error messages that include whether types were arrays or not 57 | 58 | #### 3.10.0 - 2020-08-18 59 | * Better error messages when showing the possible functions to use. 60 | * Warning when using Sql.execute and the query doesn't return a result set 61 | * Support for text int and uuid arrays both when reading columns and writing parameters 62 | 63 | #### 3.9.0 - 2020-07-19 64 | * Updated FSharp.Analyzers.SDK to 0.5.0 65 | 66 | #### 3.8.0 - 2020-06-26 67 | * Trim whitespace from parameter names 68 | * Fix aggressive syntactic matching 69 | 70 | #### 3.7.0 - 2020-05-19 71 | * Account for parameters of type `jsonb` and provide proper type mismatch error. 72 | 73 | #### 3.6.0 - 2020-05-19 74 | * Add Sql.executeRow(Async) and Sql.iter(Async) to the analysis 75 | 76 | #### 3.5.0 - 2020-05-19 77 | * Expand syntactic analysis to include searching through (async) computation expressions 78 | 79 | #### 3.4.0 - 2020-05-19 80 | * Search through nested modules and nested recursively 81 | * Configure the connection string of the analyzer via a local file 82 | 83 | #### 3.3.0 - 2020-04-17 84 | * Update FSharp.Analyzers SDK 0.4.0 -> 0.4.1 85 | 86 | #### 3.2.0 - 2020-03-05 87 | * Update FSharp.Analyzers SDK 0.3.1 -> 0.4.0 with named analyzers. 88 | 89 | #### 3.1.0 - 2020-03-05 90 | * Update FSharp.Analyzers SDK and compiler services to align types. 91 | 92 | #### 3.0.0 - 2020-02-26 93 | * Update for Npgsql.FSharp 3.x to be able to analyze column reading functions as `{type}OrNone` instead of `{type}OrNull` 94 | 95 | #### 2.0.0 - 2020-02-24 96 | * Update for Npgsql.FSharp 2.x 97 | * Detect incorrect parameter type with code fixes 98 | * Detect redundant parameters 99 | * Detect nullable types and and suggest using proper function that handle null values 100 | 101 | #### 1.9.0 - 2020-02-20 102 | * Optimize number of database calls by reusing the database schema on each invokation 103 | * Detect redundant query parameters in a clear warning message 104 | * Provide code fixes and better suggestions for mismatched query parameters 105 | * Remove duplicate messages about missing parameters 106 | * Refactor and simplify parts of `InformatioSchema` and `SqlAnalysis` 107 | 108 | #### 1.8.0 - 2020-02-19 109 | * Provide column name fix suggestions when reading an unknown column 110 | 111 | #### 1.7.0 - 2020-02-19 112 | * Read parameters as soon as they written and implement proper code fixes. 113 | 114 | #### 1.6.0 - 2020-02-19 115 | * Read queries as soon as they written without expecting `Sql.executeReader` 116 | 117 | #### 1.5.0 - 2020-02-19 118 | * Improved syntactic F# analysis in `Sql` module is used in combination with other generic functions 119 | 120 | #### 1.4.0 - 2020-02-18 121 | * Enable reading `[]` queries from the same module and add docs 122 | 123 | #### 1.3.0 - 2020-02-18 124 | * Detect type-mismatch when reading columns of type 'bool' from the database. Simplify parameter mismatch when there is only one parameter. 125 | 126 | #### 1.2.0 - 2020-02-18 127 | * Remove warning when there is no query provided (to avoid making a bother-ware analyzer) 128 | 129 | #### 1.1.0 - 2020-02-17 130 | * Proper packaging that includes third-party dependencies required for dynamic loading 131 | 132 | #### 1.0.0 - 2020-02-17 133 | * Initial release with working SQL analysis including syntax and type-checking 134 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - git config --global core.autocrlf input 3 | build_script: 4 | - cmd: build.cmd 5 | test: off 6 | version: 0.0.1.{build} 7 | image: Visual Studio 2019 8 | install: 9 | - cmd: choco install dotnetcore-sdk -y 10 | artifacts: 11 | - path: bin 12 | name: bin 13 | - path: dist 14 | name: dist 15 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | echo Restoring dotnet tools... 2 | dotnet tool restore 3 | 4 | dotnet fake build -t %* 5 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | echo "Restoring dotnet tools..." 7 | dotnet tool restore 8 | 9 | PAKET_SKIP_RESTORE_TARGETS=true FAKE_DETAILED_ERRORS=true dotnet fake build -t "$@" 10 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | pg: 5 | image: postgres:12 6 | restart: always 7 | environment: 8 | POSTGRES_PASSWORD: "postgres" 9 | POSTGRES_USER: "postgres" 10 | ports: 11 | - "5432:5432" 12 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://www.nuget.org/api/v2 2 | source https://api.nuget.org/v3/index.json 3 | storage: none 4 | lowest_matching: true 5 | clitool dotnet-mono 0.5.2 6 | nuget Microsoft.SourceLink.GitHub prerelease copy_local: true 7 | nuget Microsoft.NETFramework.ReferenceAssemblies copy_local: true 8 | nuget Expecto 8.13.1 9 | nuget YoloDev.Expecto.TestSdk 0.8.0 10 | nuget Microsoft.NET.Test.Sdk 15.7.2 11 | nuget altcover ~> 6 12 | nuget FSharp.Analyzers.SDK 0.8.0 13 | nuget Npgsql 14 | nuget ThrowawayDb.Postgres 15 | nuget Npgsql.FSharp ~> 4.0 16 | nuget F23.StringSimilarity 17 | 18 | // [ FAKE GROUP ] 19 | group Build 20 | storage: none 21 | source https://www.nuget.org/api/v2 22 | source https://api.nuget.org/v3/index.json 23 | nuget Fake.IO.FileSystem 5.19.0 24 | nuget Fake.Core.Target 5.19.0 25 | nuget Fake.Core.ReleaseNotes 5.19.0 26 | nuget FAKE.Core.Environment 5.19.0 27 | nuget Fake.DotNet.Cli 5.19.0 28 | nuget FAKE.Core.Process 5.19.0 29 | nuget Fake.DotNet.AssemblyInfoFile 5.19.0 30 | nuget Fake.Tools.Git 5.19.0 31 | nuget Fake.DotNet.Paket 5.19.0 32 | nuget Fake.Api.GitHub 5.19.0 33 | nuget Fake.BuildServer.AppVeyor 5.19.0 34 | nuget Fake.BuildServer.Travis 5.19.0 35 | nuget Fantomas 3.1.0 36 | nuget Argu 37 | 38 | group Docs 39 | storage: none 40 | source https://www.nuget.org/api/v2 41 | source https://api.nuget.org/v3/index.json 42 | nuget Argu 43 | nuget FSharp.Core 44 | nuget Fake.IO.FileSystem 45 | nuget FAKE.Core.Environment 46 | nuget Fake.DotNet.Cli 47 | nuget FSharp.Literate 48 | nuget Fable.React 49 | nuget Microsoft.AspNetCore.StaticFiles 50 | nuget Microsoft.AspNetCore.Hosting 51 | nuget Microsoft.AspNetCore.Server.Kestrel 52 | nuget Microsoft.AspNetCore.WebSockets 53 | nuget Dotnet.ProjInfo.Workspace.FCS 54 | -------------------------------------------------------------------------------- /sql.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zaid-Ajaj/Npgsql.FSharp.Analyzer/798ab4fe989690688d61ed1196ca79bb4e1aca37/sql.gif -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | false 5 | 6 | true 7 | true 8 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/FParsec/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "FParsec" 17 | let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" 18 | let [] AssemblyVersion = "3.26.1" 19 | let [] AssemblyMetadata_ReleaseDate = "2021-05-06T00:00:00.0000000" 20 | let [] AssemblyFileVersion = "3.26.1" 21 | let [] AssemblyInformationalVersion = "3.26.1" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "237f46ee0d5505c9adc4e1615dd231b89cb1b992" 24 | -------------------------------------------------------------------------------- /src/FParsec/Error.fsi: -------------------------------------------------------------------------------- 1 | // Copyright (c) Stephan Tolksdorf 2007-2011 2 | // License: Simplified BSD License. See accompanying documentation. 3 | 4 | [] 5 | module FParsec.Error 6 | 7 | type Expected = ErrorMessage.Expected 8 | type ExpectedString = ErrorMessage.ExpectedString 9 | type ExpectedStringCI = ErrorMessage.ExpectedCaseInsensitiveString 10 | type Unexpected = ErrorMessage.Unexpected 11 | type UnexpectedString = ErrorMessage.UnexpectedString 12 | type UnexpectedStringCI = ErrorMessage.UnexpectedCaseInsensitiveString 13 | type Message = ErrorMessage.Message 14 | type NestedError = ErrorMessage.NestedError 15 | type CompoundError = ErrorMessage.CompoundError 16 | type OtherErrorMessage = ErrorMessage.Other 17 | 18 | val (|Expected|_|): ErrorMessage -> string option 19 | val (|ExpectedString|_|): ErrorMessage -> string option 20 | val (|ExpectedStringCI|_|): ErrorMessage -> string option 21 | val (|Unexpected|_|): ErrorMessage -> string option 22 | val (|UnexpectedString|_|): ErrorMessage -> string option 23 | val (|UnexpectedStringCI|_|): ErrorMessage -> string option 24 | val (|Message|_|): ErrorMessage -> string option 25 | val (|NestedError|_|): ErrorMessage -> (Position * obj * ErrorMessageList) option 26 | val (|CompoundError|_|): ErrorMessage -> (string * Position * obj * ErrorMessageList) option 27 | val (|OtherErrorMessage|_|): ErrorMessage -> obj option 28 | 29 | [] 30 | val NoErrorMessages: ErrorMessageList = null;; 31 | val (|ErrorMessageList|NoErrorMessages|): ErrorMessageList -> Choice 32 | 33 | val inline isSingleErrorMessageOfType: ErrorMessageType -> ErrorMessageList -> bool 34 | 35 | /// `expectedError label` creates an `ErrorMessageList` with a single `Expected label` message. 36 | val expected: string -> ErrorMessageList 37 | /// `expectedStringError str` creates an `ErrorMessageList` with a single `ExpectedString str` message. 38 | val expectedString: string -> ErrorMessageList 39 | /// `expectedStringCIError str` creates an `ErrorMessageList` with a single `ExpectedStringCI str` message. 40 | val expectedStringCI: string -> ErrorMessageList 41 | 42 | /// `unexpectedError label` creates an `ErrorMessageList` with a single `Unexpected label` message. 43 | val unexpected: string -> ErrorMessageList 44 | /// `unexpectedStringError str` creates an `ErrorMessageList` with a single `UnexpectedString str` message. 45 | val unexpectedString: string -> ErrorMessageList 46 | /// `unexpectedStringCIError str` creates an `ErrorMessageList` with a single `UnexpectedStringCI str` message. 47 | val unexpectedStringCI: string -> ErrorMessageList 48 | 49 | /// `messageError msg` creates an `ErrorMessageList` with a single `Message msg` message. 50 | val messageError: string -> ErrorMessageList 51 | 52 | /// `otherError o` creates an `ErrorMessageList` with a single `OtherError o` message. 53 | val otherError: obj -> ErrorMessageList 54 | 55 | /// `backtrackError stream msgs` creates an `ErrorMessageList` with a single `BacktrackPoint stream.Position msgs` message, 56 | /// except if `msgs` is already an `ErrorMessageList` with a single `BacktrackPoint(_, _)` message, 57 | /// in which case `msgs` is returned instead. 58 | val nestedError: CharStream<'u> -> ErrorMessageList -> ErrorMessageList 59 | 60 | /// `compoundError label state msgs` creates an `ErrorMessageList` with a single `CompoundError label stream.Position msgs` message, 61 | /// except if `msgs` is an `ErrorMessageList` with a single `BacktrackPoint(pos2, msgs2)` message, 62 | /// in which case an `ErrorMessageList` with a single `CompoundError label pos2 msgs2` message is returned instead. 63 | val compoundError: string -> CharStream<'u> -> ErrorMessageList -> ErrorMessageList 64 | 65 | /// `mergeErrors error1 error2` is equivalent to `ErrorMessageList.Merge(error1, error2)`. 66 | val 67 | #if NOINLINE 68 | #else 69 | inline 70 | #endif 71 | mergeErrors: ErrorMessageList -> ErrorMessageList -> ErrorMessageList 72 | 73 | /// Represents a simple container type that brings together the position, user state and error messages of a parser error. 74 | [] 75 | type ParserError = 76 | new: Position * userState:obj * ErrorMessageList -> ParserError 77 | 78 | member Position: Position 79 | member UserState: obj 80 | member Messages: ErrorMessageList 81 | 82 | /// Returns a string representation of the `ParserError`. 83 | override ToString: unit -> string 84 | 85 | /// Returns a string representation of the `ParserError`. 86 | /// 87 | /// The given `CharStream` must contain the content of the original `CharStream` 88 | /// for which this `ParserError` was generated (at the original indices). 89 | /// 90 | /// For each error location the printed position information is augmented 91 | /// with the line of text surrounding the error position, together with a '^'-marker 92 | /// pointing to the exact location of the error in the input stream. 93 | member ToString: streamWhereErrorOccurred: CharStream<'u> -> string 94 | 95 | /// Writes a string representation of the `ParserError` to the given `TextWriter` value. 96 | /// 97 | /// The given `CharStream` must contain the content of the original `CharStream` 98 | /// for which this `ParserError` was generated (at the original indices). 99 | /// 100 | /// For each error location the printed position information is augmented 101 | /// with the line of text surrounding the error position, together with a '^'-marker 102 | /// pointing to the exact location of the error in the input stream. 103 | member WriteTo: textWriter: System.IO.TextWriter 104 | * streamWhereErrorOccurred: CharStream<'u> 105 | * ?tabSize: int 106 | * ?columnWidth: int 107 | * ?initialIndention: string * ?indentionIncrement: string 108 | -> unit 109 | 110 | /// Writes a string representation of the `ParserError` to the given `TextWriter` value. 111 | /// 112 | /// For each error position `getStreamByName` is called with the `StreamName` of the `Position`. 113 | /// The returned `CharStream` must be `null` or contain the content of the `CharStream` for which 114 | /// the error was generated (at the original indices). 115 | /// 116 | /// If `getStreamByName` returns a non-null `CharStream`, the printed error position information is 117 | /// augmented with the line of text surrounding the error position, together with a '^'-marker 118 | /// pointing to the exact location of the error in the input stream. 119 | member WriteTo: textWriter: System.IO.TextWriter 120 | * getStream: (Position -> CharStream<'u>) 121 | * ?tabSize: int 122 | * ?columnWidth: int 123 | * ?initialIndention: string * ?indentionIncrement: string 124 | -> unit 125 | 126 | /// Writes a string representation of the `ParserError` to the given `TextWriter` value. 127 | /// 128 | /// The format of the position information can be customized by specifying the `positionPrinter` 129 | /// argument. The given function is expected to print a representation of the passed `Position` value 130 | /// to the passed `TextWriter` value. If possible, it should indent text lines with the passed string 131 | /// and take into account the maximum column count (including indention) passed as the last argument. 132 | member WriteTo: textWriter: System.IO.TextWriter 133 | * ?positionPrinter: (System.IO.TextWriter -> Position -> string -> int -> unit) 134 | * ?columnWidth: int 135 | * ?initialIndention: string * ?indentionIncrement: string 136 | -> unit 137 | 138 | override Equals: obj -> bool 139 | override GetHashCode: unit -> int 140 | 141 | 142 | val inline internal raiseInfiniteLoopException: string -> CharStream -> 'a -------------------------------------------------------------------------------- /src/FParsec/FParsec.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | LOW_TRUST 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/FParsec/StaticMapping.fsi: -------------------------------------------------------------------------------- 1 | // Copyright (c) Stephan Tolksdorf 2010-2011 2 | // License: Simplified BSD License. See accompanying documentation. 3 | 4 | module FParsec.StaticMapping 5 | 6 | #if LOW_TRUST 7 | #else 8 | 9 | /// `createStaticCharIndicatorFunction invert charsInSet` 10 | /// creates an optimized indicator function for the chars specified by the `charsInSet` sequence. 11 | /// If `invert` is `false` (`true`), the returned indicator function will return `true` (`false`) 12 | /// if and only if it is called with a char contained in `charsInSet`. 13 | val createStaticCharIndicatorFunction: 14 | invert: bool -> charsInSet: seq -> (char -> bool) 15 | 16 | /// `createStaticCharRangeIndicatorFunction invert rangesInSet` 17 | /// creates an optimized indicator function for the chars in the ranges specified by the `rangesInSet` sequence. 18 | /// If `invert` is `false` (`true`), the returned indicator function will return `true` (`false`) if and only if it is 19 | /// called with a char contained in at least one of the ranges of `rangesInSet`. 20 | val createStaticCharRangeIndicatorFunction: 21 | invert: bool -> rangesInSet: seq -> (char -> bool) 22 | 23 | /// `createStaticIntIndicatorFunction invert valuesInSet` 24 | /// creates an optimized indicator function for the integers specified by the `valuesInSet` sequence. 25 | /// If `invert` is `false` (`true`), the returned indicator function will return `true` (`false`) if and only if it is 26 | /// called with an integer contained in `valuesInSet`. 27 | val createStaticIntIndicatorFunction: 28 | invert: bool -> valuesInSet: seq -> (int -> bool) 29 | 30 | /// `createStaticIntRangeIndicatorFunction invert rangesInSet` 31 | /// creates an optimized indicator function for the integers in the ranges specified by the `rangesInSet` sequence. 32 | /// If `invert` is `false` (`true`), the returned indicator function will return `true` (`false`) if and only if it is 33 | /// called with an `int` contained in at least one of the ranges of `rangesInSet`. 34 | val createStaticIntRangeIndicatorFunction: 35 | invert: bool -> rangesInSet: seq -> (int -> bool) 36 | 37 | /// `createStaticIntMapping defaultValue keyValues` 38 | /// creates an optimized mapping function that maps integer keys to values. 39 | /// The `keyValues` sequence specifies the key-value pairs for the mapping. 40 | /// All keys not specified in `keyValues` are mapped to `defaultValue`. 41 | val createStaticIntMapping: 42 | defaultValue: 'T -> keyValues: #seq -> (int -> 'T) 43 | 44 | /// `createStaticIntRangeMapping defaultValue keyValues` 45 | /// creates an optimized mapping function that maps integer key ranges to values. 46 | /// The `keyValues` sequence specifies the range-value pairs for the mapping. 47 | /// All keys not contained in one of the ranges in `keyValues` are mapped to `defaultValue`. 48 | val createStaticIntRangeMapping: 49 | defaultValue: 'T -> keyValues: #seq -> (int -> 'T) 50 | 51 | /// `createStaticStringMapping defaultValue keyValues` 52 | /// creates an optimized mapping function that maps string keys to values. 53 | /// The `keyValues` sequence specifies the key-value pairs for the mapping. 54 | /// All keys not specified in `keyValues` are mapped to `defaultValue`. A `null` key is not supported. 55 | val createStaticStringMapping: 56 | defaultValue: 'T -> keyValues: #seq -> (string -> 'T) 57 | 58 | 59 | val internal filterOutDefaultValueRanges: 60 | comparer: System.Collections.Generic.EqualityComparer<'T> 61 | -> ranges: Range[] 62 | -> values: 'T[] 63 | -> defaultValue: 'T 64 | -> Range[]*'T[] 65 | 66 | val internal createStaticIntIndicatorFunctionImpl<'TInt when 'TInt : struct> : 67 | lengthCap: int -> densityThreshold: double 68 | -> minValue: int -> maxValue: int 69 | -> invert: bool -> ranges: Range[] 70 | -> ('TInt -> bool) 71 | 72 | val internal createStaticIntMappingImpl: 73 | lengthCap: int -> densityThreshold: double 74 | -> minKey: int -> maxKey: int 75 | -> defaultValue: 'T -> ranges: Range[] -> values: 'T[] 76 | -> (int -> 'T) 77 | 78 | #endif -------------------------------------------------------------------------------- /src/FParsecCS/Buffer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Stephan Tolksdorf 2007-2010 2 | // License: Simplified BSD License. See accompanying documentation. 3 | 4 | using System; 5 | 6 | using System.Diagnostics; 7 | 8 | namespace FParsec { 9 | 10 | public static class Buffer { 11 | 12 | #if !LOW_TRUST 13 | 14 | /// Calculates: end - begin.
15 | /// Precondition: 2^31 > end - begin >= 0.
16 | internal static unsafe uint PositiveDistance(char* begin, char* end) { 17 | return (uint)((byte*)end - (byte*)begin)/2; 18 | } 19 | 20 | /// Calculates: end - begin.
21 | /// Precondition: end - begin >= 0.
22 | internal static unsafe long PositiveDistance64(char* begin, char* end) { 23 | return (long)((ulong)((byte*)end - (byte*)begin)/2); 24 | } 25 | 26 | // Probably for pedagogical reasons there is no System.Buffer.BlockCopy 27 | // that takes pointers, hence we are forced to write our own version. 28 | 29 | /// Copies size bytes from src to dst. Correctly handles overlapped memory blocks. 30 | static internal unsafe void Copy(byte* dst, byte* src, int size) { 31 | if (size < 0) throw new ArgumentOutOfRangeException("size", "The size must be non-negative."); 32 | 33 | // C# doesn't support native ints and the 32-bit .NET JIT can't optimize the 34 | // 64-comparison into a single 32-bit one, so we have to get our hands dirty... 35 | 36 | // goto Reverse if src < dst && dst - src < size 37 | if (sizeof(IntPtr) == 4) { 38 | if (unchecked((uint)(dst - src)) < (uint)size) goto Reverse; 39 | } else { 40 | if (unchecked((ulong)(dst - src)) < (ulong)size) goto Reverse; 41 | } 42 | 43 | #if UNALIGNED_READS 44 | // with UNALIGNED_READS we don't require identical 2-byte alignment 45 | if (((uint)dst & 1) == ((uint)src & 1)) { 46 | #else 47 | if (((uint)dst & 3) == ((uint)src & 3)) { 48 | #endif 49 | // the pointers have identical byte (and 2-byte) alignment 50 | 51 | // align dst 52 | if (((uint)dst & 1) != 0 && size != 0) { 53 | *dst = *src; 54 | ++src; ++dst; --size; 55 | } 56 | if (((uint)dst & 2) != 0 && size >= 2) { 57 | *((short*)dst) = *((short*)src); 58 | src += 2; dst += 2;; size -= 2; 59 | } 60 | 61 | for (; size >= 16; size -= 16) { 62 | ((int*)dst)[0] = ((int*)src)[0]; 63 | ((int*)dst)[1] = ((int*)src)[1]; 64 | ((int*)dst)[2] = ((int*)src)[2]; 65 | ((int*)dst)[3] = ((int*)src)[3]; 66 | src += 16; dst += 16; 67 | } 68 | if ((size != 0)) { 69 | if ((size & 8) != 0) { 70 | ((int*)dst)[0] = ((int*)src)[0]; 71 | ((int*)dst)[1] = ((int*)src)[1]; 72 | src += 8; dst += 8; 73 | } 74 | if ((size & 4) != 0) { 75 | *((int*)dst) = *((int*)src); 76 | src += 4; dst += 4; 77 | } 78 | if ((size & 2) != 0) { 79 | *((short*)dst) = *((short*)src); 80 | src += 2; dst += 2; 81 | } 82 | if ((size & 1) != 0) { 83 | *dst = *src; 84 | } 85 | } 86 | return; 87 | } else { 88 | // backup path for pointers with different byte (or 2-byte) alignment 89 | for (; size != 0; --size) { 90 | *dst = *src; 91 | ++src; ++dst; 92 | } 93 | return; 94 | } 95 | 96 | Reverse: 97 | src += size; dst += size; 98 | #if UNALIGNED_READS 99 | // with UNALIGNED_READS we don't require identical 2-byte alignment 100 | if (((uint)dst & 1) == ((uint)src & 1)) { 101 | #else 102 | if (((uint)dst & 3) == ((uint)src & 3)) { 103 | #endif 104 | // the pointers have identical byte (and 2-byte) alignment 105 | 106 | // align dst 107 | if (((uint)dst & 1) != 0 && size != 0) { 108 | --src; --dst; --size; 109 | *dst = *src; 110 | } 111 | if (((uint)dst & 2) != 0 && size >= 2) { 112 | src -= 2; dst -= 2; size -= 2; 113 | *((short*)dst) = *((short*)src); 114 | } 115 | for (; size >= 16; size -= 16) { 116 | src -= 16; dst -= 16; 117 | ((int*)dst)[3] = ((int*)src)[3]; 118 | ((int*)dst)[2] = ((int*)src)[2]; 119 | ((int*)dst)[1] = ((int*)src)[1]; 120 | ((int*)dst)[0] = ((int*)src)[0]; 121 | } 122 | if ((size & 0xf) != 0) { 123 | if ((size & 8) != 0) { 124 | src -= 8; dst -= 8; 125 | ((int*)dst)[1] = ((int*)src)[1]; 126 | ((int*)dst)[0] = ((int*)src)[0]; 127 | } 128 | if ((size & 4) != 0) { 129 | src -= 4; dst -= 4; 130 | *((int*)dst) = *((int*)src); 131 | } 132 | if ((size & 2) != 0) { 133 | src -= 2; dst -= 2; 134 | *((short*)dst) = *((short*)src); 135 | } 136 | if ((size & 1) != 0) { 137 | src -= 1; dst -= 1; 138 | *dst = *src; 139 | } 140 | } 141 | return; 142 | } else { 143 | // backup path for pointers with different byte (or 2-byte) alignment 144 | for (; size != 0; --size) { 145 | --src; --dst; 146 | *dst = *src; 147 | } 148 | return; 149 | } 150 | } 151 | 152 | #endif 153 | 154 | internal static uint SwapByteOrder(uint value) { 155 | return (((value << 24) | (value >> 8)) & 0xff00ff00U) 156 | | (((value << 8) | (value >> 24)) & 0x00ff00ffU); 157 | } 158 | 159 | internal static ulong SwapByteOrder(ulong value) { 160 | return (((value << 56) | (value >> 8)) & 0xff000000ff000000UL) 161 | | (((value << 8) | (value >> 56)) & 0x000000ff000000ffUL) 162 | | (((value << 40) | (value >> 24)) & 0x00ff000000ff0000UL) 163 | | (((value << 24) | (value >> 40)) & 0x0000ff000000ff00UL); 164 | } 165 | 166 | internal static void SwapByteOrder(uint[] array) { 167 | for (int i = 0; i < array.Length; ++i) { 168 | var v = array[i]; 169 | array[i] = (((v << 24) | (v >> 8)) & 0xff00ff00U) 170 | | (((v << 8) | (v >> 24)) & 0x00ff00ffU); 171 | } 172 | } 173 | 174 | #if !LOW_TRUST 175 | 176 | internal static unsafe void SwapByteOrder(uint* buffer, uint length) { 177 | for (int i = 0; i < length; ++i) { 178 | var v = buffer[i]; 179 | buffer[i] = (((v << 24) | (v >> 8)) & 0xff00ff00U) 180 | | (((v << 8) | (v >> 24)) & 0x00ff00ffU); 181 | } 182 | } 183 | 184 | #endif 185 | 186 | #if LOW_TRUST 187 | 188 | internal static byte[] CopySubarray(byte[] array, int index, int length) { 189 | var subArray = new byte[length]; 190 | System.Buffer.BlockCopy(array, index, subArray, 0, length); 191 | return subArray; 192 | } 193 | 194 | internal static uint[] CopyUIntsStoredInLittleEndianByteArray(byte[] src, int srcIndex, int srcLength) { 195 | Debug.Assert(srcLength%sizeof(uint) == 0); 196 | var subArray = new uint[srcLength/sizeof(uint)]; 197 | System.Buffer.BlockCopy(src, srcIndex, subArray, 0, srcLength); 198 | if (!BitConverter.IsLittleEndian) SwapByteOrder(subArray); 199 | return subArray; 200 | } 201 | 202 | #endif 203 | 204 | #if !LOW_TRUST 205 | // used by StaticMapping.createStaticStringMapping 206 | public static unsafe bool Equals(uint* ptr1, uint* ptr2, uint length) { 207 | Debug.Assert(length >= 0); 208 | for (; length >= 4; length -= 4) { 209 | if ( ptr1[0] != ptr2[0] 210 | || ptr1[1] != ptr2[1] 211 | || ptr1[2] != ptr2[2] 212 | || ptr1[3] != ptr2[3]) goto ReturnFalse; 213 | ptr1 += 4; 214 | ptr2 += 4; 215 | } 216 | if ((length & 2) != 0) { 217 | if ( ptr1[0] != ptr2[0] 218 | || ptr1[1] != ptr2[1]) goto ReturnFalse; 219 | ptr1 += 2; 220 | ptr2 += 2; 221 | } 222 | if ((length & 1) != 0) { 223 | if (ptr1[0] != ptr2[0]) goto ReturnFalse; 224 | } 225 | return true; 226 | ReturnFalse: 227 | return false; 228 | } 229 | #endif 230 | 231 | } 232 | 233 | } -------------------------------------------------------------------------------- /src/FParsecCS/CharSet.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Stephan Tolksdorf 2008-2010 2 | // License: Simplified BSD License. See accompanying documentation. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | 7 | namespace FParsec { 8 | 9 | #if !LOW_TRUST 10 | unsafe 11 | #endif 12 | internal sealed class CharSet { 13 | private const int WordSize = 32; 14 | private const int Log2WordSize = 5; 15 | 16 | private int Min; 17 | private int Max; 18 | private int BitTableMin; 19 | private int[] BitTable; 20 | private string CharsNotInBitTable; // We use a string here instead of a char[] because the 21 | // .NET JITs tend to produce better code for loops involving strings. 22 | 23 | public CharSet(string chars) : this(chars, 32) {} 24 | // because of mandatory bounds checking, we wouldn't get any advantage from a fixed size table 25 | 26 | public CharSet(string chars, int maxTableSize) { 27 | if (chars.Length == 0) { 28 | BitTableMin = Min = 0x10000; 29 | Max = -1; 30 | BitTable = new int[0]; 31 | // charsNotInTable = null; 32 | return; 33 | } 34 | if (maxTableSize < 4) maxTableSize = 4; 35 | else if (maxTableSize > 0x10000/WordSize) maxTableSize = 0x10000/WordSize; 36 | int maxTableBits = maxTableSize*WordSize; 37 | 38 | char prevChar = chars[0]; 39 | Min = prevChar; 40 | Max = prevChar; 41 | BitTableMin = -1; 42 | int bitTableMax = -1; 43 | int nCharsNotInTable = 0; 44 | for (int i = 1; i < chars.Length; ++i) { 45 | char c = chars[i]; 46 | if (c == prevChar) continue; // filter out repeated chars 47 | prevChar = c; 48 | int prevMin = Min; 49 | if (c < Min) Min = c; 50 | int prevMax = Max; 51 | if (c > Max) Max = c; 52 | if (BitTableMin < 0) { 53 | // the first time the table range is exceeded the tableMin is set 54 | if (Max - Min >= maxTableBits) { 55 | BitTableMin = prevMin; // stays fixed 56 | bitTableMax = prevMax; // will be updated later 57 | nCharsNotInTable = 1; 58 | } 59 | } else if (c < BitTableMin || c >= BitTableMin + maxTableBits) { 60 | ++nCharsNotInTable; 61 | } else { 62 | bitTableMax = Math.Max(c, bitTableMax); 63 | } 64 | } 65 | if (BitTableMin < 0) { 66 | BitTableMin = Min; 67 | bitTableMax = Max; 68 | } 69 | int tableSize = bitTableMax - BitTableMin + 1 < maxTableBits 70 | ? (bitTableMax - BitTableMin + 1)/WordSize + ((bitTableMax - BitTableMin + 1)%WordSize != 0 ? 1 : 0) 71 | : maxTableSize; 72 | BitTable = new int[tableSize]; 73 | 74 | #if LOW_TRUST 75 | var notInTable = nCharsNotInTable > 0 ? new char[nCharsNotInTable] : null; 76 | #else 77 | CharsNotInBitTable = nCharsNotInTable > 0 ? new string('\u0000', nCharsNotInTable) : ""; 78 | fixed (char* notInTable = CharsNotInBitTable) { 79 | #endif 80 | prevChar = chars[0] != 'x' ? 'x' : 'y'; 81 | int n = 0; 82 | for (int i = 0; i < chars.Length; ++i) { 83 | char c = chars[i]; 84 | if (c == prevChar) continue; 85 | prevChar = c; 86 | int off = c - BitTableMin; 87 | int idx = off >> Log2WordSize; 88 | if (unchecked((uint)idx) < (uint)BitTable.Length) { 89 | BitTable[idx] |= 1 << off; // we don't need to mask off because C#'s operator<< does that for us 90 | } else { 91 | notInTable[n++] = c; 92 | } 93 | } 94 | Debug.Assert(n == nCharsNotInTable); 95 | #if !LOW_TRUST 96 | } 97 | #else 98 | if (nCharsNotInTable > 0) CharsNotInBitTable = new string(notInTable); 99 | #endif 100 | } 101 | 102 | public bool Contains(char value) { 103 | int off = value - BitTableMin; 104 | int idx = off >> Log2WordSize; 105 | if (unchecked((uint)idx) < (uint)BitTable.Length) { 106 | return ((BitTable[idx] >> off) & 1) != 0; // we don't need to mask off because C#'s operator>> does that for us 107 | } 108 | if (CharsNotInBitTable == null) return false; 109 | if (value >= Min && value <= Max) { 110 | foreach (char c in CharsNotInBitTable) { 111 | if (c == value) goto ReturnTrue; 112 | } 113 | } 114 | return false; 115 | ReturnTrue: 116 | return true; 117 | } 118 | } 119 | 120 | } -------------------------------------------------------------------------------- /src/FParsecCS/ErrorMessageList.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Stephan Tolksdorf 2010 2 | // License: Simplified BSD License. See accompanying documentation. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.Collections.Generic; 7 | 8 | namespace FParsec { 9 | 10 | [DebuggerDisplay("{ErrorMessageList.GetDebuggerDisplay(this),nq}"), 11 | DebuggerTypeProxy(typeof(ErrorMessageList.DebugView))] 12 | public sealed class ErrorMessageList : IEquatable { 13 | public readonly ErrorMessage Head; 14 | public readonly ErrorMessageList Tail; 15 | 16 | public ErrorMessageList(ErrorMessage head, ErrorMessageList tail) { 17 | var throwNullReferenceExceptionIfHeadIsNull = head.Type; 18 | Head = head; 19 | Tail = tail; 20 | } 21 | 22 | public ErrorMessageList(ErrorMessage message) { 23 | var throwNullReferenceExceptionIfMessageIsNull = message.Type; 24 | Head = message; 25 | } 26 | 27 | public ErrorMessageList(ErrorMessage message1, ErrorMessage message2) { 28 | var throwNullReferenceExceptionIfMessage1IsNull = message1.Type; 29 | Head = message1; 30 | Tail = new ErrorMessageList(message2); 31 | } 32 | 33 | public static ErrorMessageList Merge(ErrorMessageList list1, ErrorMessageList list2) { 34 | if ((object)list1 == null) return list2; 35 | return MergeContinue(list1, list2); 36 | } 37 | private static ErrorMessageList MergeContinue(ErrorMessageList list1, ErrorMessageList list2) { 38 | while ((object)list2 != null) { 39 | list1 = new ErrorMessageList(list2.Head, list1); 40 | list2 = list2.Tail; 41 | } 42 | return list1; 43 | } 44 | 45 | public static HashSet ToHashSet(ErrorMessageList messages) { 46 | var msgs = messages; 47 | var set = new HashSet(); 48 | for (; (object)msgs != null; msgs = msgs.Tail) { 49 | var msg = msgs.Head; 50 | Debug.Assert(msg.Type >= 0); 51 | if (msg.Type <= ErrorMessageType.Message && string.IsNullOrEmpty(msg.String)) continue; 52 | set.Add(msg); 53 | } 54 | return set; 55 | } 56 | 57 | public static ErrorMessage[] ToSortedArray(ErrorMessageList messages) { 58 | var set = ToHashSet(messages); 59 | var array = new ErrorMessage[set.Count]; 60 | set.CopyTo(array); 61 | Array.Sort(array, ErrorMessage.Comparer); 62 | return array; 63 | } 64 | 65 | public override bool Equals(object obj) { return Equals(obj as ErrorMessageList); } 66 | 67 | public bool Equals(ErrorMessageList other) { 68 | return (object)this == (object)other 69 | || ( (object)other != null 70 | && ToHashSet(this).SetEquals(ToHashSet(other))); 71 | } 72 | 73 | public static bool operator==(ErrorMessageList left, ErrorMessageList right) { 74 | return (object)left == (object)right 75 | || ( (object)left != null 76 | && (object)right != null 77 | && ToHashSet(left).SetEquals(ToHashSet(right))); 78 | } 79 | public static bool operator!=(ErrorMessageList left, ErrorMessageList right) { return !(left == right); } 80 | 81 | public override int GetHashCode() { 82 | var set = ToHashSet(this); 83 | var h = 0; 84 | foreach (var msg in set) 85 | h ^= msg.GetHashCode(); 86 | return h; 87 | } 88 | 89 | internal static string GetDebuggerDisplay(ErrorMessageList list) { 90 | var es = ErrorMessageList.ToSortedArray(list); 91 | switch (es.Length) { 92 | case 0: return "[]"; 93 | case 1: return "[" + es[0].GetDebuggerDisplay() + "]"; 94 | case 2: return "[" + es[0].GetDebuggerDisplay() + "; " + es[1].GetDebuggerDisplay() + "]"; 95 | case 3: return "[" + es[0].GetDebuggerDisplay() + "; " + es[1].GetDebuggerDisplay() + "; " + es[2].GetDebuggerDisplay() + "]"; 96 | default: return "[" + es[0].GetDebuggerDisplay() + "; " + es[1].GetDebuggerDisplay() + "; " + es[2].GetDebuggerDisplay() + "; ...]"; 97 | } 98 | } 99 | 100 | internal class DebugView { 101 | //[DebuggerBrowsable(DebuggerBrowsableState.Never)] 102 | private ErrorMessageList List; 103 | 104 | public DebugView(ErrorMessageList list) { List = list; } 105 | 106 | [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] 107 | public ErrorMessage[] Items { get { return ErrorMessageList.ToSortedArray(List); } } 108 | } 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /src/FParsecCS/Errors.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Stephan Tolksdorf 2010-2011 2 | // License: Simplified BSD License. See accompanying documentation. 3 | 4 | using System; 5 | 6 | namespace FParsec { 7 | 8 | internal static class Errors { 9 | static private ErrorMessageList Expected(string str) { 10 | return new ErrorMessageList(new ErrorMessage.Expected(str)); 11 | } 12 | 13 | static private ErrorMessageList Unexpected(string str) { 14 | return new ErrorMessageList(new ErrorMessage.Unexpected(str)); 15 | } 16 | 17 | static private ErrorMessageList Message(string str) { 18 | return new ErrorMessageList(new ErrorMessage.Message(str)); 19 | } 20 | 21 | public static readonly ErrorMessageList ExpectedEndOfInput = Expected(Strings.EndOfInput); 22 | public static readonly ErrorMessageList UnexpectedEndOfInput = Unexpected(Strings.EndOfInput); 23 | 24 | public static readonly ErrorMessageList ExpectedAnyChar = Expected(Strings.AnyChar); 25 | public static readonly ErrorMessageList ExpectedWhitespace = Expected(Strings.Whitespace); 26 | public static readonly ErrorMessageList ExpectedAsciiUppercaseLetter = Expected(Strings.AsciiUppercaseLetter); 27 | public static readonly ErrorMessageList ExpectedAsciiLowercaseLetter = Expected(Strings.AsciiLowercaseLetter); 28 | public static readonly ErrorMessageList ExpectedAsciiLetter = Expected(Strings.AsciiLetter); 29 | public static readonly ErrorMessageList ExpectedUppercaseLetter = Expected(Strings.UppercaseLetter); 30 | public static readonly ErrorMessageList ExpectedLowercaseLetter = Expected(Strings.LowercaseLetter); 31 | public static readonly ErrorMessageList ExpectedLetter = Expected(Strings.Letter); 32 | public static readonly ErrorMessageList ExpectedBinaryDigit = Expected(Strings.BinaryDigit); 33 | public static readonly ErrorMessageList ExpectedOctalDigit = Expected(Strings.OctalDigit); 34 | public static readonly ErrorMessageList ExpectedDecimalDigit = Expected(Strings.DecimalDigit); 35 | public static readonly ErrorMessageList ExpectedHexadecimalDigit = Expected(Strings.HexadecimalDigit); 36 | 37 | public static readonly ErrorMessageList ExpectedNewline = Expected(Strings.Newline); 38 | public static readonly ErrorMessageList UnexpectedNewline = Unexpected(Strings.Newline); 39 | 40 | public static readonly ErrorMessageList ExpectedTab = Expected(Strings.Tab); 41 | 42 | public static readonly ErrorMessageList ExpectedFloatingPointNumber = Expected(Strings.FloatingPointNumber); 43 | 44 | public static readonly ErrorMessageList ExpectedInt64 = Expected(Strings.Int64); 45 | public static readonly ErrorMessageList ExpectedInt32 = Expected(Strings.Int32); 46 | public static readonly ErrorMessageList ExpectedInt16 = Expected(Strings.Int16); 47 | public static readonly ErrorMessageList ExpectedInt8 = Expected(Strings.Int8); 48 | public static readonly ErrorMessageList ExpectedUInt64 = Expected(Strings.UInt64); 49 | public static readonly ErrorMessageList ExpectedUInt32 = Expected(Strings.UInt32); 50 | public static readonly ErrorMessageList ExpectedUInt16 = Expected(Strings.UInt16); 51 | public static readonly ErrorMessageList ExpectedUInt8 = Expected(Strings.UInt8); 52 | 53 | public static readonly ErrorMessageList ExpectedPrefixOperator = Expected(Strings.PrefixOperator); 54 | public static readonly ErrorMessageList ExpectedInfixOperator = Expected(Strings.InfixOperator); 55 | public static readonly ErrorMessageList ExpectedPostfixOperator = Expected(Strings.PostfixOperator); 56 | public static readonly ErrorMessageList ExpectedInfixOrPostfixOperator = ErrorMessageList.Merge(ExpectedInfixOperator, ExpectedPostfixOperator); 57 | 58 | public static readonly ErrorMessageList NumberOutsideOfDoubleRange = Message(Strings.NumberOutsideOfDoubleRange); 59 | public static readonly ErrorMessageList NumberOutsideOfInt64Range = Message(Strings.NumberOutsideOfInt64Range); 60 | public static readonly ErrorMessageList NumberOutsideOfInt32Range = Message(Strings.NumberOutsideOfInt32Range); 61 | public static readonly ErrorMessageList NumberOutsideOfInt16Range = Message(Strings.NumberOutsideOfInt16Range); 62 | public static readonly ErrorMessageList NumberOutsideOfInt8Range = Message(Strings.NumberOutsideOfInt8Range); 63 | public static readonly ErrorMessageList NumberOutsideOfUInt64Range = Message(Strings.NumberOutsideOfUInt64Range); 64 | public static readonly ErrorMessageList NumberOutsideOfUInt32Range = Message(Strings.NumberOutsideOfUInt32Range); 65 | public static readonly ErrorMessageList NumberOutsideOfUInt16Range = Message(Strings.NumberOutsideOfUInt16Range); 66 | public static readonly ErrorMessageList NumberOutsideOfUInt8Range = Message(Strings.NumberOutsideOfUInt8Range); 67 | 68 | 69 | public static ErrorMessageList ExpectedAnyCharIn(string chars) { 70 | return Expected(Strings.AnyCharIn(chars)); 71 | } 72 | 73 | public static ErrorMessageList ExpectedAnyCharNotIn(string chars) { 74 | return Expected(Strings.AnyCharNotIn(chars)); 75 | } 76 | 77 | public static ErrorMessageList ExpectedStringMatchingRegex(string regexPattern) { 78 | return Expected(Strings.StringMatchingRegex(regexPattern)); 79 | } 80 | 81 | public static ErrorMessageList ExpectedAnySequenceOfNChars(int n) { 82 | return Expected(Strings.ExpectedAnySequenceOfNChars(n)); 83 | } 84 | 85 | public static ErrorMessageList CouldNotFindString(string str) { 86 | return Message(Strings.CouldNotFindString(str)); 87 | } 88 | 89 | public static ErrorMessageList CouldNotFindCaseInsensitiveString(string str) { 90 | return Message(Strings.CouldNotFindCaseInsensitiveString(str)); 91 | } 92 | 93 | public static ErrorMessageList OperatorsConflict(Position position1, Operator operator1, 94 | Position position2, Operator operator2) 95 | { 96 | return Message(Strings.OperatorsConflict(position1, operator1, position2, operator2)); 97 | } 98 | 99 | public static ErrorMessageList UnexpectedNonPrefixOperator(Operator op) { 100 | return new ErrorMessageList( 101 | ExpectedPrefixOperator.Head, 102 | new ErrorMessage.Unexpected(Strings.OperatorToString(op))); 103 | } 104 | 105 | public static ErrorMessageList MissingTernary2ndString(Position position1, Position position2, Operator op) { 106 | return new ErrorMessageList( 107 | new ErrorMessage.ExpectedString(op.TernaryRightString), 108 | new ErrorMessage.Message(Strings.OperatorStringIsRightPartOfTernaryOperator(position1, position2, op))); 109 | } 110 | } 111 | 112 | namespace Internal { // the internal namespace contains internal types that must be public for inlining reasons 113 | public static class ParserCombinatorInInfiniteLoopHelper { 114 | public static Exception CreateException(string combinatorName, CharStream stream) { 115 | return new InvalidOperationException(stream.Position.ToString() + ": The combinator '" + combinatorName + "' was applied to a parser that succeeds without consuming input and without changing the parser state in any other way. (If no exception had been raised, the combinator likely would have entered an infinite loop.)"); 116 | } 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/FParsecCS/FParsecCS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | true 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/FParsecCS/FastGenericEqualityERComparer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Stephan Tolksdorf 2010 2 | // License: Simplified BSD License. See accompanying documentation. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | #if PCL || NETSTANDARD1_6 9 | using System.Reflection; 10 | #endif 11 | 12 | using Microsoft.FSharp.Core; 13 | 14 | namespace FParsec { 15 | 16 | internal static class FastGenericEqualityERComparer { 17 | // if T is a reference type, accessing the field requires a hash table lookup 18 | public static EqualityComparer Instance = FastGenericEqualityERComparer.Create(); 19 | 20 | /// For reference types it's faster to call Instance.Equals directly 21 | /// (due to limitations of the inliner of the .NET JIT.) 22 | public static bool Equals(T left, T right) { 23 | return Instance.Equals(left, right); 24 | } 25 | } 26 | 27 | internal static class FastGenericEqualityERComparer { 28 | public static EqualityComparer Create() { 29 | var t = typeof(T); 30 | if (t.IsArray) return new ArrayStructuralEqualityERComparer(); 31 | #if PCL || NETSTANDARD1_6 32 | var ti = t.GetTypeInfo(); 33 | var ise = typeof(IStructuralEquatable).GetTypeInfo(); 34 | #else 35 | var ti = t; 36 | var ise = typeof(IStructuralEquatable); 37 | #endif 38 | if (ise.IsAssignableFrom(ti)) { 39 | var gct = ti.IsValueType ? typeof(StructStructuralEqualityERComparer<>) 40 | : typeof(ClassStructuralEqualityERComparer<>); 41 | var ct = gct.MakeGenericType(t); 42 | #if LOW_TRUST || NETSTANDARD1_6 43 | return (EqualityComparer)Activator.CreateInstance(ct); 44 | #else 45 | return (EqualityComparer)System.Runtime.Serialization.FormatterServices.GetUninitializedObject(ct); 46 | #endif 47 | } 48 | return EqualityComparer.Default; 49 | } 50 | 51 | private class ClassStructuralEqualityERComparer : EqualityComparer where T : class, IStructuralEquatable { 52 | public override bool Equals(T x, T y) { 53 | return (object)x == (object)y 54 | || ((object)x != null && x.Equals(y, LanguagePrimitives.GenericEqualityERComparer)); 55 | } 56 | 57 | public override int GetHashCode(T obj) { 58 | if ((object)obj == null) throw new ArgumentNullException("obj"); 59 | return obj.GetHashCode(LanguagePrimitives.GenericEqualityERComparer); 60 | } 61 | } 62 | 63 | private class StructStructuralEqualityERComparer : EqualityComparer where T : struct, IStructuralEquatable { 64 | public override bool Equals(T x, T y) { 65 | return x.Equals(y, LanguagePrimitives.GenericEqualityERComparer); 66 | } 67 | 68 | public override int GetHashCode(T obj) { 69 | return obj.GetHashCode(LanguagePrimitives.GenericEqualityERComparer); 70 | } 71 | } 72 | 73 | /// Forwards all work to F#'s GenericEqualityERComparer. 74 | private class ArrayStructuralEqualityERComparer : EqualityComparer { 75 | public override bool Equals(T x, T y) { 76 | return (object)x == (object)y || LanguagePrimitives.GenericEqualityERComparer.Equals(x, y); 77 | } 78 | 79 | public override int GetHashCode(T obj) { 80 | if ((object)obj == null) throw new ArgumentNullException("obj"); 81 | return LanguagePrimitives.GenericEqualityERComparer.GetHashCode(obj); 82 | } 83 | } 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/FParsecCS/Position.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Stephan Tolksdorf 2007-2009 2 | // License: Simplified BSD License. See accompanying documentation. 3 | 4 | using System; 5 | 6 | namespace FParsec { 7 | 8 | public sealed class Position : IEquatable, IComparable, IComparable { 9 | public long Index { get; private set; } 10 | public long Line { get; private set; } 11 | public long Column { get; private set; } 12 | public string StreamName { get; private set; } 13 | 14 | public Position(string streamName, long index, long line, long column) { 15 | StreamName = streamName; Index = index; Line = line; Column = column; 16 | } 17 | 18 | public override string ToString() { 19 | var ln = String.IsNullOrEmpty(StreamName) ? "(Ln: " : Text.Escape(StreamName, "", "(\"", "\", Ln: ", "", '"'); 20 | return ln + Line.ToString() + ", Col: " + Column.ToString() + ")"; 21 | } 22 | 23 | public override bool Equals(object obj) { 24 | return Equals(obj as Position); 25 | } 26 | public bool Equals(Position other) { 27 | return (object)this == (object)other 28 | || ( (object)other != null 29 | && Index == other.Index 30 | && Line == other.Line 31 | && Column == other.Column 32 | && StreamName == other.StreamName); 33 | } 34 | public static bool operator==(Position left, Position right) { 35 | return (object)left == null ? (object)right == null : left.Equals(right); 36 | } 37 | public static bool operator!=(Position left, Position right) { return !(left == right); } 38 | 39 | public override int GetHashCode() { 40 | return Index.GetHashCode(); 41 | } 42 | 43 | public static int Compare(Position left, Position right) { 44 | if ((object)left != null) return left.CompareTo(right); 45 | return (object)right == null ? 0 : -1; 46 | } 47 | 48 | public int CompareTo(Position other) { 49 | if ((object)this == (object)other) return 0; 50 | if ((object)other == null) return 1; 51 | int r = String.CompareOrdinal(StreamName, other.StreamName); 52 | if (r != 0) return r; 53 | r = Line.CompareTo(other.Line); 54 | if (r != 0) return r; 55 | r = Column.CompareTo(other.Column); 56 | if (r != 0) return r; 57 | return Index.CompareTo(other.Index); 58 | } 59 | int IComparable.CompareTo(object value) { 60 | Position position = value as Position; 61 | if ((object)position != null) return CompareTo(position); 62 | if (value == null) return 1; 63 | throw new ArgumentException("Object must be of type Position."); 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/FParsecCS/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: ComVisible(false)] 5 | [assembly: InternalsVisibleTo ("FParsec")] 6 | -------------------------------------------------------------------------------- /src/FParsecCS/Reply.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Stephan Tolksdorf 2008-2010 2 | // License: Simplified BSD License. See accompanying documentation. 3 | 4 | using System; 5 | 6 | namespace FParsec { 7 | 8 | public enum ReplyStatus { 9 | Ok = 1, 10 | Error = 0, 11 | FatalError = -1 12 | } 13 | 14 | [System.Diagnostics.DebuggerDisplay("{GetDebuggerDisplay(),nq}")] 15 | public struct Reply : IEquatable> { 16 | public ErrorMessageList Error; 17 | public TResult Result; 18 | public ReplyStatus Status; 19 | 20 | public Reply(TResult result) { 21 | Result = result; 22 | Error = null; 23 | Status = ReplyStatus.Ok; 24 | } 25 | 26 | public Reply(ReplyStatus status, ErrorMessageList error) { 27 | Status = status; 28 | Error = error; 29 | Result = default(TResult); 30 | } 31 | 32 | public Reply(ReplyStatus status, TResult result, ErrorMessageList error) { 33 | Status = status; 34 | Error = error; 35 | Result = result; 36 | } 37 | 38 | public override bool Equals(object other) { 39 | if (!(other is Reply)) return false; 40 | return Equals((Reply) other); 41 | } 42 | public bool Equals(Reply other) { 43 | return Status == other.Status 44 | && (Status != ReplyStatus.Ok || FastGenericEqualityERComparer.Instance.Equals(Result, other.Result)) 45 | && Error == other.Error; 46 | } 47 | public override int GetHashCode() { 48 | return (int)Status 49 | ^ (Status != ReplyStatus.Ok ? 0 : FastGenericEqualityERComparer.Instance.GetHashCode(Result)); 50 | } 51 | public static bool operator==(Reply r1, Reply r2) { return r1.Equals(r2); } 52 | public static bool operator!=(Reply r1, Reply r2) { return !r1.Equals(r2); } 53 | 54 | private string GetDebuggerDisplay() { 55 | if (Status == ReplyStatus.Ok) { 56 | string result; 57 | if (Result == null) 58 | result = typeof(TResult) == typeof(Microsoft.FSharp.Core.Unit) ? "()" : "null"; 59 | else if (typeof(TResult) == typeof(string)) 60 | result = Text.DoubleQuote(Result.ToString()); 61 | else 62 | result = Result.ToString(); 63 | 64 | return Error == null 65 | ? "Reply(" + result + ")" 66 | : "Reply(Ok, " + result + ", " + ErrorMessageList.GetDebuggerDisplay(Error) + ")"; 67 | } else { 68 | var status = Status == ReplyStatus.Error ? "Error" : 69 | Status == ReplyStatus.FatalError ? "FatalError" : 70 | "(ReplyStatus)" + ((int)Status).ToString(); 71 | 72 | return Error == null 73 | ? "Reply(" + status + ", NoErrorMessages)" 74 | : "Reply(" + status + ", " + ErrorMessageList.GetDebuggerDisplay(Error) + ")"; 75 | } 76 | } 77 | } 78 | 79 | } -------------------------------------------------------------------------------- /src/FParsecCS/UnmanagedMemoryPool.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Stephan Tolksdorf 2010 2 | // License: Simplified BSD License. See accompanying documentation. 3 | 4 | #if !LOW_TRUST 5 | 6 | using System; 7 | using System.Runtime.InteropServices; 8 | using System.Collections.Generic; 9 | 10 | namespace FParsec { 11 | 12 | /// 13 | /// Allocates and keeps references to chunks of unmanaged memory that we 14 | /// intend to keep around for the lifetime of the AppDomain. 15 | /// 16 | internal sealed class UnmanagedMemoryPool { 17 | private static List Handles = new List(); 18 | 19 | static public IntPtr Allocate(int size) { 20 | lock (Handles) { 21 | var h = Marshal.AllocHGlobal(size); 22 | Handles.Add(h); 23 | return h; 24 | } 25 | } 26 | 27 | // implementation of a "static finalizer" 28 | private UnmanagedMemoryPool() { } 29 | private static readonly UnmanagedMemoryPool Instance = new UnmanagedMemoryPool(); 30 | ~UnmanagedMemoryPool() { 31 | var hs = Handles; 32 | Handles = null; 33 | foreach (var h in hs) 34 | Marshal.FreeHGlobal(h); 35 | } 36 | } 37 | 38 | } 39 | 40 | #endif -------------------------------------------------------------------------------- /src/NpgsqlFSharpAnalyzer.Core/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "NpgsqlFSharpAnalyzer.Core" 17 | let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" 18 | let [] AssemblyVersion = "3.26.1" 19 | let [] AssemblyMetadata_ReleaseDate = "2021-05-06T00:00:00.0000000" 20 | let [] AssemblyFileVersion = "3.26.1" 21 | let [] AssemblyInformationalVersion = "3.26.1" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "237f46ee0d5505c9adc4e1615dd231b89cb1b992" 24 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpAnalyzer.Core/NpgsqlFSharpAnalyzer.Core.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpAnalyzer.Core/SqlAnalyzer.fs: -------------------------------------------------------------------------------- 1 | namespace Npgsql.FSharp.Analyzers.Core 2 | 3 | open System 4 | open System.IO 5 | open System.Linq 6 | open FSharp.Compiler.SourceCodeServices 7 | 8 | module SqlAnalyzer = 9 | 10 | /// Recursively tries to find the parent of a file starting from a directory 11 | let rec findParent (directory: string) (fileToFind: string) = 12 | let path = if Directory.Exists(directory) then directory else Directory.GetParent(directory).FullName 13 | let files = Directory.GetFiles(path) 14 | if files.Any(fun file -> Path.GetFileName(file).ToLower() = fileToFind.ToLower()) 15 | then path 16 | else findParent (DirectoryInfo(path).Parent.FullName) fileToFind 17 | 18 | let getSymbols (checkResults: FSharpCheckFileResults) = 19 | checkResults.PartialAssemblySignature.Entities 20 | |> Seq.toList 21 | 22 | let checkAnswerResult checkFileAnswer = 23 | match checkFileAnswer with 24 | | FSharpCheckFileAnswer.Aborted -> None 25 | | FSharpCheckFileAnswer.Succeeded results -> Some results 26 | 27 | let tryFindConfig (file: string) = 28 | try 29 | let parent = (Directory.GetParent file).FullName 30 | try Some (Path.Combine(findParent parent "NPGSQL_FSHARP", "NPGSQL_FSHARP")) 31 | with error -> None 32 | with error -> 33 | None 34 | 35 | let tryFindConnectionString fileName = 36 | match tryFindConfig fileName with 37 | | Some config -> 38 | try (File.ReadAllText config) 39 | with error -> Environment.GetEnvironmentVariable "NPGSQL_FSHARP" 40 | | None -> 41 | Environment.GetEnvironmentVariable "NPGSQL_FSHARP" 42 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpAnalyzer.Core/Types.fs: -------------------------------------------------------------------------------- 1 | namespace Npgsql.FSharp.Analyzers.Core 2 | 3 | open FSharp.Compiler.Text 4 | open FSharp.Compiler.SyntaxTree 5 | open FSharp.Compiler.SourceCodeServices 6 | 7 | type SqlAnalyzerContext = 8 | { FileName: string 9 | Content: string[] 10 | ParseTree: ParsedInput 11 | Symbols: FSharpEntity list } 12 | 13 | type Fix = 14 | { FromRange : range 15 | FromText : string 16 | ToText : string } 17 | 18 | type Severity = 19 | | Info 20 | | Warning 21 | | Error 22 | 23 | type Message = 24 | { Type: string 25 | Message: string 26 | Code: string 27 | Severity: Severity 28 | Range: range 29 | Fixes: Fix list } 30 | 31 | with 32 | member self.IsWarning() = self.Severity = Warning 33 | member self.IsInfo() = self.Severity = Info 34 | member self.IsError() = self.Severity = Error 35 | 36 | type ColumnReadAttempt = { 37 | funcName: string; 38 | columnName: string; 39 | columnNameRange : range 40 | funcCallRange: range 41 | } 42 | 43 | type UsedParameter = { 44 | name : string 45 | range : range 46 | paramFunc : string 47 | paramFuncRange : range 48 | applicationRange : range option 49 | } 50 | 51 | type ParameterSet = { 52 | parameters : UsedParameter list 53 | range : range 54 | } 55 | 56 | type TransactionQuery = { 57 | query: string 58 | queryRange : range 59 | parameterSets : ParameterSet list 60 | } 61 | 62 | [] 63 | type SqlAnalyzerBlock = 64 | | Query of string * range 65 | | LiteralQuery of ident:string * range 66 | | StoredProcedure of string * range 67 | | Parameters of UsedParameter list * range 68 | | ReadingColumns of ColumnReadAttempt list 69 | | Transaction of TransactionQuery list 70 | | SkipAnalysis 71 | 72 | member this.Range() = 73 | match this with 74 | | Query(value, range) -> Some range 75 | | LiteralQuery(id, range) -> Some range 76 | | StoredProcedure(name, range) -> Some range 77 | | Parameters(parameterList, range) -> Some range 78 | | Transaction(queries) -> 79 | queries 80 | |> List.tryHead 81 | |> Option.map (fun query -> query.queryRange) 82 | | _ -> 83 | None 84 | 85 | type SqlOperation = { 86 | blocks : SqlAnalyzerBlock list 87 | range : range 88 | } 89 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpAnalyzer/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "NpgsqlFSharpAnalyzer" 17 | let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" 18 | let [] AssemblyVersion = "3.26.1" 19 | let [] AssemblyMetadata_ReleaseDate = "2021-05-06T00:00:00.0000000" 20 | let [] AssemblyFileVersion = "3.26.1" 21 | let [] AssemblyInformationalVersion = "3.26.1" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "237f46ee0d5505c9adc4e1615dd231b89cb1b992" 24 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpAnalyzer/NpgsqlFSharpAnalyzer.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NpgsqlFSharpAnalyzer 5 | net5.0 6 | true 7 | true 8 | Advanced embedded static analysis and type-checking for SQL code from F# 9 | true 10 | 11 | 12 | true 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpAnalyzer/SqlAnalyzer.fs: -------------------------------------------------------------------------------- 1 | namespace Npgsql.FSharp.Analyzers 2 | 3 | open FSharp.Analyzers.SDK 4 | open System 5 | 6 | module SqlAnalyzer = 7 | 8 | let specializedSeverity = function 9 | | Core.Severity.Error -> Severity.Error 10 | | Core.Severity.Info -> Severity.Info 11 | | Core.Severity.Warning -> Severity.Warning 12 | 13 | let specializedFix (fix: Core.Fix) : Fix = 14 | { 15 | FromRange = fix.FromRange 16 | FromText = fix.FromText 17 | ToText = fix.ToText 18 | } 19 | 20 | let specializedMessage (message: Core.Message) : Message = 21 | { 22 | Code = message.Code 23 | Fixes = message.Fixes |> List.map specializedFix 24 | Message = message.Message 25 | Range = message.Range 26 | Severity = message.Severity |> specializedSeverity 27 | Type = message.Type 28 | } 29 | 30 | let sqlAnalyzerContext (ctx: Context) : Core.SqlAnalyzerContext = { 31 | Content = ctx.Content 32 | FileName = ctx.FileName 33 | Symbols = ctx.Symbols 34 | ParseTree = ctx.ParseTree 35 | } 36 | 37 | [] 38 | let queryAnalyzer : Analyzer = 39 | fun (ctx: Context) -> 40 | let syntacticBlocks = Core.SyntacticAnalysis.findSqlOperations (sqlAnalyzerContext ctx) 41 | if List.isEmpty syntacticBlocks then 42 | [ ] 43 | else 44 | let connectionString = Core.SqlAnalyzer.tryFindConnectionString ctx.FileName 45 | if isNull connectionString || String.IsNullOrWhiteSpace connectionString then 46 | [ 47 | for block in syntacticBlocks -> 48 | Core.SqlAnalysis.createWarning "Missing environment variable 'NPGSQL_FSHARP'. Please set that variable to the connection string of your development database put the connection string in a file called 'NPGSQL_FSHARP' relative next your project or in your project root." block.range 49 | |> specializedMessage 50 | ] 51 | else 52 | match Core.SqlAnalysis.databaseSchema connectionString with 53 | | Result.Error connectionError -> 54 | [ 55 | for block in syntacticBlocks -> 56 | Core.SqlAnalysis.createWarning (sprintf "Error while connecting to the development database using the connection string from environment variable 'NPGSQL_FSHARP' or put the connection string in a file called 'NPGSQL_FSHARP' relative next your project or in your project root. Connection error: %s" connectionError) block.range 57 | |> specializedMessage 58 | ] 59 | 60 | | Result.Ok schema -> 61 | syntacticBlocks 62 | |> List.collect (fun block -> Core.SqlAnalysis.analyzeOperation block connectionString schema) 63 | |> List.map specializedMessage 64 | |> List.distinctBy (fun message -> message.Range) 65 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpAnalyzer/paket.references: -------------------------------------------------------------------------------- 1 | Microsoft.SourceLink.GitHub 2 | Microsoft.NETFramework.ReferenceAssemblies 3 | FSharp.Analyzers.SDK 4 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpParser/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "NpgsqlFSharpParser" 17 | let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" 18 | let [] AssemblyVersion = "3.26.1" 19 | let [] AssemblyMetadata_ReleaseDate = "2021-05-06T00:00:00.0000000" 20 | let [] AssemblyFileVersion = "3.26.1" 21 | let [] AssemblyInformationalVersion = "3.26.1" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "237f46ee0d5505c9adc4e1615dd231b89cb1b992" 24 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpParser/NpgsqlFSharpParser.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpParser/Types.fs: -------------------------------------------------------------------------------- 1 | namespace rec NpgsqlFSharpParser 2 | 3 | [] 4 | type Expr = 5 | | Array of Expr list 6 | | List of Expr list 7 | | Null 8 | | Star 9 | | Ident of string 10 | | Parameter of string 11 | | Boolean of bool 12 | | StringLiteral of string 13 | | Integer of int64 14 | | Float of float 15 | | Date of string 16 | | Timestamp of string 17 | | Function of name:string * arguments:Expr list 18 | | And of left:Expr * right:Expr 19 | | Or of left:Expr * right:Expr 20 | | In of left:Expr * right:Expr 21 | | As of left:Expr * right:Expr 22 | | StringConcat of left:Expr * right:Expr 23 | | JsonIndex of left:Expr * right:Expr 24 | | TypeCast of left:Expr * right:Expr 25 | | DataType of DataType 26 | | Not of expr:Expr 27 | | Any of expr:Expr 28 | | Equals of left:Expr * right:Expr 29 | | GreaterThan of left:Expr * right:Expr 30 | | LessThan of left:Expr * right:Expr 31 | | GreaterThanOrEqual of left:Expr * right:Expr 32 | | LessThanOrEqual of left:Expr * right:Expr 33 | | Between of value:Expr * leftBound:Expr * rightBound:Expr 34 | | SelectQuery of expr:SelectExpr 35 | | DeleteQuery of expr:DeleteExpr 36 | | InsertQuery of expr: InsertExpr 37 | | UpdateQuery of expr: UpdateExpr 38 | | SetQuery of expr: SetExpr 39 | | DeclareQuery of expr: DeclareExpr 40 | 41 | type Ordering = 42 | | Asc of columnName:string 43 | | Desc of columnName:string 44 | | AscNullsFirst of columnName:string 45 | | AscNullsLast of columnName:string 46 | | DescNullsFirst of columnName:string 47 | | DescNullsLast of columnName:string 48 | 49 | type JoinExpr = 50 | | InnerJoin of tableName:string * on:Expr 51 | | LeftJoin of tableName:string * on:Expr 52 | | RightJoin of tableName:string * on:Expr 53 | | FullJoin of tableName:string * on:Expr 54 | 55 | type SelectExpr = { 56 | Columns : Expr list 57 | From : Expr option 58 | Joins : JoinExpr list 59 | Where : Expr option 60 | OrderBy : Ordering list 61 | GroupBy : Expr list 62 | Having : Expr option 63 | Limit : Expr option 64 | Offset : Expr option 65 | } 66 | with 67 | static member Default = { 68 | Columns = [ ] 69 | From = None 70 | Where = None 71 | Having = None 72 | Limit = None 73 | Offset = None 74 | OrderBy = [ ] 75 | GroupBy = [ ] 76 | Joins = [ ] 77 | } 78 | 79 | type UpdateExpr = { 80 | Table : string 81 | Where : Expr option 82 | Assignments : Expr list 83 | ConflictResolution : Expr list 84 | Returning : Expr list 85 | } with 86 | static member Default = { 87 | Table = "" 88 | Where = None 89 | Returning = [ ] 90 | Assignments = [ ] 91 | ConflictResolution = [ ] 92 | } 93 | 94 | type DeleteExpr = { 95 | Table : string 96 | Where : Expr option 97 | Returning : Expr list 98 | } with 99 | static member Default = 100 | { 101 | Table = ""; 102 | Where = None 103 | Returning = [ ] 104 | } 105 | 106 | type InsertExpr = { 107 | Table: string 108 | Columns : string list 109 | Values : Expr list 110 | ConflictResolution : (string * Expr) list 111 | Returning : Expr list 112 | } with 113 | static member Default = 114 | { 115 | Table = ""; 116 | Columns = [ ] 117 | Values = [ ] 118 | ConflictResolution = [ ] 119 | Returning = [ ] 120 | } 121 | 122 | type Scope = 123 | | Local 124 | | Session 125 | 126 | type SetExpr = { 127 | Parameter: string 128 | Value: Expr option 129 | Scope: Scope 130 | } with 131 | static member Default = 132 | { 133 | Parameter = ""; 134 | Value = None 135 | Scope = Session 136 | } 137 | 138 | type CursorDeclaration = { 139 | Parameter: string 140 | Query: Expr 141 | } with 142 | static member Default = 143 | { 144 | Parameter = ""; 145 | Query = Expr.Null 146 | } 147 | 148 | type DeclareExpr = 149 | | Cursor of CursorDeclaration 150 | 151 | [] 152 | type DataType = 153 | | Integer 154 | | BigInt 155 | | SmallInt 156 | | Real 157 | | Double 158 | | Array of dataType:DataType * size:int option 159 | 160 | static member TryFromString(valueType: string, ?isArray: bool, ?arraySize: int) : DataType option = 161 | let dType = 162 | match valueType.ToUpper () with 163 | | "INT2" 164 | | "SMALLINT" -> Some SmallInt 165 | | "INT" 166 | | "INT4" 167 | | "INTEGER" -> Some Integer 168 | | "INT8" 169 | | "BIGINT" -> Some BigInt 170 | | "FLOAT4" 171 | | "REAL" -> Some Real 172 | | "FLOAT8" 173 | | "DOUBLE PRECISION" -> Some Double 174 | | _ -> None 175 | 176 | let isArray = isArray |> Option.bind (function | false -> None | _ -> Some true) // Note: Some false -> None. 177 | 178 | dType 179 | |> Option.bind (fun t -> isArray |> Option.map (fun _ -> Array(dataType=t, size=arraySize))) 180 | |> Option.orElse dType 181 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/ContentTypeNames.cs: -------------------------------------------------------------------------------- 1 | namespace NpgsqlFSharpVs 2 | { 3 | public static class ContentTypeNames 4 | { 5 | public const string FSharpContentType = "F#"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/FsLintVsPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using EnvDTE80; 7 | using Microsoft.VisualStudio.ComponentModelHost; 8 | using Microsoft.VisualStudio.Shell; 9 | using Microsoft.VisualStudio.Shell.Interop; 10 | using Task = System.Threading.Tasks.Task; 11 | 12 | namespace NpgsqlFSharpVs 13 | { 14 | // DO NOT REMOVE THIS MAGICAL INCANTATION NO MATTER HOW MUCH VS WARNS YOU OF DEPRECATION 15 | // -------------------------------------------------------------------------------------- 16 | [InstalledProductRegistration("F# Npgsql Analyzer", "Static SQL analyzer with Npgsql.FSharp", "0.1", IconResourceID = 400)] 17 | // -------------------------------------------------------------------------------------- 18 | 19 | // Package registration attributes 20 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 21 | [Guid(FsLintVsPackage.PackageGuidString)] 22 | 23 | // Auto load only if a solution is open, this is important too 24 | [ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)] 25 | 26 | // Options page 27 | [ProvideOptionPage(typeof(FsLintOptionsPage), "F# Tools", "Linting", 0, 0, supportsAutomation: true)] 28 | 29 | public sealed partial class FsLintVsPackage : AsyncPackage 30 | { 31 | /// 32 | /// FsLintVsPackage GUID string. 33 | /// 34 | public const string PackageGuidString = "74927147-72e8-4b47-a80d-5568807d6879"; 35 | 36 | private static readonly TaskCompletionSource _instance = new TaskCompletionSource(); 37 | public static Task Instance => _instance.Task; 38 | 39 | public FsLintOptionsPage Options => GetDialogPage(typeof(FsLintOptionsPage)) as FsLintOptionsPage ?? new FsLintOptionsPage(); 40 | 41 | public IComponentModel MefHost { get; private set; } 42 | 43 | public IVsStatusbar Statusbar { get; private set; } 44 | 45 | public DTE2 Dte { get; private set; } 46 | 47 | public IVsSolution SolutionService { get; private set; } 48 | 49 | #region Package Members 50 | 51 | /// 52 | /// Initialization of the package; this method is called right after the package is sited, so this is the place 53 | /// where you can put all the initialization code that rely on services provided by VisualStudio. 54 | /// 55 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. 56 | /// A provider for progress updates. 57 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. 58 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 59 | { 60 | Trace.WriteLine("F# Lint Vs Package Loaded"); 61 | 62 | MefHost = await this.GetServiceAsync(); 63 | Statusbar = await this.GetServiceAsync(); 64 | Dte = await this.GetServiceAsync(); 65 | SolutionService = await this.GetServiceAsync(); 66 | 67 | // When initialized asynchronously, the current thread may be a background thread at this point. 68 | // Do any initialization that requires the UI thread after switching to the UI thread. 69 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); 70 | 71 | // signal that package is ready 72 | _instance.SetResult(this); 73 | } 74 | 75 | #endregion 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/LintCheckerProvider.cs: -------------------------------------------------------------------------------- 1 | using FSharp.Compiler.SourceCodeServices; 2 | using NpgsqlFSharpVs; 3 | using Microsoft.VisualStudio.Shell.Interop; 4 | using Microsoft.VisualStudio.Shell.TableControl; 5 | using Microsoft.VisualStudio.Shell.TableManager; 6 | using Microsoft.VisualStudio.Text; 7 | using Microsoft.VisualStudio.Text.Classification; 8 | using Microsoft.VisualStudio.Text.Editor; 9 | using Microsoft.VisualStudio.Text.Operations; 10 | using Microsoft.VisualStudio.Text.Tagging; 11 | using Microsoft.VisualStudio.Utilities; 12 | using System; 13 | using System.Collections.Generic; 14 | using System.ComponentModel.Composition; 15 | using System.Diagnostics; 16 | 17 | namespace NpgsqlFSharpVs 18 | { 19 | /// 20 | /// Factory for the and . 21 | /// There will be only one instance of this class created. 22 | /// 23 | [Export(typeof(IViewTaggerProvider))] 24 | [TagType(typeof(IErrorTag))] 25 | [ContentType(ContentTypeNames.FSharpContentType)] 26 | [TextViewRole(PredefinedTextViewRoles.Document)] 27 | [TextViewRole(PredefinedTextViewRoles.Analyzable)] 28 | public sealed class LintCheckerProvider : IViewTaggerProvider, ITableDataSource 29 | { 30 | public readonly ITableManager ErrorTableManager; 31 | public readonly ITextDocumentFactoryService TextDocumentFactoryService; 32 | 33 | public const string LintCheckerDataSource = "LintChecker"; 34 | 35 | private readonly List _managers = new List(); // Also used for locks 36 | private readonly List _lintCheckers = new List(); 37 | 38 | [ImportingConstructor] 39 | public LintCheckerProvider 40 | ( 41 | [Import] ITableManagerProvider provider, 42 | [Import] ITextDocumentFactoryService textDocumentFactoryService 43 | ) 44 | { 45 | this.TextDocumentFactoryService = textDocumentFactoryService; 46 | this.ErrorTableManager = provider.GetTableManager(StandardTables.ErrorsTable); 47 | this.ErrorTableManager.AddSource(this, 48 | StandardTableColumnDefinitions.DetailsExpander, 49 | StandardTableColumnDefinitions.ErrorSeverity, 50 | StandardTableColumnDefinitions.ErrorCode, 51 | StandardTableColumnDefinitions.ErrorSource, 52 | StandardTableColumnDefinitions.BuildTool, 53 | StandardTableColumnDefinitions.ErrorSource, 54 | StandardTableColumnDefinitions.ErrorCategory, 55 | StandardTableColumnDefinitions.Text, 56 | StandardTableColumnDefinitions.DocumentName, 57 | StandardTableColumnDefinitions.Line, 58 | StandardTableColumnDefinitions.Column, 59 | StandardTableColumnDefinitions.ProjectName 60 | ); 61 | } 62 | 63 | /// 64 | /// Create a tagger that does lint checking on the view/buffer combination. 65 | /// 66 | public ITagger CreateTagger(ITextView textView, ITextBuffer buffer) where T : ITag 67 | { 68 | ITagger tagger = null; 69 | 70 | // Only attempt to lint check on the view's edit buffer (and multiple views could have that buffer open simultaneously so 71 | // only create one instance of the lint checker. 72 | if ((buffer == textView.TextBuffer) && (typeof(T) == typeof(IErrorTag))) 73 | { 74 | var lintChecker = buffer.Properties.GetOrCreateSingletonProperty(typeof(LintChecker), () => new LintChecker(this, textView, buffer)); 75 | 76 | // This is a thin wrapper around the LintChecker that can be disposed of without shutting down the LintChecker 77 | // (unless it was the last tagger on the lint checker). 78 | tagger = new LintTagger(lintChecker) as ITagger; 79 | } 80 | 81 | return tagger; 82 | } 83 | 84 | #region ITableDataSource members 85 | 86 | // This string should, in general, be localized since it is what would be displayed in any UI that lets the end user pick 87 | // which ITableDataSources should be subscribed to by an instance of the table control. It really isn't needed for the error 88 | // list however because it autosubscribes to all the ITableDataSources. 89 | public string DisplayName => "F# Lint"; 90 | 91 | public string Identifier => LintCheckerDataSource; 92 | 93 | public string SourceTypeIdentifier => StandardTableDataSources.ErrorTableDataSource; 94 | 95 | // This is the observer pattern 96 | public IDisposable Subscribe(ITableDataSink sink) 97 | { 98 | // This method is called to each consumer interested in errors. In general, there will be only a single consumer (the error list tool window) 99 | // but it is always possible for 3rd parties to write code that will want to subscribe. 100 | return new SubscriptionManager(this, sink); 101 | } 102 | #endregion 103 | 104 | #region Checker 105 | 106 | private Lazy _checker = new Lazy(() => 107 | FSharpChecker.Create(null, null, null, null, null, null, null, null, null) 108 | ); 109 | 110 | public FSharpChecker CheckerInstance => _checker.Value; 111 | 112 | #endregion 113 | 114 | public void AddSinkManager(SubscriptionManager manager) 115 | { 116 | // This call can, in theory, happen from any thread so be appropriately thread safe. 117 | // In practice, it will probably be called only once from the UI thread (by the error list tool window). 118 | lock (_managers) 119 | { 120 | _managers.Add(manager); 121 | 122 | // Add the pre-existing lint checkers to the manager. 123 | foreach (var checker in _lintCheckers) 124 | { 125 | manager.Add(checker); 126 | } 127 | } 128 | } 129 | 130 | public void RemoveSinkManager(SubscriptionManager manager) 131 | { 132 | // This call can, in theory, happen from any thread so be appropriately thread safe. 133 | // In practice, it will probably be called only once from the UI thread (by the error list tool window). 134 | lock (_managers) 135 | { 136 | _managers.Remove(manager); 137 | } 138 | } 139 | 140 | public void AddLintChecker(LintChecker lintChecker) 141 | { 142 | // This call will always happen on the UI thread (it is a side-effect of adding or removing the 1st/last tagger). 143 | lock (_managers) 144 | { 145 | _lintCheckers.Add(lintChecker); 146 | 147 | // Tell the preexisting managers about the new lint checker 148 | foreach (var manager in _managers) 149 | { 150 | manager.Add(lintChecker); 151 | } 152 | } 153 | } 154 | 155 | public void RemoveLintChecker(LintChecker lintChecker) 156 | { 157 | // This call will always happen on the UI thread (it is a side-effect of adding or removing the 1st/last tagger). 158 | lock (_managers) 159 | { 160 | _lintCheckers.Remove(lintChecker); 161 | 162 | foreach (var manager in _managers) 163 | { 164 | manager.Remove(lintChecker); 165 | } 166 | } 167 | } 168 | 169 | public void NotifyAllSinks() 170 | { 171 | lock (_managers) 172 | { 173 | foreach (var manager in _managers) 174 | { 175 | manager.Notify(); 176 | } 177 | } 178 | } 179 | 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/LintError.cs: -------------------------------------------------------------------------------- 1 | using Npgsql.FSharp.Analyzers.Core; 2 | using Microsoft.VisualStudio.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace NpgsqlFSharpVs 7 | { 8 | public class LintError 9 | { 10 | public readonly SnapshotSpan Span; 11 | public readonly LintProjectInfo Project; 12 | private readonly Message LintWarning; 13 | 14 | public int NextIndex = -1; 15 | 16 | public string Tooltip => LintWarning.Message; 17 | 18 | public string Identifier => LintWarning.Type; 19 | 20 | public string Name => LintWarning.Code; 21 | 22 | public string Message => LintWarning.Message; 23 | 24 | public string HelpUrl => $"https://fsprojects.github.io/FSharpLint/how-tos/rules/{Identifier}.html"; 25 | 26 | public bool HasSuggestedFix => LintWarning.Fixes.Any(); 27 | 28 | public Fix GetSuggestedFix() => LintWarning.Fixes.FirstOrDefault(); 29 | 30 | public IEnumerable Fixes => LintWarning.Fixes; 31 | 32 | public int Line => LintWarning.Range.StartLine - 1; 33 | 34 | public int Column => LintWarning.Range.StartColumn; 35 | 36 | public string ErrorText => LintWarning.Message; 37 | 38 | public string ReplacementText 39 | { 40 | get 41 | { 42 | var fix = GetSuggestedFix(); 43 | if (fix == null) 44 | return ""; 45 | 46 | var startColumn = fix.FromRange.StartColumn; 47 | return ErrorText.Remove(startColumn, fix.FromRange.EndColumn - startColumn).Insert(startColumn, fix.ToText); 48 | } 49 | } 50 | 51 | public LintError(SnapshotSpan span, Message lintWarning, LintProjectInfo project) 52 | { 53 | this.Span = span; 54 | this.LintWarning = lintWarning; 55 | this.Project = project; 56 | } 57 | 58 | public static LintError Clone(LintError error) 59 | { 60 | return new LintError(error.Span, error.LintWarning, error.Project); 61 | } 62 | 63 | public static LintError CloneAndTranslateTo(LintError error, ITextSnapshot newSnapshot) 64 | { 65 | var newSpan = error.Span.TranslateTo(newSnapshot, SpanTrackingMode.EdgeExclusive); 66 | 67 | // We want to only translate the error if the length of the error span did not change (if it did change, it would imply that 68 | // there was some text edit inside the error and, therefore, that the error is no longer valid). 69 | return (newSpan.Length == error.Span.Length) 70 | ? new LintError(newSpan, error.LintWarning, error.Project) 71 | : null; 72 | } 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/LintProjectInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell.Interop; 2 | using System; 3 | 4 | namespace NpgsqlFSharpVs 5 | { 6 | public class LintProjectInfo 7 | { 8 | public string ProjectName { get; } 9 | 10 | 11 | // Performance will be improved if you "prebox" your System.Guid by, 12 | // in your Microsoft.VisualStudio.Shell.TableManager.ITableEntry 13 | // or Microsoft.VisualStudio.Shell.TableManager.ITableEntriesSnapshot, having a 14 | // member variable: 15 | // private object boxedProjectGuid = projectGuid; 16 | // and returning boxedProjectGuid instead of projectGuid. 17 | public object ProjectGuid { get; } 18 | 19 | public IVsHierarchy Hierarchy { get; } 20 | 21 | public LintProjectInfo(string projectName, Guid projectGuid, IVsHierarchy hierarchy) 22 | { 23 | ProjectName = projectName; 24 | ProjectGuid = projectGuid; 25 | Hierarchy = hierarchy; 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/LintTagger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Text; 2 | using Microsoft.VisualStudio.Text.Adornments; 3 | using Microsoft.VisualStudio.Text.Tagging; 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace NpgsqlFSharpVs 8 | { 9 | public class LintTagger : ITagger, IDisposable 10 | { 11 | private readonly LintChecker _lintChecker; 12 | private LintingErrorsSnapshot _snapshot; 13 | 14 | public LintTagger(LintChecker lintChecker) 15 | { 16 | _lintChecker = lintChecker; 17 | _snapshot = lintChecker.LastErrorsSnapshot; 18 | 19 | lintChecker.AddTagger(this); 20 | } 21 | 22 | public event EventHandler TagsChanged; 23 | 24 | public void UpdateErrors(ITextSnapshot currentSnapshot, LintingErrorsSnapshot lintingErrors) 25 | { 26 | var oldLintingErrors = _snapshot; 27 | _snapshot = lintingErrors; 28 | 29 | 30 | // Raise a single tags changed event over the span that could have been affected by the change in the errors. 31 | var start = int.MaxValue; 32 | var end = int.MinValue; 33 | 34 | if (oldLintingErrors?.Errors.Count > 0) 35 | { 36 | start = oldLintingErrors.Errors[0].Span.Start.TranslateTo(currentSnapshot, PointTrackingMode.Negative); 37 | end = oldLintingErrors.Errors[oldLintingErrors.Errors.Count - 1].Span.End.TranslateTo(currentSnapshot, PointTrackingMode.Positive); 38 | } 39 | 40 | if (lintingErrors.Count > 0) 41 | { 42 | start = Math.Min(start, lintingErrors.Errors[0].Span.Start.Position); 43 | end = Math.Max(end, lintingErrors.Errors[lintingErrors.Errors.Count - 1].Span.End.Position); 44 | } 45 | 46 | if (start < end) 47 | { 48 | TagsChanged?.Invoke(this, new SnapshotSpanEventArgs(new SnapshotSpan(currentSnapshot, Span.FromBounds(start, end)))); 49 | } 50 | } 51 | 52 | public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) 53 | { 54 | if (_snapshot != null) 55 | { 56 | foreach (var error in _snapshot.Errors) 57 | { 58 | if (spans.IntersectsWith(error.Span)) 59 | { 60 | yield return new TagSpan(error.Span, new ErrorTag(PredefinedErrorTypeNames.Warning, error.Tooltip) { }); 61 | } 62 | } 63 | } 64 | } 65 | 66 | public void Dispose() 67 | { 68 | // Called when the tagger is no longer needed (generally when the ITextView is closed). 69 | _lintChecker.RemoveTagger(this); 70 | } 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Options/FsLintOptionsPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using Microsoft.VisualStudio.Shell; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace NpgsqlFSharpVs 7 | { 8 | [Guid(GuidString)] 9 | public class FsLintOptionsPage : DialogPage 10 | { 11 | public const string GuidString = "74927147-72e8-4b47-a70d-5568807d6879"; 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | // 3 | using System.Reflection; 4 | 5 | [assembly: AssemblyTitle("NpgsqlFSharpVs")] 6 | [assembly: AssemblyProduct("NpgsqlFSharpAnalyzer")] 7 | [assembly: AssemblyVersion("3.12.0")] 8 | [assembly: AssemblyMetadata("ReleaseDate","2020-08-29T00:00:00.0000000")] 9 | [assembly: AssemblyFileVersion("3.12.0")] 10 | [assembly: AssemblyInformationalVersion("3.12.0")] 11 | [assembly: AssemblyMetadata("ReleaseChannel","release")] 12 | [assembly: AssemblyMetadata("GitHash","fdb4e9da4d035afc38f1941a84adf621fd8c2574")] 13 | namespace System { 14 | internal static class AssemblyVersionInformation { 15 | internal const System.String AssemblyTitle = "NpgsqlFSharpVs"; 16 | internal const System.String AssemblyProduct = "NpgsqlFSharpAnalyzer"; 17 | internal const System.String AssemblyVersion = "3.12.0"; 18 | internal const System.String AssemblyMetadata_ReleaseDate = "2020-08-29T00:00:00.0000000"; 19 | internal const System.String AssemblyFileVersion = "3.12.0"; 20 | internal const System.String AssemblyInformationalVersion = "3.12.0"; 21 | internal const System.String AssemblyMetadata_ReleaseChannel = "release"; 22 | internal const System.String AssemblyMetadata_GitHash = "fdb4e9da4d035afc38f1941a84adf621fd8c2574"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Resources/GettingStarted.html: -------------------------------------------------------------------------------- 1 | 

Getting Started

2 | 3 |

4 | Configure the connection string to your development database 5 |

6 | 7 |

8 | The analyzer requires a connection string that points to the database you are developing against. You can configure this connection string by either creating a file called NPGSQL_FSHARP (without extension) somewhere next to your F# project or preferably in the root of your workspace. This file should contain that connection string and nothing else. An example of the contents of such file: 9 |

10 |
11 | Host=localhost; Username=postgres; Password=postgres; Database=databaseName
12 | 
13 |

14 | Remember to add an entry in your .gitingore file to make sure you don't commit the connection string to your source version control system 15 |

16 |

17 | Another way to configure the connection string is by setting the value of an environment variable named NPGSQL_FSHARP that contains the connection string. 18 | 19 | The analyzer will try to locate and read the file first, then falls back to using the environment variable. 20 |

21 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Resources/License.txt: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright 2020 Zaid Ajaj 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Resources/ReleaseNotes.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zaid-Ajaj/Npgsql.FSharp.Analyzer/798ab4fe989690688d61ed1196ca79bb4e1aca37/src/NpgsqlFSharpVs/Resources/ReleaseNotes.html -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Resources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zaid-Ajaj/Npgsql.FSharp.Analyzer/798ab4fe989690688d61ed1196ca79bb4e1aca37/src/NpgsqlFSharpVs/Resources/logo.png -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/SubscriptionManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell.TableManager; 2 | using System; 3 | 4 | namespace NpgsqlFSharpVs 5 | { 6 | /// 7 | /// Every consumer of data from an provides an to record the changes. We give the consumer 8 | /// an IDisposable (this object) that they hang on to as long as they are interested in our data (and they Dispose() of it when they are done). 9 | /// 10 | public class SubscriptionManager : IDisposable 11 | { 12 | private readonly LintCheckerProvider _lintCheckerProvider; 13 | private readonly ITableDataSink _sink; 14 | 15 | public SubscriptionManager(LintCheckerProvider lintCheckerProvider, ITableDataSink sink) 16 | { 17 | _lintCheckerProvider = lintCheckerProvider; 18 | _sink = sink; 19 | 20 | lintCheckerProvider.AddSinkManager(this); 21 | } 22 | 23 | public void Add(LintChecker lintChecker) 24 | { 25 | _sink.AddFactory(lintChecker.Factory); 26 | } 27 | 28 | public void Remove(LintChecker lintChecker) 29 | { 30 | _sink.RemoveFactory(lintChecker.Factory); 31 | } 32 | 33 | public void Notify() 34 | { 35 | _sink.FactorySnapshotChanged(null); 36 | } 37 | 38 | public void Dispose() 39 | { 40 | // Called when the person who subscribed to the data source disposes of the cookie 41 | // (== this object) they were given. 42 | _lintCheckerProvider.RemoveSinkManager(this); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/SuggestionPreview.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | . . . 26 | 27 | - 28 | 29 | 30 | 31 | 32 | 33 | 34 | + 35 | 36 | 37 | 38 | 39 | 40 | . . . 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/SuggestionPreview.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace NpgsqlFSharpVs 17 | { 18 | /// 19 | /// Interaction logic for SuggestionPreview.xaml 20 | /// 21 | public partial class SuggestionPreview : UserControl 22 | { 23 | protected SuggestionPreview() 24 | { 25 | InitializeComponent(); 26 | } 27 | 28 | public SuggestionPreview(LintError lintError) : this() 29 | { 30 | this.DataContext = lintError; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Suggestions/LintActionsSource.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Language.Intellisense; 2 | using Microsoft.VisualStudio.Text; 3 | using Microsoft.VisualStudio.Text.Editor; 4 | using Microsoft.VisualStudio.Text.Operations; 5 | using Microsoft.VisualStudio.Threading; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Net.Http.Headers; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace NpgsqlFSharpVs 14 | { 15 | public class LintActionsSource : ISuggestedActionsSource 16 | { 17 | private readonly ITextBuffer _textBuffer; 18 | private readonly ITextView _textView; 19 | private LintChecker _lintChecker; 20 | 21 | public LintActionsSource(ITextView textView, ITextBuffer textBuffer) 22 | { 23 | _textBuffer = textBuffer; 24 | _textView = textView; 25 | } 26 | 27 | #pragma warning disable 0067 28 | public event EventHandler SuggestedActionsChanged; 29 | protected void OnSuggestedActionsChanged(object sender, EventArgs e) 30 | { 31 | SuggestedActionsChanged?.Invoke(sender, e); 32 | } 33 | #pragma warning restore 0067 34 | 35 | public void Dispose() 36 | { 37 | if (_lintChecker != null) 38 | _lintChecker.Updated -= OnSuggestedActionsChanged; 39 | } 40 | 41 | public IEnumerable GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) 42 | { 43 | yield return new SuggestedActionSet( 44 | categoryName: PredefinedSuggestedActionCategoryNames.CodeFix, 45 | actions: GetSuggestedActions(range), 46 | title: "FsLint", 47 | priority: SuggestedActionSetPriority.None, 48 | applicableToSpan: null 49 | ); 50 | } 51 | 52 | public IEnumerable GetSuggestedActions(SnapshotSpan range) 53 | { 54 | if (!TryGetLintChecker(out var lintChecker)) 55 | yield break; 56 | 57 | if (!lintChecker.HasSnapshot) 58 | yield break; 59 | 60 | foreach (var error in lintChecker.LastErrorsSnapshot.Errors) 61 | { 62 | if (range.IntersectsWith(error.Span)) 63 | { 64 | foreach(var fix in error.Fixes) 65 | { 66 | yield return new LintFixAction(error, fix); 67 | } 68 | } 69 | } 70 | } 71 | 72 | public Task HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) 73 | { 74 | return Task.Run(async () => 75 | { 76 | if (!TryGetLintChecker(out var lintChecker)) 77 | return false; 78 | 79 | // wait for linting to complete 80 | await lintChecker.Linting.WithCancellation(cancellationToken); 81 | 82 | if (!lintChecker.HasSnapshot) 83 | return false; 84 | 85 | // we can't actually traverse the to see if the suggested action is a Some (fix) or None 86 | // because we'd have to evaluate the lazy 87 | return lintChecker.LastErrorsSnapshot.Count > 0; 88 | 89 | }, cancellationToken); 90 | } 91 | 92 | private bool TryGetLintChecker(out LintChecker checker) 93 | { 94 | // return cached value 95 | if (_lintChecker != null) 96 | { 97 | checker = _lintChecker; 98 | return true; 99 | } 100 | 101 | if (!_textBuffer.Properties.TryGetProperty(typeof(LintChecker), out checker)) 102 | return false; 103 | 104 | if (checker.IsDisposed || checker.RefCount == 0 || checker.Linting == null) 105 | return false; 106 | 107 | // cache value 108 | _lintChecker = checker; 109 | _lintChecker.Updated += OnSuggestedActionsChanged; 110 | return true; 111 | } 112 | 113 | 114 | public bool TryGetTelemetryId(out Guid telemetryId) 115 | { 116 | telemetryId = Guid.Empty; 117 | return false; 118 | } 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Suggestions/LintFixAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Windows; 6 | using Microsoft.VisualStudio.Imaging; 7 | using Microsoft.VisualStudio.Imaging.Interop; 8 | using Microsoft.VisualStudio.Language.Intellisense; 9 | using Microsoft.VisualStudio.Text; 10 | using Npgsql.FSharp.Analyzers.Core; 11 | 12 | namespace NpgsqlFSharpVs 13 | { 14 | public class LintFixAction : ISuggestedAction 15 | { 16 | private readonly LintError _lintError; 17 | private readonly Fix _fix; 18 | 19 | public LintFixAction(LintError lintError, Fix fix) 20 | { 21 | this._lintError = lintError; 22 | this._fix = fix; 23 | } 24 | 25 | public string DisplayText => $"Replace with '{_fix.ToText}'"; 26 | 27 | public string IconAutomationText => null; 28 | 29 | ImageMoniker ISuggestedAction.IconMoniker => KnownMonikers.CodeWarningRule; 30 | 31 | public string InputGestureText => null; 32 | 33 | public bool HasActionSets => false; 34 | 35 | #pragma warning disable RCS1210 36 | public Task> GetActionSetsAsync(CancellationToken cancellationToken) => default; 37 | #pragma warning restore RCS1210 38 | 39 | public bool HasPreview => false; 40 | 41 | public Task GetPreviewAsync(CancellationToken cancellationToken) 42 | { 43 | var textBlock = new SuggestionPreview(this._lintError) { MaxWidth = 400, MinHeight = 100 }; 44 | return Task.FromResult(textBlock); 45 | } 46 | 47 | public void Invoke(CancellationToken cancellationToken) 48 | { 49 | if (cancellationToken.IsCancellationRequested) 50 | return; 51 | 52 | var span = LintChecker.RangeToSpan(_fix.FromRange, _lintError.Span.Snapshot); 53 | span.Snapshot.TextBuffer.Replace(span, _fix.ToText); 54 | } 55 | 56 | public bool TryGetTelemetryId(out Guid telemetryId) 57 | { 58 | telemetryId = Guid.Empty; 59 | return false; 60 | } 61 | 62 | public void Dispose() 63 | { 64 | //nothing to do 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Suggestions/LintSuggestionProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Language.Intellisense; 2 | using Microsoft.VisualStudio.Text; 3 | using Microsoft.VisualStudio.Text.Editor; 4 | using Microsoft.VisualStudio.Utilities; 5 | using System.ComponentModel.Composition; 6 | 7 | namespace NpgsqlFSharpVs 8 | { 9 | [Export(typeof(ISuggestedActionsSourceProvider))] 10 | [ContentType(ContentTypeNames.FSharpContentType)] 11 | [Name("F# Lint Suggested Actions")] 12 | public class LintSuggestionProvider : ISuggestedActionsSourceProvider 13 | { 14 | 15 | public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) 16 | { 17 | return new LintActionsSource(textView, textBuffer); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Suggestions/LintSuppressAction.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Imaging; 2 | using Microsoft.VisualStudio.Imaging.Interop; 3 | using Microsoft.VisualStudio.Language.Intellisense; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | 11 | namespace NpgsqlFSharpVs 12 | { 13 | public class LintSuppressAction : ISuggestedAction 14 | { 15 | private readonly LintError error; 16 | 17 | public LintSuppressAction(LintError error) 18 | { 19 | this.error = error; 20 | } 21 | 22 | public string DisplayText => $"Suppress {error.Identifier}"; 23 | 24 | public ImageMoniker IconMoniker => KnownMonikers.CodeSuppressedRule; 25 | 26 | public string IconAutomationText => default; 27 | 28 | public string InputGestureText => default; 29 | 30 | public bool HasPreview => false; 31 | 32 | public bool HasActionSets => true; 33 | 34 | public Task> GetActionSetsAsync(CancellationToken cancellationToken) 35 | { 36 | return Task.FromResult(new SuggestedActionSet[] 37 | { 38 | new SuggestedActionSet( 39 | categoryName: PredefinedSuggestedActionCategoryNames.CodeFix, 40 | actions: GetSuggestedActions(), 41 | title: "FsLint", 42 | priority: SuggestedActionSetPriority.None, 43 | applicableToSpan: null 44 | ) 45 | }.AsEnumerable()); 46 | } 47 | 48 | public IEnumerable GetSuggestedActions() 49 | { 50 | yield return new LintSuppressBy(error, LintSuppressBy.Method.Above); 51 | yield return new LintSuppressBy(error, LintSuppressBy.Method.Inline); 52 | yield return new LintSuppressBy(error, LintSuppressBy.Method.Section); 53 | } 54 | 55 | public Task GetPreviewAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); 56 | 57 | public void Invoke(CancellationToken cancellationToken) 58 | { 59 | GetSuggestedActions().First().Invoke(cancellationToken); 60 | } 61 | 62 | public bool TryGetTelemetryId(out Guid telemetryId) 63 | { 64 | telemetryId = default; 65 | return false; 66 | } 67 | 68 | public void Dispose() 69 | { 70 | // nothing 71 | } 72 | } 73 | 74 | 75 | } -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Suggestions/LintSuppressBy.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Imaging; 2 | using Microsoft.VisualStudio.Imaging.Interop; 3 | using Microsoft.VisualStudio.Language.Intellisense; 4 | using Microsoft.VisualStudio.Text; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using System.Windows; 11 | 12 | namespace NpgsqlFSharpVs 13 | { 14 | public class LintSuppressBy : ISuggestedAction 15 | { 16 | public enum Method 17 | { 18 | Inline, 19 | Above, 20 | Section, 21 | Global 22 | } 23 | 24 | private readonly LintError _error; 25 | private readonly Method _suppressionMethod; 26 | 27 | public LintSuppressBy(LintError error, Method suppressionMethod) 28 | { 29 | this._error = error; 30 | this._suppressionMethod = suppressionMethod; 31 | } 32 | 33 | public static string ToText(Method suppressionMethod) 34 | { 35 | switch (suppressionMethod) 36 | { 37 | case Method.Inline: 38 | return "inline"; 39 | case Method.Above: 40 | return "above"; 41 | case Method.Section: 42 | return "section"; 43 | default: 44 | return "..."; 45 | } 46 | } 47 | 48 | public string DisplayText => $"Suppress {ToText(_suppressionMethod)}"; 49 | 50 | public ImageMoniker IconMoniker => _suppressionMethod switch 51 | { 52 | Method.Above => KnownMonikers.GlyphUp, 53 | Method.Section => KnownMonikers.Inline, 54 | Method.Inline => KnownMonikers.GoToCurrentLine, 55 | _ => default 56 | }; 57 | 58 | public string IconAutomationText => default; 59 | 60 | public string InputGestureText => default; 61 | 62 | public bool HasActionSets => false; 63 | 64 | public Task> GetActionSetsAsync(CancellationToken cancellationToken) 65 | { 66 | throw new NotSupportedException(); 67 | } 68 | 69 | public bool HasPreview => false; 70 | 71 | public Task GetPreviewAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); 72 | 73 | public void Invoke(CancellationToken cancellationToken) 74 | { 75 | if (cancellationToken.IsCancellationRequested) 76 | return; 77 | 78 | var snapshot = _error.Span.Snapshot; 79 | 80 | switch (_suppressionMethod) 81 | { 82 | case Method.Inline: 83 | { 84 | var line = _error.Span.Start.GetContainingLine(); 85 | if (line != null) 86 | snapshot.TextBuffer.Insert(line.End.Position, $" // fsharplint:disable-line {_error.Name}"); 87 | break; 88 | } 89 | case Method.Above: 90 | { 91 | var line = snapshot.GetLineFromLineNumber(_error.Line - 1); 92 | var indent = snapshot.GetLineFromLineNumber(_error.Line).GetText().TakeWhile(Char.IsWhiteSpace).Count(); 93 | if (line != null) 94 | snapshot.TextBuffer.Insert(line.End.Position, $"{Environment.NewLine}{new String(' ', indent)}// fsharplint:disable-next-line {_error.Name}"); 95 | break; 96 | } 97 | case Method.Section: 98 | { 99 | var containingLine = FindSection().FirstOrDefault(); 100 | if (containingLine == null) 101 | { 102 | snapshot = _error.Span.Snapshot.TextBuffer.Insert(0, $"// fsharplint:disable{Environment.NewLine}"); 103 | containingLine = snapshot.GetLineFromLineNumber(0); 104 | } 105 | 106 | snapshot.TextBuffer.Insert(containingLine.End, $" {_error.Name}"); 107 | break; 108 | } 109 | default: 110 | break; 111 | } 112 | } 113 | 114 | public IEnumerable FindSection() 115 | { 116 | var cursor = _error.Line; 117 | var snapshot = _error.Span.Snapshot; 118 | 119 | while (cursor-- >= 0) 120 | { 121 | var line = snapshot.GetLineFromLineNumber(cursor); 122 | if (line.GetText().StartsWith("// fsharplint:disable ")) 123 | yield return line; 124 | } 125 | } 126 | 127 | public bool TryGetTelemetryId(out Guid telemetryId) 128 | { 129 | telemetryId = default; 130 | return false; 131 | } 132 | 133 | public void Dispose() 134 | { 135 | // nothing 136 | } 137 | } 138 | 139 | 140 | } -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Table/LintTableSnapshotFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.Shell.TableManager; 2 | 3 | namespace NpgsqlFSharpVs 4 | { 5 | public class LintTableSnapshotFactory : TableEntriesSnapshotFactoryBase 6 | { 7 | 8 | public LintingErrorsSnapshot CurrentSnapshot { get; private set; } 9 | 10 | public LintTableSnapshotFactory(LintingErrorsSnapshot lintErrors) 11 | { 12 | this.CurrentSnapshot = lintErrors; 13 | } 14 | 15 | public void UpdateErrors(LintingErrorsSnapshot lintingErrors) 16 | { 17 | this.CurrentSnapshot.NextSnapshot = lintingErrors; 18 | this.CurrentSnapshot = lintingErrors; 19 | } 20 | 21 | #region ITableEntriesSnapshotFactory members 22 | 23 | public override int CurrentVersionNumber => this.CurrentSnapshot.VersionNumber; 24 | 25 | public override ITableEntriesSnapshot GetCurrentSnapshot() => this.CurrentSnapshot; 26 | 27 | public override void Dispose() 28 | { 29 | } 30 | 31 | public override ITableEntriesSnapshot GetSnapshot(int versionNumber) 32 | { 33 | // In theory the snapshot could change in the middle of the return statement so snap the snapshot just to be safe. 34 | var snapshot = this.CurrentSnapshot; 35 | return (versionNumber == snapshot.VersionNumber) ? snapshot : null; 36 | } 37 | 38 | #endregion 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/Table/LintingErrorsSnapshot.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Internal.VisualStudio.Shell.TableControl; 2 | using Microsoft.VisualStudio.Shell.Interop; 3 | using Microsoft.VisualStudio.Shell.TableControl; 4 | using Microsoft.VisualStudio.Shell.TableManager; 5 | using Microsoft.VisualStudio.Text; 6 | using System.Collections.Generic; 7 | using System.Diagnostics; 8 | using System.Globalization; 9 | using System.Windows; 10 | using System.Windows.Documents; 11 | 12 | namespace NpgsqlFSharpVs 13 | { 14 | public class LintingErrorsSnapshot : WpfTableEntriesSnapshotBase 15 | { 16 | public ITextDocument Document { get; } 17 | 18 | public override int VersionNumber { get; } 19 | 20 | public override int Count => this.Errors.Count; 21 | 22 | public const string ErrorCategory = "Lint"; 23 | public const string ToolName = "FSharpLint"; 24 | 25 | // We're not using an immutable list here but we cannot modify the list in any way once we've published the snapshot. 26 | public List Errors { get; } 27 | 28 | public LintingErrorsSnapshot NextSnapshot; 29 | 30 | public LintingErrorsSnapshot(ITextDocument document, int version) 31 | { 32 | Document = document; 33 | VersionNumber = version; 34 | Errors = new List(); 35 | } 36 | 37 | public override int IndexOf(int currentIndex, ITableEntriesSnapshot newerSnapshot) 38 | { 39 | // This and TranslateTo() are used to map errors from one snapshot to a different one (that way the error list can do things like maintain the selection on an error 40 | // even when the snapshot containing the error is replaced by a new one). 41 | // 42 | // You only need to implement Identity() or TranslateTo() and, of the two, TranslateTo() is more efficient for the error list to use. 43 | 44 | // Map currentIndex to the corresponding index in newerSnapshot (and keep doing it until either 45 | // we run out of snapshots, we reach newerSnapshot, or the index can no longer be mapped forward). 46 | var currentSnapshot = this; 47 | do 48 | { 49 | Debug.Assert(currentIndex >= 0); 50 | Debug.Assert(currentIndex < currentSnapshot.Count); 51 | 52 | currentIndex = currentSnapshot.Errors[currentIndex].NextIndex; 53 | 54 | currentSnapshot = currentSnapshot.NextSnapshot; 55 | } 56 | while ((currentSnapshot != null) && (currentSnapshot != newerSnapshot) && (currentIndex >= 0)); 57 | 58 | return currentIndex; 59 | } 60 | 61 | public override bool TryGetValue(int index, string columnName, out object content) 62 | { 63 | if (index >= 0 && index < this.Errors.Count) 64 | { 65 | var err = this.Errors[index]; 66 | 67 | switch (columnName) 68 | { 69 | case StandardTableKeyNames.DocumentName: 70 | { 71 | // We return the full file path here. The UI handles displaying only the Path.GetFileName(). 72 | content = Document.FilePath; 73 | return true; 74 | } 75 | 76 | case StandardTableKeyNames.ErrorCategory: 77 | { 78 | content = ErrorCategory; 79 | return true; 80 | } 81 | 82 | case StandardTableKeyNames.ErrorSource: 83 | { 84 | content = ErrorSource.Other; 85 | return true; 86 | } 87 | 88 | case StandardTableKeyNames.Line: 89 | { 90 | // Line and column numbers are 0-based (the UI that displays the line/column number will add one to the value returned here). 91 | content = err.Line; 92 | return true; 93 | } 94 | 95 | case StandardTableKeyNames.Column: 96 | { 97 | content = err.Column; 98 | return true; 99 | } 100 | 101 | case StandardTableKeyNames.Text: 102 | { 103 | content = err.Message; 104 | return true; 105 | } 106 | 107 | case StandardTableKeyNames2.TextInlines: 108 | { 109 | //content = "?"; 110 | // Do we have detailed inline text? 111 | content = ""; 112 | return false; 113 | } 114 | 115 | case StandardTableKeyNames.ErrorSeverity: 116 | { 117 | content = __VSERRORCATEGORY.EC_WARNING; 118 | return true; 119 | } 120 | 121 | case StandardTableKeyNames.BuildTool: 122 | { 123 | content = ToolName; 124 | return true; 125 | } 126 | 127 | case StandardTableKeyNames.ErrorCode: 128 | { 129 | content = err.Identifier; 130 | return true; 131 | } 132 | 133 | case StandardTableKeyNames.ErrorCodeToolTip: 134 | { 135 | content = err.Name; 136 | return true; 137 | } 138 | 139 | case StandardTableKeyNames.HelpLink: 140 | { 141 | content = err.HelpUrl; 142 | return true; 143 | } 144 | 145 | case StandardTableKeyNames.ProjectName: 146 | { 147 | content = err.Project.ProjectName; 148 | return true; 149 | } 150 | 151 | case StandardTableKeyNames.ProjectGuid: 152 | { 153 | content = err.Project.ProjectGuid; 154 | return true; 155 | } 156 | 157 | // TODO: add support for case StandardTableKeyNames.ProjectGuid: 158 | } 159 | 160 | } 161 | 162 | content = null; 163 | return false; 164 | } 165 | 166 | public override bool CanCreateDetailsContent(int index) 167 | { 168 | return false; 169 | } 170 | 171 | public override bool TryCreateDetailsStringContent(int index, out string content) 172 | { 173 | content = this.Errors[index].Tooltip; 174 | return content != null; 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core 2 | -------------------------------------------------------------------------------- /src/NpgsqlFSharpVs/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | NpgsqlFSharpVs 8 | F# Analyzer for embedded SQL syntax analysis, type-checking for parameters and result sets and nullable column detection when writing queries using Npgsql.FSharp. 9 | https://github.com/Zaid-Ajaj/Npgsql.FSharp.Analyzer 10 | Resources\License.txt 11 | Resources\GettingStarted.html 12 | Resources\ReleaseNotes.html 13 | Resources\logo.png 14 | Resources\logo.png 15 | fsharp, linting, sql, static-analysis 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/ParserTestsWithNet48/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/ParserTestsWithNet48/ParserTestsWithNet48.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {40067E09-6281-4DC2-905D-60F7C6E3B812} 8 | Exe 9 | ParserTestsWithNet48 10 | ParserTestsWithNet48 11 | v4.8 12 | 512 13 | true 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | {c5eb813f-4278-4ee7-925b-6757bad0fe9b} 55 | FParsecCS 56 | 57 | 58 | {9c8e7641-9dc8-470c-8009-71a747c01dc5} 59 | FParsec 60 | 61 | 62 | {5964bb56-97b8-4fae-9933-8113db11438d} 63 | NpgsqlFSharpAnalyzer.Core 64 | 65 | 66 | {bc524f8e-6282-4e31-9a0e-29fce38832e7} 67 | NpgsqlFSharpParser 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/ParserTestsWithNet48/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NpgsqlFSharpParser; 3 | 4 | namespace ParserTestsWithNet48 5 | { 6 | static class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | // Making sure the code from the parser 11 | // can be called from a net48 .NET Framework library 12 | // simulating the VS extension 13 | var query = "SELECT * FROM users"; 14 | var result = Parser.parseUnsafe(query); 15 | Console.WriteLine(result.ToString()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ParserTestsWithNet48/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | // 3 | using System.Reflection; 4 | 5 | [assembly: AssemblyTitle("ParserTestsWithNet48")] 6 | [assembly: AssemblyProduct("NpgsqlFSharpAnalyzer")] 7 | [assembly: AssemblyVersion("3.12.0")] 8 | [assembly: AssemblyMetadata("ReleaseDate","2020-08-29T00:00:00.0000000")] 9 | [assembly: AssemblyFileVersion("3.12.0")] 10 | [assembly: AssemblyInformationalVersion("3.12.0")] 11 | [assembly: AssemblyMetadata("ReleaseChannel","release")] 12 | [assembly: AssemblyMetadata("GitHash","fdb4e9da4d035afc38f1941a84adf621fd8c2574")] 13 | namespace System { 14 | internal static class AssemblyVersionInformation { 15 | internal const System.String AssemblyTitle = "ParserTestsWithNet48"; 16 | internal const System.String AssemblyProduct = "NpgsqlFSharpAnalyzer"; 17 | internal const System.String AssemblyVersion = "3.12.0"; 18 | internal const System.String AssemblyMetadata_ReleaseDate = "2020-08-29T00:00:00.0000000"; 19 | internal const System.String AssemblyFileVersion = "3.12.0"; 20 | internal const System.String AssemblyInformationalVersion = "3.12.0"; 21 | internal const System.String AssemblyMetadata_ReleaseChannel = "release"; 22 | internal const System.String AssemblyMetadata_GitHash = "fdb4e9da4d035afc38f1941a84adf621fd8c2574"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Ubik/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "Ubik" 17 | let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" 18 | let [] AssemblyVersion = "3.26.1" 19 | let [] AssemblyMetadata_ReleaseDate = "2021-05-06T00:00:00.0000000" 20 | let [] AssemblyFileVersion = "3.26.1" 21 | let [] AssemblyInformationalVersion = "3.26.1" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "237f46ee0d5505c9adc4e1615dd231b89cb1b992" 24 | -------------------------------------------------------------------------------- /src/Ubik/Program.fs: -------------------------------------------------------------------------------- 1 | open System 2 | open System.IO 3 | open System.Linq 4 | open Npgsql.FSharp.Analyzers.Core 5 | open Spectre.Console 6 | open System.Xml 7 | open FSharp.Compiler.Text 8 | open FSharp.Data.LiteralProviders 9 | 10 | let resolveFile (path: string) = 11 | if Path.IsPathRooted path 12 | then path 13 | else Path.GetFullPath (Path.Combine(Environment.CurrentDirectory, path)) 14 | 15 | type FilePath = 16 | | File of path: string 17 | | InvalidFile of fileName: string 18 | 19 | type CliArgs = 20 | | Version 21 | | InvalidArgs of error:string 22 | | Project of projectPath:string 23 | | Files of fsharpFiles: FilePath[] 24 | 25 | let getProject (args: string []) = 26 | try 27 | match args with 28 | | [| |] -> 29 | Directory.GetFiles(Environment.CurrentDirectory, "*.fsproj") 30 | |> Array.tryHead 31 | |> Option.map (fun projectPath -> Project(resolveFile projectPath)) 32 | |> function 33 | | Some project -> project 34 | | None -> 35 | Directory.GetFiles(Environment.CurrentDirectory, "*.*") 36 | |> Array.filter (fun file -> file.EndsWith ".fsx" || file.EndsWith ".fs") 37 | |> Array.map File 38 | |> Files 39 | 40 | | [| "--version" |] -> Version 41 | 42 | | multipleArgs -> 43 | if multipleArgs.Length = 1 && multipleArgs.[0].EndsWith ".fsproj" then 44 | Project (resolveFile multipleArgs.[0]) 45 | else if Directory.Exists(resolveFile multipleArgs.[0]) then 46 | Directory.GetFiles(resolveFile multipleArgs.[0], "*.fsproj") 47 | |> Array.tryHead 48 | |> Option.map (fun projectPath -> Project(resolveFile projectPath)) 49 | |> function 50 | | Some project -> project 51 | | None -> 52 | Directory.GetFiles(Environment.CurrentDirectory, "*.*") 53 | |> Array.filter (fun file -> file.EndsWith ".fsx" || file.EndsWith ".fs") 54 | |> Array.map File 55 | |> Files 56 | else 57 | multipleArgs 58 | |> Array.filter (fun file -> file.EndsWith ".fs" || file.EndsWith ".fsx") 59 | |> Array.map (fun file -> try File (resolveFile file) with _ -> InvalidFile file) 60 | |> Files 61 | 62 | with 63 | | error -> InvalidArgs error.Message 64 | 65 | let analyzeFiles (fsharpFiles: FilePath[]) = 66 | let errorCount = ResizeArray() 67 | for fsharpFile in fsharpFiles do 68 | match fsharpFile with 69 | | InvalidFile nonExistingFile -> 70 | AnsiConsole.MarkupLine("Analyzing file did not exist [red]{0}[/]", nonExistingFile) 71 | errorCount.Add(1) 72 | | File fsharpFile -> 73 | AnsiConsole.MarkupLine("Analyzing file [green]{0}[/]", fsharpFile) 74 | match Project.context fsharpFile with 75 | | None -> () 76 | | Some context -> 77 | let syntacticBlocks = SyntacticAnalysis.findSqlOperations context 78 | if not syntacticBlocks.IsEmpty then 79 | let messages = 80 | let connectionString = SqlAnalyzer.tryFindConnectionString context.FileName 81 | if isNull connectionString || String.IsNullOrWhiteSpace connectionString then 82 | [ 83 | for block in syntacticBlocks -> 84 | SqlAnalysis.createWarning "Missing environment variable 'NPGSQL_FSHARP'. Please set that variable to the connection string of your development database or put the connection string in a file called 'NPGSQL_FSHARP' next to your project file or in your workspace root." block.range 85 | ] 86 | else 87 | match SqlAnalysis.databaseSchema connectionString with 88 | | Result.Error connectionError -> 89 | [ 90 | for block in syntacticBlocks -> 91 | SqlAnalysis.createWarning (sprintf "Error while connecting to the development database using the connection string from environment variable 'NPGSQL_FSHARP' or put the connection string in a file called 'NPGSQL_FSHARP' relative next your project or in your project root. Connection error: %s" connectionError) block.range 92 | ] 93 | 94 | | Result.Ok schema -> 95 | syntacticBlocks 96 | |> List.collect (fun block -> SqlAnalysis.analyzeOperation block connectionString schema) 97 | |> List.distinctBy (fun message -> message.Range) 98 | 99 | for message in messages do 100 | 101 | let range = message.Range 102 | let source = SourceText.ofString(File.ReadAllText fsharpFile) 103 | if range.StartLine = range.EndLine then 104 | let marker = 105 | source.GetLineString(range.StartLine - 1) 106 | |> Seq.mapi (fun index token -> 107 | if index >= range.StartColumn && index < range.EndColumn 108 | then "[orange1]^[/]" 109 | else " " 110 | ) 111 | |> String.concat "" 112 | 113 | let original = 114 | source.GetLineString(range.StartLine - 1) 115 | |> Seq.mapi (fun index token -> 116 | if index >= range.StartColumn && index < range.EndColumn 117 | then "[orange1]" + token.ToString().EscapeMarkup() + "[/]" 118 | else token.ToString().EscapeMarkup() 119 | ) 120 | |> String.concat "" 121 | 122 | let before = (range.StartLine - 1).ToString() 123 | let current = range.StartLine.ToString() 124 | let after = (range.StartLine + 1).ToString() 125 | 126 | let maxLength = List.max [ before.Length; current.Length; after.Length ] 127 | 128 | AnsiConsole.MarkupLine(" [blue]{0} |[/]", before.PadLeft(maxLength, ' ')) 129 | AnsiConsole.MarkupLine(" [blue]{0} |[/] {1}", current.PadLeft(maxLength, ' '), original) 130 | AnsiConsole.MarkupLine(" [blue]{0} |[/] {1}", after.PadLeft(maxLength, ' '), marker) 131 | AnsiConsole.MarkupLine("[orange1]{0}[/]", message.Message.EscapeMarkup()) 132 | errorCount.Add(1) 133 | else 134 | let lines = [range.StartLine-1 .. range.EndLine+1] 135 | let maxLength = 136 | lines 137 | |> List.map (fun line -> line.ToString().Length) 138 | |> List.max 139 | 140 | for line in lines do 141 | if line = range.StartLine - 1 || line = range.EndLine + 1 142 | then AnsiConsole.MarkupLine(" [blue]{0} |[/] ", line.ToString().PadLeft(maxLength, ' ')) 143 | else AnsiConsole.MarkupLine(" [blue]{0} |[/] {1}", line.ToString().PadLeft(maxLength, ' '), source.GetLineString(line - 1).EscapeMarkup()) 144 | 145 | AnsiConsole.MarkupLine("[orange1]{0}[/]", message.Message.EscapeMarkup()) 146 | errorCount.Add(1) 147 | let exitCode = errorCount.Sum() 148 | Console.WriteLine() 149 | if exitCode = 0 150 | then AnsiConsole.MarkupLine("[green]No errors found[/]") 151 | elif exitCode = 1 152 | then AnsiConsole.MarkupLine("[orange1]Found 1 error[/]", exitCode) 153 | else AnsiConsole.MarkupLine("[orange1]Found {0} errors[/]", exitCode) 154 | exitCode 155 | 156 | let [] projectFile = TextFile<"./Ubik.fsproj">.Text 157 | 158 | let projectVersion = 159 | let doc = XmlDocument() 160 | use content = new MemoryStream(Text.Encoding.UTF8.GetBytes projectFile) 161 | doc.Load(content) 162 | doc.GetElementsByTagName("Version").[0].InnerText 163 | 164 | [] 165 | let main argv = 166 | match getProject argv with 167 | | InvalidArgs error -> 168 | AnsiConsole.MarkupLine("[red]{0}: {1}[/]", "Error occured while reading CLI arguments: ", error) 169 | 1 170 | 171 | | Version -> 172 | printfn "%s" projectVersion 173 | 0 174 | 175 | | Files files -> analyzeFiles files 176 | 177 | | Project project -> 178 | AnsiConsole.MarkupLine("Analyzing project [blue]{0}[/]", project) 179 | 180 | let document = XmlDocument() 181 | document.LoadXml(File.ReadAllText project) 182 | 183 | let fsharpFileNodes = document.GetElementsByTagName("Compile") 184 | analyzeFiles [| 185 | for item in 0 .. fsharpFileNodes.Count - 1 do 186 | let relativePath = fsharpFileNodes.[item].Attributes.["Include"].InnerText 187 | let projectParent = Directory.GetParent project 188 | let filePath = Path.Combine(projectParent.FullName, relativePath) 189 | if filePath.EndsWith ".fs" || filePath.EndsWith ".fsx" 190 | then File(filePath) 191 | |] 192 | -------------------------------------------------------------------------------- /src/Ubik/Project.fs: -------------------------------------------------------------------------------- 1 | module Project 2 | 3 | open System 4 | open System.IO 5 | open FSharp.Compiler.SourceCodeServices 6 | open FSharp.Compiler.Text 7 | open Npgsql.FSharp.Analyzers.Core 8 | 9 | let checker = 10 | FSharpChecker.Create( 11 | keepAllBackgroundResolutions = true, 12 | keepAssemblyContents = true, 13 | ImplicitlyStartBackgroundWork = true) 14 | 15 | let dumpOpts (opts : FSharpProjectOptions) = 16 | printfn "FSharpProjectOptions.OtherOptions ->" 17 | opts.OtherOptions 18 | |> Array.iter(printfn "%s") 19 | 20 | let loadProject file = 21 | async { 22 | let! source = IO.File.ReadAllTextAsync file |> Async.AwaitTask 23 | let! (opts, error) = checker.GetProjectOptionsFromScript(file, SourceText.ofString source, assumeDotNetFramework = false, useSdkRefs = true, useFsiAuxLib = true, otherFlags = [|"--targetprofile:netstandard" |]) 24 | let newOO = 25 | opts.OtherOptions 26 | |> Array.map(fun i -> 27 | if i.StartsWith("-r:") then 28 | let path = i.Split("-r:", StringSplitOptions.RemoveEmptyEntries).[0] 29 | 30 | sprintf "-r:%s" (IO.FileInfo(path).FullName) 31 | else 32 | i 33 | ) 34 | // dumpOpts opts 35 | return file, opts 36 | } |> Async.RunSynchronously 37 | 38 | let typeCheckFile (file,opts) = 39 | let text = File.ReadAllText file 40 | let st = SourceText.ofString text 41 | let (parseRes, checkAnswer) = 42 | checker.ParseAndCheckFileInProject(file, 1, st, opts) 43 | |> Async.RunSynchronously 44 | 45 | match checkAnswer with 46 | | FSharpCheckFileAnswer.Aborted -> 47 | printfn "Checking of file %s aborted because %A" file parseRes.Errors 48 | None 49 | | FSharpCheckFileAnswer.Succeeded(c) -> 50 | Some (file, text, parseRes, c) 51 | 52 | let entityCache = EntityCache() 53 | 54 | let getAllEntities (checkResults: FSharpCheckFileResults) (publicOnly: bool) : AssemblySymbol list = 55 | try 56 | let res = [ 57 | yield! AssemblyContentProvider.getAssemblySignatureContent AssemblyContentType.Full checkResults.PartialAssemblySignature 58 | let ctx = checkResults.ProjectContext 59 | let assembliesByFileName = 60 | ctx.GetReferencedAssemblies() 61 | |> Seq.groupBy (fun asm -> asm.FileName) 62 | |> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms) 63 | |> Seq.toList 64 | |> List.rev // if mscorlib.dll is the first then FSC raises exception when we try to 65 | // get Content.Entities from it. 66 | 67 | for fileName, signatures in assembliesByFileName do 68 | let contentType = if publicOnly then Public else Full 69 | let content = AssemblyContentProvider.getAssemblyContent entityCache.Locking contentType fileName signatures 70 | yield! content 71 | ] 72 | res 73 | with 74 | | _ -> [] 75 | 76 | let createContext (file, text: string, p: FSharpParseFileResults,c: FSharpCheckFileResults) = 77 | match p.ParseTree with 78 | | Some parseTree -> 79 | let context : SqlAnalyzerContext = { 80 | FileName = file 81 | Content = text.Split([|'\n'|]) 82 | ParseTree = parseTree 83 | Symbols = c.PartialAssemblySignature.Entities |> Seq.toList 84 | } 85 | 86 | Some context 87 | | _ -> 88 | None 89 | 90 | let context proj = 91 | let path = 92 | Path.Combine(Environment.CurrentDirectory, proj) 93 | |> Path.GetFullPath 94 | 95 | loadProject path 96 | |> typeCheckFile 97 | |> Option.bind createContext 98 | -------------------------------------------------------------------------------- /src/Ubik/Ubik.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | ubik 7 | true 8 | true 9 | Major 10 | 3.28.0 11 | Escape error messages before logging them to console 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | true 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/NpgsqlFSharpAnalyzer.Tests/Analyzer.fs: -------------------------------------------------------------------------------- 1 | module AnalyzerBootstrap 2 | 3 | open System 4 | open System.IO 5 | open FSharp.Compiler.SourceCodeServices 6 | open FSharp.Compiler.Text 7 | open FSharp.Analyzers.SDK 8 | 9 | let checker = 10 | FSharpChecker.Create( 11 | keepAllBackgroundResolutions = true, 12 | keepAssemblyContents = true, 13 | ImplicitlyStartBackgroundWork = true) 14 | 15 | let dumpOpts (opts : FSharpProjectOptions) = 16 | printfn "FSharpProjectOptions.OtherOptions ->" 17 | opts.OtherOptions 18 | |> Array.iter(printfn "%s") 19 | 20 | let loadProject file = 21 | async { 22 | let! source = IO.File.ReadAllTextAsync file |> Async.AwaitTask 23 | let! (opts, error) = checker.GetProjectOptionsFromScript(file, SourceText.ofString source, assumeDotNetFramework = false, useSdkRefs = true, useFsiAuxLib = true, otherFlags = [|"--targetprofile:netstandard" |]) 24 | let newOO = 25 | opts.OtherOptions 26 | |> Array.map(fun i -> 27 | if i.StartsWith("-r:") then 28 | let path = i.Split("-r:", StringSplitOptions.RemoveEmptyEntries).[0] 29 | 30 | sprintf "-r:%s" (IO.FileInfo(path).FullName) 31 | else 32 | i 33 | ) 34 | // dumpOpts opts 35 | return file, opts 36 | } |> Async.RunSynchronously 37 | 38 | let typeCheckFile (file,opts) = 39 | let text = File.ReadAllText file 40 | let st = SourceText.ofString text 41 | let (parseRes, checkAnswer) = 42 | checker.ParseAndCheckFileInProject(file, 1, st, opts) 43 | |> Async.RunSynchronously 44 | 45 | match checkAnswer with 46 | | FSharpCheckFileAnswer.Aborted -> 47 | printfn "Checking of file %s aborted because %A" file parseRes.Errors 48 | None 49 | | FSharpCheckFileAnswer.Succeeded(c) -> 50 | Some (file, text, parseRes, c) 51 | 52 | let entityCache = EntityCache() 53 | 54 | let getAllEntities (checkResults: FSharpCheckFileResults) (publicOnly: bool) : AssemblySymbol list = 55 | try 56 | let res = [ 57 | yield! AssemblyContentProvider.getAssemblySignatureContent AssemblyContentType.Full checkResults.PartialAssemblySignature 58 | let ctx = checkResults.ProjectContext 59 | let assembliesByFileName = 60 | ctx.GetReferencedAssemblies() 61 | |> Seq.groupBy (fun asm -> asm.FileName) 62 | |> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms) 63 | |> Seq.toList 64 | |> List.rev // if mscorlib.dll is the first then FSC raises exception when we try to 65 | // get Content.Entities from it. 66 | 67 | for fileName, signatures in assembliesByFileName do 68 | let contentType = if publicOnly then Public else Full 69 | let content = AssemblyContentProvider.getAssemblyContent entityCache.Locking contentType fileName signatures 70 | yield! content 71 | ] 72 | res 73 | with 74 | | _ -> [] 75 | 76 | let createContext (file, text: string, p: FSharpParseFileResults,c: FSharpCheckFileResults) = 77 | match p.ParseTree, c.ImplementationFile with 78 | | Some pt, Some tast -> 79 | let context : Context = { 80 | FileName = file 81 | Content = text.Split([|'\n'|]) 82 | ParseTree = pt 83 | TypedTree = tast 84 | Symbols = c.PartialAssemblySignature.Entities |> Seq.toList 85 | GetAllEntities = getAllEntities c 86 | } 87 | Some context 88 | | _ -> None 89 | 90 | let context proj = 91 | let path = 92 | Path.Combine(Environment.CurrentDirectory, proj) 93 | |> Path.GetFullPath 94 | 95 | loadProject path 96 | |> typeCheckFile 97 | |> Option.bind createContext 98 | -------------------------------------------------------------------------------- /tests/NpgsqlFSharpAnalyzer.Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "NpgsqlFSharpAnalyzer.Tests" 17 | let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" 18 | let [] AssemblyVersion = "3.26.1" 19 | let [] AssemblyMetadata_ReleaseDate = "2021-05-06T00:00:00.0000000" 20 | let [] AssemblyFileVersion = "3.26.1" 21 | let [] AssemblyInformationalVersion = "3.26.1" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "237f46ee0d5505c9adc4e1615dd231b89cb1b992" 24 | -------------------------------------------------------------------------------- /tests/NpgsqlFSharpAnalyzer.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | module ExpectoTemplate 2 | 3 | open Expecto 4 | open Expecto.Logging 5 | 6 | let config = { defaultConfig with verbosity = LogLevel.Verbose } 7 | 8 | [] 9 | let main argv = Tests.runTestsInAssembly config argv 10 | -------------------------------------------------------------------------------- /tests/NpgsqlFSharpAnalyzer.Tests/NpgsqlFSharpAnalyzer.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/NpgsqlFSharpAnalyzer.Tests/ParseDeclareTests.fs: -------------------------------------------------------------------------------- 1 | module ParseDeclareTests 2 | 3 | open Expecto 4 | open NpgsqlFSharpParser 5 | 6 | let testDeclare inputQuery expected = 7 | test inputQuery { 8 | match Parser.parse inputQuery with 9 | | Ok (Expr.DeclareQuery query) -> 10 | Expect.equal query expected "The query is parsed correctly" 11 | | Ok somethingElse -> 12 | failwithf "Unexpected declare statement %A" somethingElse 13 | | Error errorMsg -> 14 | failwith errorMsg 15 | } 16 | 17 | let ftestDeclare inputQuery expected = 18 | ftest inputQuery { 19 | match Parser.parse inputQuery with 20 | | Ok (Expr.DeclareQuery query) -> 21 | Expect.equal query expected "The query is parsed correctly" 22 | | Ok somethingElse -> 23 | failwithf "Unexpected declare statement %A" somethingElse 24 | | Error errorMsg -> 25 | failwith errorMsg 26 | } 27 | 28 | [] 29 | let declareQueryTests = testList "Parse DECLARE tests" [ 30 | testDeclare "DECLARE c1 CURSOR FOR SELECT NOW();" ({ 31 | Parameter = "c1" 32 | Query = Expr.SelectQuery { SelectExpr.Default with Columns = [Expr.Function("NOW", [])] } 33 | } |> Cursor) 34 | ] 35 | 36 | -------------------------------------------------------------------------------- /tests/NpgsqlFSharpAnalyzer.Tests/ParseDeleteTests.fs: -------------------------------------------------------------------------------- 1 | module ParserDeleteTests 2 | 3 | open Expecto 4 | open NpgsqlFSharpParser 5 | 6 | let testDelete inputQuery expected = 7 | test inputQuery { 8 | match Parser.parse inputQuery with 9 | | Ok (Expr.DeleteQuery query) -> 10 | Expect.equal query expected "The query is parsed correctly" 11 | | Ok somethingElse -> 12 | failwithf "Unexpected delete statement %A" somethingElse 13 | | Error errorMsg -> 14 | failwith errorMsg 15 | } 16 | 17 | let ftestDelete inputQuery expected = 18 | ftest inputQuery { 19 | match Parser.parse inputQuery with 20 | | Ok (Expr.DeleteQuery query) -> 21 | Expect.equal query expected "The query is parsed correctly" 22 | | Ok somethingElse -> 23 | failwithf "Unexpected delete statement %A" somethingElse 24 | | Error errorMsg -> 25 | failwith errorMsg 26 | } 27 | 28 | [] 29 | let deleteQueryTests = testList "Parse DELETE tests" [ 30 | testDelete "DELETE FROM users WHERE last_login IS NULL" { 31 | DeleteExpr.Default with 32 | Table = "users" 33 | Where = Some (Expr.Equals(Expr.Null, Expr.Ident "last_login")) 34 | } 35 | 36 | testDelete "DELETE FROM users WHERE luck = 'bad' RETURNING *" { 37 | DeleteExpr.Default with 38 | Table = "users" 39 | Where = Some (Expr.Equals(Expr.Ident "luck", Expr.StringLiteral "bad")) 40 | Returning = [Expr.Star] 41 | } 42 | ] 43 | -------------------------------------------------------------------------------- /tests/NpgsqlFSharpAnalyzer.Tests/ParseInsertTests.fs: -------------------------------------------------------------------------------- 1 | module ParseInsertTests 2 | 3 | open Expecto 4 | open NpgsqlFSharpParser 5 | 6 | let testInsert inputQuery expected = 7 | test inputQuery { 8 | match Parser.parse inputQuery with 9 | | Ok (Expr.InsertQuery query) -> 10 | Expect.equal query expected "The query is parsed correctly" 11 | | Ok somethingElse -> 12 | failwithf "Unexpected insert statement %A" somethingElse 13 | | Error errorMsg -> 14 | failwith errorMsg 15 | } 16 | 17 | let ftestInsert inputQuery expected = 18 | ftest inputQuery { 19 | match Parser.parse inputQuery with 20 | | Ok (Expr.InsertQuery query) -> 21 | Expect.equal query expected "The query is parsed correctly" 22 | | Ok somethingElse -> 23 | failwithf "Unexpected insert statement %A" somethingElse 24 | | Error errorMsg -> 25 | failwith errorMsg 26 | } 27 | 28 | [] 29 | let insertQueryTests = testList "Parse INSERT queries" [ 30 | testInsert "INSERT INTO users (username, active) VALUES (@username, true)" { 31 | InsertExpr.Default with 32 | Table = "users" 33 | Columns = ["username"; "active"] 34 | Values = [ Expr.Parameter("@username"); Expr.Boolean true ] 35 | } 36 | 37 | testInsert "INSERT INTO users (username, active) VALUES (@username, true) RETURNING *" { 38 | InsertExpr.Default with 39 | Table = "users" 40 | Columns = ["username"; "active"] 41 | Values = [ Expr.Parameter("@username"); Expr.Boolean true ] 42 | Returning = [Expr.Star] 43 | } 44 | ] 45 | -------------------------------------------------------------------------------- /tests/NpgsqlFSharpAnalyzer.Tests/ParseSetTests.fs: -------------------------------------------------------------------------------- 1 | module ParseSetTests 2 | 3 | open Expecto 4 | open NpgsqlFSharpParser 5 | 6 | let testSet inputQuery expected = 7 | test inputQuery { 8 | match Parser.parse inputQuery with 9 | | Ok (Expr.SetQuery query) -> 10 | Expect.equal query expected "The query is parsed correctly" 11 | | Ok somethingElse -> 12 | failwithf "Unexpected set statement %A" somethingElse 13 | | Error errorMsg -> 14 | failwith errorMsg 15 | } 16 | 17 | let ftestSet inputQuery expected = 18 | ftest inputQuery { 19 | match Parser.parse inputQuery with 20 | | Ok (Expr.SetQuery query) -> 21 | Expect.equal query expected "The query is parsed correctly" 22 | | Ok somethingElse -> 23 | failwithf "Unexpected set statement %A" somethingElse 24 | | Error errorMsg -> 25 | failwith errorMsg 26 | } 27 | 28 | [] 29 | let setQueryTests = testList "Parse SET tests" [ 30 | 31 | testSet "SET DateStyle = ISO" { 32 | SetExpr.Default with 33 | Parameter = "DateStyle" 34 | Value = Some (Expr.Ident "ISO") 35 | } 36 | 37 | testSet "SET timezone TO 'Europe/Rome'" { 38 | SetExpr.Default with 39 | Parameter = "timezone" 40 | Value = Some (Expr.StringLiteral "Europe/Rome") 41 | } 42 | ] 43 | 44 | -------------------------------------------------------------------------------- /tests/NpgsqlFSharpAnalyzer.Tests/ParseUpdateTests.fs: -------------------------------------------------------------------------------- 1 | module ParseUpdateTests 2 | 3 | open Expecto 4 | open NpgsqlFSharpParser 5 | 6 | let testUpdate inputQuery expected = 7 | test inputQuery { 8 | match Parser.parse inputQuery with 9 | | Ok (Expr.UpdateQuery query) -> 10 | Expect.equal query expected "The query is parsed correctly" 11 | | Ok somethingElse -> 12 | failwithf "Unexpected update statement %A" somethingElse 13 | | Error errorMsg -> 14 | failwith errorMsg 15 | } 16 | 17 | let ftestUpdate inputQuery expected = 18 | ftest inputQuery { 19 | match Parser.parse inputQuery with 20 | | Ok (Expr.UpdateQuery query) -> 21 | Expect.equal query expected "The query is parsed correctly" 22 | | Ok somethingElse -> 23 | failwithf "Unexpected update statement %A" somethingElse 24 | | Error errorMsg -> 25 | failwith errorMsg 26 | } 27 | 28 | [] 29 | let updateQueryTests = testList "Parse UPDATE queries" [ 30 | testUpdate "UPDATE users SET enabled = @enabled WHERE user_id = @user_id" { 31 | UpdateExpr.Default with 32 | Table = "users" 33 | Where = Some(Expr.Equals(Expr.Ident "user_id", Expr.Parameter "@user_id")) 34 | Assignments = [ 35 | Expr.Equals(Expr.Ident "enabled", Expr.Parameter "@enabled") 36 | ] 37 | } 38 | 39 | testUpdate "UPDATE users SET enabled = @enabled, roles = @roles WHERE user_id = @user_id" { 40 | UpdateExpr.Default with 41 | Table = "users" 42 | Where = Some(Expr.Equals(Expr.Ident "user_id", Expr.Parameter "@user_id")) 43 | Assignments = [ 44 | Expr.Equals(Expr.Ident "enabled", Expr.Parameter "@enabled") 45 | Expr.Equals(Expr.Ident "roles", Expr.Parameter "@roles") 46 | ] 47 | } 48 | 49 | testUpdate "UPDATE users SET enabled = @enabled, roles = @roles WHERE user_id = @user_id RETURNING *" { 50 | UpdateExpr.Default with 51 | Table = "users" 52 | 53 | Assignments = [ 54 | Expr.Equals(Expr.Ident "enabled", Expr.Parameter "@enabled") 55 | Expr.Equals(Expr.Ident "roles", Expr.Parameter "@roles") 56 | ] 57 | 58 | Where = Some(Expr.Equals(Expr.Ident "user_id", Expr.Parameter "@user_id")) 59 | 60 | Returning = [Expr.Star] 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /tests/examples/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | false 4 | false 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/examples/hashing/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | // Auto-Generated by FAKE; do not edit 2 | namespace System 3 | open System.Reflection 4 | 5 | [] 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | do () 14 | 15 | module internal AssemblyVersionInformation = 16 | let [] AssemblyTitle = "examples" 17 | let [] AssemblyProduct = "NpgsqlFSharpAnalyzer" 18 | let [] AssemblyVersion = "3.26.1" 19 | let [] AssemblyMetadata_ReleaseDate = "2021-05-06T00:00:00.0000000" 20 | let [] AssemblyFileVersion = "3.26.1" 21 | let [] AssemblyInformationalVersion = "3.26.1" 22 | let [] AssemblyMetadata_ReleaseChannel = "release" 23 | let [] AssemblyMetadata_GitHash = "237f46ee0d5505c9adc4e1615dd231b89cb1b992" 24 | -------------------------------------------------------------------------------- /tests/examples/hashing/MultipleNestedModules.fs: -------------------------------------------------------------------------------- 1 | namespace App 2 | 3 | module Domain = 4 | type Article = { 5 | Id : int ; 6 | Name: string; 7 | Stock: int 8 | } 9 | 10 | module Data = 11 | open Domain 12 | open Npgsql.FSharp 13 | 14 | let getConnectionString () = 15 | System.Environment.GetEnvironmentVariable "DATABASE_CONNECTION_STRING" 16 | 17 | [] 18 | let getArticlesQuery = "SELECT x FROM test.articles" 19 | 20 | let getArticles () = 21 | getConnectionString() 22 | |> Sql.connect 23 | |> Sql.query getArticlesQuery 24 | |> Sql.execute (fun read -> 25 | { 26 | Id = read.int "id" 27 | Name = read.text "art_name" 28 | Stock = read.int "stock" 29 | }) 30 | -------------------------------------------------------------------------------- /tests/examples/hashing/MultipleNestedModules.fsx: -------------------------------------------------------------------------------- 1 | #r "nuget:Npgsql.FSharp" 2 | 3 | module Domain = 4 | type Article = { 5 | Id : int ; 6 | Name: string; 7 | Stock: int 8 | } 9 | 10 | module Data = 11 | open Domain 12 | open Npgsql.FSharp 13 | 14 | let getConnectionString () = 15 | System.Environment.GetEnvironmentVariable "DATABASE_CONNECTION_STRING" 16 | 17 | let getArticles () = 18 | getConnectionString() 19 | |> Sql.connect 20 | |> Sql.query "SELECT x FROM test.articles" 21 | |> Sql.execute (fun read -> 22 | { 23 | Id = read.int "id" 24 | Name = read.text "art_name" 25 | Stock = read.int "stock" 26 | }) 27 | -------------------------------------------------------------------------------- /tests/examples/hashing/SprintfBlock.fs: -------------------------------------------------------------------------------- 1 | module SprintfBlock 2 | 3 | let routerPaths typeName method = sprintf "/api/%s" method 4 | -------------------------------------------------------------------------------- /tests/examples/hashing/castingNonNullableStaysNonNullable.fs: -------------------------------------------------------------------------------- 1 | module castingNonNullableStaysNonNullable 2 | 3 | open Npgsql.FSharp 4 | 5 | let userIds connection = 6 | connection 7 | |> Sql.connect 8 | |> Sql.query "SELECT user_id::text AS useridtext FROM users" 9 | |> Sql.execute (fun read -> read.text "useridtext") 10 | 11 | let moreIds connection = 12 | connection 13 | |> Sql.connect 14 | |> Sql.query "SELECT user_id::text as useridtext FROM users" 15 | |> Sql.execute (fun read -> read.text "useridtext") 16 | 17 | let withoutAlias connection = 18 | connection 19 | |> Sql.connect 20 | |> Sql.query "SELECT user_id::text FROM users" 21 | |> Sql.execute (fun read -> read.text "user_id") 22 | -------------------------------------------------------------------------------- /tests/examples/hashing/detectingDynamicListsInTransactions.fs: -------------------------------------------------------------------------------- 1 | module DetectingDynamicListsInTransactions 2 | 3 | open Npgsql.FSharp 4 | 5 | let transaction connection values = 6 | connection 7 | |> Sql.connect 8 | |> Sql.executeTransaction [ 9 | "DELETE FROM users WHERE user_id = @user_id", [ 10 | for value in values -> [ 11 | "@user_id", Sql.int value 12 | ] 13 | ] 14 | ] 15 | 16 | let transactionWithYield connection values = 17 | connection 18 | |> Sql.connect 19 | |> Sql.executeTransaction [ 20 | "DELETE FROM users WHERE user_id = @user_id", [ 21 | for value in values do yield [ 22 | "@user_id", Sql.int value 23 | ] 24 | ] 25 | ] 26 | -------------------------------------------------------------------------------- /tests/examples/hashing/detectingEmptyParameterSet.fs: -------------------------------------------------------------------------------- 1 | module DetectingEmptyParameterSet 2 | 3 | open Npgsql.FSharp 4 | 5 | let transaction connection = 6 | connection 7 | |> Sql.connect 8 | |> Sql.executeTransaction [ 9 | "SELECT * FROM users WHERE user_id = @user_id", [ 10 | [ ] 11 | ] 12 | ] 13 | -------------------------------------------------------------------------------- /tests/examples/hashing/errorsInTransactions.fs: -------------------------------------------------------------------------------- 1 | module ErrorsInTransactions 2 | 3 | open Npgsql.FSharp 4 | 5 | let executeMultipleQueries = 6 | "connectionString" 7 | |> Sql.connect 8 | |> Sql.executeTransaction [ 9 | "UPDATE users SET user_id = @user_id", [ ] 10 | "DELTE FROM meters", [ ] 11 | "INSERT INTO users (username) VALUES (@username)", [ 12 | [ "@username", Sql.int 42 ] 13 | ] 14 | ] 15 | -------------------------------------------------------------------------------- /tests/examples/hashing/examples.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /tests/examples/hashing/executingQueryInsteadOfNonQuery.fs: -------------------------------------------------------------------------------- 1 | module ExecutingQueryInsteadOfNonQuery 2 | 3 | open Npgsql.FSharp 4 | 5 | let findRoles connection = 6 | connection 7 | |> Sql.query "INSERT INTO users (username, roles) VALUES (@username, @roles)" 8 | |> Sql.parameters [ "@username", Sql.string "Hello"; "@roles", Sql.stringArray [| |] ] 9 | |> Sql.execute (fun read -> read.stringArray "roles") 10 | -------------------------------------------------------------------------------- /tests/examples/hashing/insertQueryWithNonNullableParameter.fs: -------------------------------------------------------------------------------- 1 | module InsertQueryWithNonNullableParameter 2 | 3 | open Npgsql.FSharp 4 | 5 | let findUsers connection = 6 | connection 7 | |> Sql.query "INSERT INTO users (username, active) VALUES (@username, @active) RETURNING *" 8 | |> Sql.parameters [ "@username", Sql.dbnull; "@active", Sql.bool true ] 9 | |> Sql.execute (fun read -> read.text "username") 10 | -------------------------------------------------------------------------------- /tests/examples/hashing/parameterTypeMismatch.fs: -------------------------------------------------------------------------------- 1 | module parameterTypeMismatch 2 | 3 | open Npgsql.FSharp 4 | 5 | let connectionString = "Dummy connection string" 6 | 7 | let findUsers() = 8 | connectionString 9 | |> Sql.connect 10 | |> Sql.query "SELECT * FROM users WHERE user_id = @user_id" 11 | |> Sql.parameters [ "user_id", Sql.bit false ] 12 | |> Sql.execute (fun read -> read.text "username") 13 | -------------------------------------------------------------------------------- /tests/examples/hashing/readAttemptIntegerTypeMismatch.fs: -------------------------------------------------------------------------------- 1 | module readAttemptIntegerTypeMismatch 2 | 3 | open Npgsql.FSharp 4 | 5 | let connectionString = "Dummy connection string" 6 | 7 | let findUsers() = 8 | connectionString 9 | |> Sql.connect 10 | |> Sql.query "SELECT * FROM users" 11 | |> Sql.executeAsync (fun read -> 12 | let user_id = read.bool "user_id" 13 | let username = read.text "username" 14 | let active = read.bool "active" 15 | (user_id, username, active)) 16 | -------------------------------------------------------------------------------- /tests/examples/hashing/readingTextArray.fs: -------------------------------------------------------------------------------- 1 | module ReadingTextArray 2 | 3 | open Npgsql.FSharp 4 | 5 | let findRoles connection = 6 | connection 7 | |> Sql.query "SELECT roles FROM users" 8 | |> Sql.execute (fun read -> read.string "roles") // should be read.stringArray 9 | -------------------------------------------------------------------------------- /tests/examples/hashing/readingUuidArray.fs: -------------------------------------------------------------------------------- 1 | module ReadingUuidArray 2 | 3 | open Npgsql.FSharp 4 | 5 | let findRoles connection = 6 | connection 7 | |> Sql.query "SELECT codes FROM users" 8 | |> Sql.execute (fun read -> read.uuid "codes") // should be read.uuidArray 9 | -------------------------------------------------------------------------------- /tests/examples/hashing/selectWithInnerJoins.fs: -------------------------------------------------------------------------------- 1 | module SelectWithInnerJoins 2 | 3 | open Npgsql.FSharp 4 | 5 | let [] usersWithRoles = """ 6 | SELECT * FROM users 7 | JOIN user_roles ON users.user_id = user_roles.user_id 8 | WHERE users.user_id = @user_id AND role_description = @role 9 | """ 10 | 11 | let usersAndTheirRoles connectionString = 12 | connectionString 13 | |> Sql.connect 14 | |> Sql.query usersWithRoles 15 | |> Sql.parameters [ "@user_id", Sql.intOrNone None; "@role", Sql.text "User" ] 16 | |> Sql.execute (fun read -> read.int "user_id") 17 | -------------------------------------------------------------------------------- /tests/examples/hashing/selectWithNonNullableColumnComparison.fs: -------------------------------------------------------------------------------- 1 | module SelectWithNonNullableColumnComparison 2 | 3 | open Npgsql.FSharp 4 | 5 | let findUsernames connectionString = 6 | connectionString 7 | |> Sql.connect 8 | |> Sql.query "SELECT * FROM users WHERE user_id = @user_id AND username = @username" 9 | |> Sql.parameters [ "@user_id", Sql.intOrNone None; "@username", Sql.text "User" ] 10 | |> Sql.execute (fun read -> read.int "user_id") 11 | -------------------------------------------------------------------------------- /tests/examples/hashing/semanticAnalysis-missingColumn.fs: -------------------------------------------------------------------------------- 1 | module semanticAnalysis 2 | 3 | open Npgsql.FSharp 4 | 5 | let connectionString = "Dummy connection string" 6 | 7 | let findUsers() = 8 | connectionString 9 | |> Sql.connect 10 | |> Sql.query "SELECT non_existent FROM users" 11 | |> Sql.executeAsync (fun read -> 12 | {| 13 | userId = read.int "user_id" 14 | username = read.text "username" 15 | active = read.bool "active" 16 | |}) 17 | -------------------------------------------------------------------------------- /tests/examples/hashing/semanticAnalysis-missingParameter.fs: -------------------------------------------------------------------------------- 1 | module semanticAnalysis_missingParameter 2 | 3 | open Npgsql.FSharp 4 | 5 | let connectionString = "Dummy connection string" 6 | 7 | let findUsers() = 8 | connectionString 9 | |> Sql.connect 10 | |> Sql.query "SELECT * FROM users WHERE user_id = @user_id AND active = @active" 11 | |> Sql.parameters [ "user_id", Sql.int64 42L ] 12 | |> Sql.executeAsync (fun read -> 13 | let userId = read.int "user_id" 14 | let username = read.text "username" 15 | let active = read.bool "active" 16 | (userId, username, active)) 17 | -------------------------------------------------------------------------------- /tests/examples/hashing/semanticAnalysis-redundantParameters.fs: -------------------------------------------------------------------------------- 1 | module semanticAnalysis_redundantParameters 2 | 3 | open Npgsql.FSharp 4 | 5 | let connectionString = "Dummy connection string" 6 | 7 | let findUsers() = 8 | connectionString 9 | |> Sql.connect 10 | |> Sql.query "SELECT * FROM users" 11 | |> Sql.parameters [ "user_id", Sql.int 42 ] 12 | |> Sql.execute (fun read -> read.text "username") 13 | -------------------------------------------------------------------------------- /tests/examples/hashing/semanticAnalysis-typeMismatch.fs: -------------------------------------------------------------------------------- 1 | module semanticAnalysis_typeMismatch 2 | 3 | open Npgsql.FSharp 4 | 5 | let connectionString = "Dummy connection string" 6 | 7 | let findUsers() = 8 | connectionString 9 | |> Sql.connect 10 | |> Sql.query "SELECT * FROM users" 11 | |> Sql.executeAsync (fun read -> 12 | let user_id = read.int "user_id" 13 | let username = read.text "username" 14 | // Sql.readInt should be Sql.readBool 15 | let active = read.int "active" 16 | (user_id, username, active)) 17 | -------------------------------------------------------------------------------- /tests/examples/hashing/syntacticAnalysis-literalStrings.fs: -------------------------------------------------------------------------------- 1 | module semanticAnalysis_literalStrings 2 | 3 | open Npgsql.FSharp 4 | 5 | let connectionString = "Dummy connection string" 6 | 7 | let [] query = "SELECT * FROM users" 8 | 9 | let [] maskedQuery = query 10 | 11 | let findUsers() = 12 | connectionString 13 | |> Sql.connect 14 | |> Sql.query maskedQuery 15 | |> Sql.executeAsync (fun read -> read.text "username") 16 | -------------------------------------------------------------------------------- /tests/examples/hashing/syntacticAnalysis.fs: -------------------------------------------------------------------------------- 1 | module Postgres 2 | 3 | open Npgsql.FSharp 4 | 5 | type OptionBuilder() = 6 | member x.Bind(v,f) = Option.bind f v 7 | member x.Return v = Some v 8 | member x.ReturnFrom o = o 9 | member x.Zero () = None 10 | 11 | let option = OptionBuilder() 12 | 13 | let connectionString = "Dummy connection string" 14 | 15 | let findSingleUser(userId: int) = 16 | connectionString 17 | |> Sql.connect 18 | |> Sql.query "SELECT * FROM users WHERE user_id = @user_id" 19 | |> Sql.parameters [ "@user_id", Sql.int userId ] 20 | |> Sql.executeAsync (fun read -> 21 | option { 22 | let username = read.text "username" 23 | let user_id = read.int "user_id" 24 | let active = read.bool "active" 25 | return (username, user_id, active) 26 | }) 27 | 28 | let findUsers() = 29 | Sql.host "localhost" 30 | |> Sql.connectFromConfig 31 | |> Sql.query "SELECT * FROM users" 32 | |> Sql.parameters [ "@whatever", Sql.bit false; "@another", Sql.int 12 ] 33 | |> Sql.executeAsync (fun read -> 34 | option { 35 | let username = read.text "username" 36 | let user_id = read.int "user_id" 37 | let active = read.bool "active" 38 | return (username, user_id, active) 39 | }) 40 | 41 | let findNumberOfUsers() = 42 | Sql.host "localhost" 43 | |> Sql.connectFromConfig 44 | |> Sql.query "SELECT COUNT(*) as count FROM users" 45 | |> Sql.execute (fun read -> read.int64 "count") 46 | 47 | let findNumberOfUsersAfterCallingPrintExpressions () = 48 | printfn "Non blocking expression" 49 | printf "Non blocking expression" 50 | Sql.host "localhost" 51 | |> Sql.connectFromConfig 52 | |> Sql.query "SELECT COUNT(*) as count FROM users" 53 | |> Sql.execute (fun read -> read.int64 "count") 54 | 55 | let executeFunction() = 56 | Sql.host "localhost" 57 | |> Sql.connectFromConfig 58 | |> Sql.func "getNumberOfUsers" 59 | |> Sql.execute (fun read -> read.text "username") 60 | 61 | let executeFunctionAsync() = 62 | Sql.host "localhost" 63 | |> Sql.connectFromConfig 64 | |> Sql.func "getNumberOfUsers" 65 | |> Sql.executeAsync (fun read -> read.text "username") 66 | |> Async.AwaitTask 67 | 68 | let executeTransactionAsync() = 69 | Sql.host "localhost" 70 | |> Sql.connectFromConfig 71 | |> Sql.executeTransactionAsync [ "SOME QUERY", [ [ "@someNumber", Sql.int 42 ] ] ] 72 | |> Async.AwaitTask 73 | 74 | let executeTransactionWithEmptyList() = 75 | Sql.host "localhost" 76 | |> Sql.connectFromConfig 77 | |> Sql.executeTransactionAsync [ ] 78 | |> Async.AwaitTask 79 | 80 | type Whatever() = 81 | member this.Hello = 82 | let whateverSql() = 83 | Sql.host "localhost" 84 | |> Sql.connectFromConfig 85 | |> Sql.query "SELECT COUNT(*) as count FROM users" 86 | |> Sql.execute (fun read -> read.int64 "count") 87 | 88 | () 89 | 90 | let doCoolStuff() = async { 91 | return Sql.host "localhost" 92 | |> Sql.connectFromConfig 93 | |> Sql.query "SELECT COUNT(*) as count FROM users" 94 | |> Sql.execute (fun read -> read.int64 "count") 95 | } 96 | 97 | let doCoolStuffAsync() = async { 98 | let value = 99 | Sql.host "localhost" 100 | |> Sql.connectFromConfig 101 | |> Sql.query "SELECT COUNT(*) as count FROM users" 102 | |> Sql.executeAsync (fun read -> read.int64 "count") 103 | 104 | return 1 105 | } 106 | 107 | let doCoolStuffAsyncWithLet() = async { 108 | let x = 109 | Sql.host "localhost" 110 | |> Sql.connectFromConfig 111 | |> Sql.query "SELECT COUNT(*) as count FROM users" 112 | |> Sql.executeAsync (fun read -> read.int64 "count") 113 | 114 | return x; 115 | } 116 | 117 | module InsidePostgres = 118 | 119 | let nextedDeclaretion() = 120 | Sql.host "localhost" 121 | |> Sql.connectFromConfig 122 | |> Sql.query "SELECT COUNT(*) as count FROM users" 123 | |> Sql.execute (fun read -> read.int64 "count") 124 | 125 | let nextedDeclaretionWithExplicitReturnType() : int64 list = 126 | Sql.host "localhost" 127 | |> Sql.connectFromConfig 128 | |> Sql.query "SELECT COUNT(*) as count FROM users" 129 | |> Sql.execute (fun read -> read.int64 "count") 130 | 131 | type NestedWhatever() = 132 | member this.Hello = 133 | let whateverSql() = 134 | Sql.host "localhost" 135 | |> Sql.connectFromConfig 136 | |> Sql.query "SELECT COUNT(*) as count FROM users" 137 | |> Sql.execute (fun read -> read.int64 "count") 138 | 139 | () 140 | -------------------------------------------------------------------------------- /tests/examples/hashing/syntacticAnalysisExecuteScalar.fs: -------------------------------------------------------------------------------- 1 | module syntacticAnalysisExecuteScalar 2 | 3 | open Npgsql.FSharp 4 | 5 | let connectionString = "Dummy connection string" 6 | 7 | let findUsernames() = 8 | connectionString 9 | |> Sql.connect 10 | |> Sql.query "SELECT COUNT(*) as Count FROM users WHERE is_active = @is_active" 11 | |> Sql.parameters [ "is_active", Sql.bit true ] 12 | |> Sql.execute (fun read -> read.int64 "count") 13 | -------------------------------------------------------------------------------- /tests/examples/hashing/syntacticAnalysisFromLambdaBody.fs: -------------------------------------------------------------------------------- 1 | module SyntacticAnalysisFromLambdaBody 2 | 3 | open Npgsql.FSharp 4 | 5 | let getData = 6 | fun (connectionString: string) -> 7 | connectionString 8 | |> Sql.connect 9 | |> Sql.query "SELECT * FROM users WHERE user_id = @user_id" 10 | |> Sql.parameters [ "@user_id", Sql.int 42 ] 11 | |> Sql.execute (fun read -> read.int "user_id") 12 | -------------------------------------------------------------------------------- /tests/examples/hashing/syntacticAnalysisFromSingleCaseUnion.fs: -------------------------------------------------------------------------------- 1 | module SyntacticAnalysisFromSingleCaseUnion 2 | 3 | open Npgsql.FSharp 4 | 5 | type SqlAction = SqlAction of (string -> int list) 6 | 7 | let getData = 8 | SqlAction (fun (connectionString: string) -> 9 | connectionString 10 | |> Sql.connect 11 | |> Sql.query "SELECT * FROM users WHERE user_id = @user_id" 12 | |> Sql.parameters [ "@user_id", Sql.int 42 ] 13 | |> Sql.execute (fun read -> read.int "user_id")) 14 | -------------------------------------------------------------------------------- /tests/examples/hashing/syntacticAnalysisProcessedList.fs: -------------------------------------------------------------------------------- 1 | module semanticAnalysisProcessedList 2 | 3 | open Npgsql.FSharp 4 | 5 | let connectionString = "Dummy connection string" 6 | 7 | let findUsernames() = 8 | connectionString 9 | |> Sql.connect 10 | |> Sql.query "SELECT * FROM users" 11 | |> Sql.execute (fun read -> read.text "username") 12 | |> Ok 13 | |> function 14 | | Error error -> None 15 | | Ok users -> Some users 16 | |> Option.map List.isEmpty 17 | -------------------------------------------------------------------------------- /tests/examples/hashing/syntacticAnalysisSimpleQuery.fs: -------------------------------------------------------------------------------- 1 | module syntacticAnalysisSimpleQuery 2 | 3 | open Npgsql.FSharp 4 | 5 | let connectionString = "Dummy connection string" 6 | 7 | let findUsernames() = 8 | connectionString 9 | |> Sql.connect 10 | |> Sql.query "SELECT COUNT(*) FROM users" 11 | -------------------------------------------------------------------------------- /tests/examples/hashing/syntaxAnalysis-detectingSkipAnalysis.fs: -------------------------------------------------------------------------------- 1 | module SyntaxAnalysisDetectingSkipAnalysis 2 | 3 | open Npgsql.FSharp 4 | 5 | let badQuery connection = 6 | connection 7 | |> Sql.query "SELECT * FROM non_existing_table" 8 | |> Sql.skipAnalysis 9 | |> Sql.execute (fun read -> read.int64 "user_id") 10 | -------------------------------------------------------------------------------- /tests/examples/hashing/syntaxAnalysis-usingTransactions.fs: -------------------------------------------------------------------------------- 1 | module SyntaxAnalysisUsingTransactions 2 | 3 | open Npgsql.FSharp 4 | 5 | let executeMultipleQueries = 6 | "connectionString" 7 | |> Sql.connect 8 | |> Sql.executeTransaction [ 9 | "UPDATE users SET user_id = @user_id", [ 10 | [ "@user_id", Sql.int 42 ] 11 | [ "@user_id", Sql.int 43 ] 12 | [ "@user_id", Sql.int 44 ] 13 | [ "@user_id", (Sql.int 44) ] 14 | ] 15 | 16 | "DELETE FROM users", [ ] 17 | ] 18 | 19 | let executeMultipleQueriesAsync = 20 | "connectionString" 21 | |> Sql.connect 22 | |> Sql.executeTransactionAsync [ 23 | "UPDATE users SET user_id = @user_id", [ ] 24 | "DELETE FROM users", [ ] 25 | ] 26 | -------------------------------------------------------------------------------- /tests/examples/hashing/syntaxAnalysisReferencingQueryDoesNotGiveError.fs: -------------------------------------------------------------------------------- 1 | module SyntaxAnalysisReferencingQueryDoesNotGiveError 2 | 3 | open Npgsql 4 | open Npgsql.FSharp 5 | 6 | // this shouldn't be analyzed when query is dynamic 7 | 8 | let deleteUsers connection = 9 | 10 | let usersQuery = "DELETE FROM users WHERE user_id = @user_id" 11 | 12 | connection 13 | |> Sql.query usersQuery 14 | |> Sql.parameters [ "@user_id", Sql.int 42 ] 15 | |> Sql.executeNonQuery 16 | -------------------------------------------------------------------------------- /tests/examples/hashing/topLevelExpressionIsDetected.fs: -------------------------------------------------------------------------------- 1 | module TopLevelExpressionIsDetected 2 | 3 | open Npgsql.FSharp 4 | 5 | "some connection string" 6 | |> Sql.connect 7 | |> Sql.query "SELECT * FROM users WHERE user_id = @user_id" 8 | |> Sql.parameters [ "@user_id", Sql.int 42 ] 9 | |> Sql.execute (fun read -> read.int "user_id") 10 | |> ignore 11 | -------------------------------------------------------------------------------- /tests/examples/hashing/updateQueryWithParametersInAssignments.fs: -------------------------------------------------------------------------------- 1 | module UpdateQueryWithParametersInAssignments 2 | 3 | open Npgsql.FSharp 4 | open System 5 | 6 | let [] updateUsernameQuery = """ 7 | UPDATE users 8 | SET username = @username, last_login = @last_login 9 | WHERE user_id = @user_id 10 | RETURNING * 11 | """ 12 | 13 | let usersAndTheirRoles connectionString = 14 | connectionString 15 | |> Sql.connect 16 | |> Sql.query updateUsernameQuery 17 | |> Sql.parameters [ 18 | "@user_id", Sql.intOrNone (Some 10) 19 | "@username", Sql.textOrNone (Some "John") 20 | "@last_login", Sql.timestamptz DateTime.Now 21 | ] 22 | |> Sql.execute (fun read -> read.int "user_id") 23 | -------------------------------------------------------------------------------- /tests/examples/hashing/usingIntArrayParameter.fs: -------------------------------------------------------------------------------- 1 | module UsingIntArrayParameter 2 | 3 | open Npgsql.FSharp 4 | 5 | let findRoles connection = 6 | connection 7 | |> Sql.query "SELECT * FROM users WHERE user_id = ANY(@user_ids)" 8 | |> Sql.parameters [ "user_ids", Sql.text "Hello" ] // should use Sql.intArray, Sql.intArrayOrNone or Sql.dbnull 9 | |> Sql.execute (fun read -> read.int "user_id") 10 | -------------------------------------------------------------------------------- /tests/examples/hashing/usingLiteralQueriesWithTransactions.fs: -------------------------------------------------------------------------------- 1 | module UsingLiteralQueriesWithTransactons 2 | 3 | open Npgsql.FSharp 4 | 5 | let [] DeleteUsersFirst = "DELETE FROM users WHERE user_id = @user_id" 6 | 7 | let [] DeleteUserSecond = "DELETE FROM users WHERE user_id = @user_id" 8 | 9 | let transaction connection values = 10 | connection 11 | |> Sql.connect 12 | |> Sql.executeTransaction [ 13 | DeleteUsersFirst, [ 14 | for value in values -> [ 15 | "@user_id", Sql.int value 16 | ] 17 | ] 18 | ] 19 | 20 | let transactionWithYield connection values = 21 | connection 22 | |> Sql.connect 23 | |> Sql.executeTransaction [ 24 | DeleteUserSecond, [ 25 | for value in values do yield [ 26 | "@user_id", Sql.int value 27 | ] 28 | ] 29 | ] 30 | -------------------------------------------------------------------------------- /tests/examples/hashing/usingProcessedParameters.fs: -------------------------------------------------------------------------------- 1 | module UsingProcessedParameters 2 | 3 | open Npgsql.FSharp 4 | 5 | let add x y = x + y 6 | 7 | let findRoles connection = 8 | connection 9 | |> Sql.query "SELECT * FROM users WHERE username = @username" 10 | |> Sql.parameters [ 11 | "@username", "Hello" |> Sql.text 12 | "@user_id", 5 |> add 10 |> Sql.int 13 | ] 14 | |> Sql.execute (fun read -> read.int "user_id") 15 | --------------------------------------------------------------------------------