Skip to content

Commit

Permalink
Npgsql.FSharp.Analyzer v3.18 detect queries within lambda expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
Zaid-Ajaj committed Dec 6, 2020
1 parent 8d5412f commit d105d7d
Show file tree
Hide file tree
Showing 17 changed files with 312 additions and 49 deletions.
15 changes: 15 additions & 0 deletions NpgsqlFSharpAnalyzer.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FParsec", "src\FParsec\FPar
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParserTestsWithNet48", "src\ParserTestsWithNet48\ParserTestsWithNet48.csproj", "{40067E09-6281-4DC2-905D-60F7C6E3B812}"
EndProject
Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Ubik", "src\Ubik\Ubik.fsproj", "{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -159,6 +161,18 @@ Global
{40067E09-6281-4DC2-905D-60F7C6E3B812}.Release|x64.Build.0 = Release|Any CPU
{40067E09-6281-4DC2-905D-60F7C6E3B812}.Release|x86.ActiveCfg = Release|Any CPU
{40067E09-6281-4DC2-905D-60F7C6E3B812}.Release|x86.Build.0 = Release|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Debug|x64.ActiveCfg = Debug|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Debug|x64.Build.0 = Debug|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Debug|x86.ActiveCfg = Debug|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Debug|x86.Build.0 = Debug|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Release|Any CPU.Build.0 = Release|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Release|x64.ActiveCfg = Release|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Release|x64.Build.0 = Release|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Release|x86.ActiveCfg = Release|Any CPU
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -174,6 +188,7 @@ Global
{C5EB813F-4278-4EE7-925B-6757BAD0FE9B} = {C397A34C-84F1-49E7-AEBC-2F9F2B196216}
{9C8E7641-9DC8-470C-8009-71A747C01DC5} = {C397A34C-84F1-49E7-AEBC-2F9F2B196216}
{40067E09-6281-4DC2-905D-60F7C6E3B812} = {ACBEE43C-7A88-4FB1-9B06-DB064D22B29F}
{0CCC7E3F-78F0-4A4F-8AC1-27A1F6386B1E} = {C397A34C-84F1-49E7-AEBC-2F9F2B196216}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BC821061-2FB3-4ABD-9FA1-044D4C59C475}
Expand Down
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### 3.18.0 - 2020-09-15
* Analyze SQL blocks from within lambda expressions

### 3.17.0 - 2020-09-15
* Support for datetimeOffset and datetimeOffsetOrNone when reading columns of type timestamptz

Expand Down
16 changes: 8 additions & 8 deletions src/FParsec/AssemblyInfo.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ open System.Reflection

[<assembly: AssemblyTitleAttribute("FParsec")>]
[<assembly: AssemblyProductAttribute("NpgsqlFSharpAnalyzer")>]
[<assembly: AssemblyVersionAttribute("3.17.0")>]
[<assembly: AssemblyVersionAttribute("3.18.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseDate","2020-09-15T00:00:00.0000000")>]
[<assembly: AssemblyFileVersionAttribute("3.17.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.17.0")>]
[<assembly: AssemblyFileVersionAttribute("3.18.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.18.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseChannel","release")>]
[<assembly: AssemblyMetadataAttribute("GitHash","985f38c696391f5c8f38f4498e3a59db66ffe3c6")>]
[<assembly: AssemblyMetadataAttribute("GitHash","8d5412fe3dd28abc45fb45a7d97134d09ec1ce82")>]
do ()

module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "FParsec"
let [<Literal>] AssemblyProduct = "NpgsqlFSharpAnalyzer"
let [<Literal>] AssemblyVersion = "3.17.0"
let [<Literal>] AssemblyVersion = "3.18.0"
let [<Literal>] AssemblyMetadata_ReleaseDate = "2020-09-15T00:00:00.0000000"
let [<Literal>] AssemblyFileVersion = "3.17.0"
let [<Literal>] AssemblyInformationalVersion = "3.17.0"
let [<Literal>] AssemblyFileVersion = "3.18.0"
let [<Literal>] AssemblyInformationalVersion = "3.18.0"
let [<Literal>] AssemblyMetadata_ReleaseChannel = "release"
let [<Literal>] AssemblyMetadata_GitHash = "985f38c696391f5c8f38f4498e3a59db66ffe3c6"
let [<Literal>] AssemblyMetadata_GitHash = "8d5412fe3dd28abc45fb45a7d97134d09ec1ce82"
16 changes: 8 additions & 8 deletions src/NpgsqlFSharpAnalyzer.Core/AssemblyInfo.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ open System.Reflection

[<assembly: AssemblyTitleAttribute("NpgsqlFSharpAnalyzer.Core")>]
[<assembly: AssemblyProductAttribute("NpgsqlFSharpAnalyzer")>]
[<assembly: AssemblyVersionAttribute("3.17.0")>]
[<assembly: AssemblyVersionAttribute("3.18.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseDate","2020-09-15T00:00:00.0000000")>]
[<assembly: AssemblyFileVersionAttribute("3.17.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.17.0")>]
[<assembly: AssemblyFileVersionAttribute("3.18.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.18.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseChannel","release")>]
[<assembly: AssemblyMetadataAttribute("GitHash","985f38c696391f5c8f38f4498e3a59db66ffe3c6")>]
[<assembly: AssemblyMetadataAttribute("GitHash","8d5412fe3dd28abc45fb45a7d97134d09ec1ce82")>]
do ()

module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "NpgsqlFSharpAnalyzer.Core"
let [<Literal>] AssemblyProduct = "NpgsqlFSharpAnalyzer"
let [<Literal>] AssemblyVersion = "3.17.0"
let [<Literal>] AssemblyVersion = "3.18.0"
let [<Literal>] AssemblyMetadata_ReleaseDate = "2020-09-15T00:00:00.0000000"
let [<Literal>] AssemblyFileVersion = "3.17.0"
let [<Literal>] AssemblyInformationalVersion = "3.17.0"
let [<Literal>] AssemblyFileVersion = "3.18.0"
let [<Literal>] AssemblyInformationalVersion = "3.18.0"
let [<Literal>] AssemblyMetadata_ReleaseChannel = "release"
let [<Literal>] AssemblyMetadata_GitHash = "985f38c696391f5c8f38f4498e3a59db66ffe3c6"
let [<Literal>] AssemblyMetadata_GitHash = "8d5412fe3dd28abc45fb45a7d97134d09ec1ce82"
3 changes: 3 additions & 0 deletions src/NpgsqlFSharpAnalyzer.Core/SyntacticAnalysis.fs
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,9 @@ module SyntacticAnalysis =
| Some expr -> yield! visitSyntacticExpression expr range
]

| SynExpr.Lambda (fromMethod, inSeq, args, body, range) ->
visitSyntacticExpression body range

| otherwise ->
[ ]

Expand Down
16 changes: 8 additions & 8 deletions src/NpgsqlFSharpAnalyzer/AssemblyInfo.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ open System.Reflection

[<assembly: AssemblyTitleAttribute("NpgsqlFSharpAnalyzer")>]
[<assembly: AssemblyProductAttribute("NpgsqlFSharpAnalyzer")>]
[<assembly: AssemblyVersionAttribute("3.17.0")>]
[<assembly: AssemblyVersionAttribute("3.18.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseDate","2020-09-15T00:00:00.0000000")>]
[<assembly: AssemblyFileVersionAttribute("3.17.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.17.0")>]
[<assembly: AssemblyFileVersionAttribute("3.18.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.18.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseChannel","release")>]
[<assembly: AssemblyMetadataAttribute("GitHash","985f38c696391f5c8f38f4498e3a59db66ffe3c6")>]
[<assembly: AssemblyMetadataAttribute("GitHash","8d5412fe3dd28abc45fb45a7d97134d09ec1ce82")>]
do ()

module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "NpgsqlFSharpAnalyzer"
let [<Literal>] AssemblyProduct = "NpgsqlFSharpAnalyzer"
let [<Literal>] AssemblyVersion = "3.17.0"
let [<Literal>] AssemblyVersion = "3.18.0"
let [<Literal>] AssemblyMetadata_ReleaseDate = "2020-09-15T00:00:00.0000000"
let [<Literal>] AssemblyFileVersion = "3.17.0"
let [<Literal>] AssemblyInformationalVersion = "3.17.0"
let [<Literal>] AssemblyFileVersion = "3.18.0"
let [<Literal>] AssemblyInformationalVersion = "3.18.0"
let [<Literal>] AssemblyMetadata_ReleaseChannel = "release"
let [<Literal>] AssemblyMetadata_GitHash = "985f38c696391f5c8f38f4498e3a59db66ffe3c6"
let [<Literal>] AssemblyMetadata_GitHash = "8d5412fe3dd28abc45fb45a7d97134d09ec1ce82"
16 changes: 8 additions & 8 deletions src/NpgsqlFSharpParser/AssemblyInfo.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ open System.Reflection

[<assembly: AssemblyTitleAttribute("NpgsqlFSharpParser")>]
[<assembly: AssemblyProductAttribute("NpgsqlFSharpAnalyzer")>]
[<assembly: AssemblyVersionAttribute("3.17.0")>]
[<assembly: AssemblyVersionAttribute("3.18.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseDate","2020-09-15T00:00:00.0000000")>]
[<assembly: AssemblyFileVersionAttribute("3.17.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.17.0")>]
[<assembly: AssemblyFileVersionAttribute("3.18.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.18.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseChannel","release")>]
[<assembly: AssemblyMetadataAttribute("GitHash","985f38c696391f5c8f38f4498e3a59db66ffe3c6")>]
[<assembly: AssemblyMetadataAttribute("GitHash","8d5412fe3dd28abc45fb45a7d97134d09ec1ce82")>]
do ()

module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "NpgsqlFSharpParser"
let [<Literal>] AssemblyProduct = "NpgsqlFSharpAnalyzer"
let [<Literal>] AssemblyVersion = "3.17.0"
let [<Literal>] AssemblyVersion = "3.18.0"
let [<Literal>] AssemblyMetadata_ReleaseDate = "2020-09-15T00:00:00.0000000"
let [<Literal>] AssemblyFileVersion = "3.17.0"
let [<Literal>] AssemblyInformationalVersion = "3.17.0"
let [<Literal>] AssemblyFileVersion = "3.18.0"
let [<Literal>] AssemblyInformationalVersion = "3.18.0"
let [<Literal>] AssemblyMetadata_ReleaseChannel = "release"
let [<Literal>] AssemblyMetadata_GitHash = "985f38c696391f5c8f38f4498e3a59db66ffe3c6"
let [<Literal>] AssemblyMetadata_GitHash = "8d5412fe3dd28abc45fb45a7d97134d09ec1ce82"
2 changes: 1 addition & 1 deletion src/NpgsqlFSharpVs/source.extension.vsixmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011"
xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
<Metadata>
<Identity Id="FSharpLintVs.ef00bfc3-a899-45fc-aae8-afecf8673aaf" Version="3.17.0" Language="en-US" Publisher="Zaid Ajaj" />
<Identity Id="FSharpLintVs.ef00bfc3-a899-45fc-aae8-afecf8673aaf" Version="3.18.0" Language="en-US" Publisher="Zaid Ajaj" />
<DisplayName>NpgsqlFSharpVs</DisplayName>
<Description xml:space="preserve">F# Analyzer for embedded SQL syntax analysis, type-checking for parameters and result sets and nullable column detection when writing queries using Npgsql.FSharp.</Description>
<MoreInfo>https://github.com/Zaid-Ajaj/Npgsql.FSharp.Analyzer</MoreInfo>
Expand Down
23 changes: 23 additions & 0 deletions src/Ubik/AssemblyInfo.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Auto-Generated by FAKE; do not edit
namespace System
open System.Reflection

[<assembly: AssemblyTitleAttribute("Ubik")>]
[<assembly: AssemblyProductAttribute("NpgsqlFSharpAnalyzer")>]
[<assembly: AssemblyVersionAttribute("3.18.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseDate","2020-09-15T00:00:00.0000000")>]
[<assembly: AssemblyFileVersionAttribute("3.18.0")>]
[<assembly: AssemblyInformationalVersionAttribute("3.18.0")>]
[<assembly: AssemblyMetadataAttribute("ReleaseChannel","release")>]
[<assembly: AssemblyMetadataAttribute("GitHash","8d5412fe3dd28abc45fb45a7d97134d09ec1ce82")>]
do ()

module internal AssemblyVersionInformation =
let [<Literal>] AssemblyTitle = "Ubik"
let [<Literal>] AssemblyProduct = "NpgsqlFSharpAnalyzer"
let [<Literal>] AssemblyVersion = "3.18.0"
let [<Literal>] AssemblyMetadata_ReleaseDate = "2020-09-15T00:00:00.0000000"
let [<Literal>] AssemblyFileVersion = "3.18.0"
let [<Literal>] AssemblyInformationalVersion = "3.18.0"
let [<Literal>] AssemblyMetadata_ReleaseChannel = "release"
let [<Literal>] AssemblyMetadata_GitHash = "8d5412fe3dd28abc45fb45a7d97134d09ec1ce82"
79 changes: 79 additions & 0 deletions src/Ubik/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
open System
open System.IO
open Npgsql.FSharp.Analyzers.Core
open Spectre.Console
open System.Xml

let resolveFile (path: string) =
if Path.IsPathRooted path
then path
else Path.GetFullPath (Path.Combine(Environment.CurrentDirectory, path))

let getProject (args: string []) =
try
match args with
| [| |] ->
Directory.GetFiles(Environment.CurrentDirectory, "*.fsproj")
|> Array.tryHead
|> Option.map (fun projectPath -> resolveFile projectPath)

| multipleArgs ->
let firstArg = multipleArgs.[0]
if firstArg.EndsWith(".fsproj") then
Some (resolveFile firstArg)
else
Directory.GetFiles(resolveFile firstArg, "*.fsproj")
|> Array.tryHead
|> Option.map (fun projectPath -> resolveFile projectPath)
with
| error -> None

[<EntryPoint>]
let main argv =
match getProject argv with
| None ->
printfn "No project file found in the current directory"
1

| Some project ->
AnsiConsole.MarkupLine("Analyzing [blue]{0}[/]", project)

let document = XmlDocument()
document.LoadXml(File.ReadAllText project)

let fsharpFileNodes = document.GetElementsByTagName("Compile")
let fsharpFiles = [
for item in 0 .. fsharpFileNodes.Count - 1 ->
let relativePath = fsharpFileNodes.[item].Attributes.["Include"].InnerText
let projectParent = Directory.GetParent project
Path.Combine(projectParent.FullName, relativePath)
]

for file in fsharpFiles do
AnsiConsole.MarkupLine("Analyzing file [green]{0}[/]", file)
match Project.context file with
| None -> ()
| Some context ->
let syntacticBlocks = SyntacticAnalysis.findSqlOperations context
if not syntacticBlocks.IsEmpty then
let messages =
let connectionString = SqlAnalyzer.tryFindConnectionString context.FileName
if isNull connectionString || String.IsNullOrWhiteSpace connectionString then
[ ]
else
match SqlAnalysis.databaseSchema connectionString with
| Result.Error connectionError ->
[
for block in syntacticBlocks ->
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
]

| Result.Ok schema ->
syntacticBlocks
|> List.collect (fun block -> SqlAnalysis.analyzeOperation block connectionString schema)
|> List.distinctBy (fun message -> message.Range)

for message in messages do
AnsiConsole.MarkupLine("Error [red]{0}[/]", message.Message)

0
97 changes: 97 additions & 0 deletions src/Ubik/Project.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
module Project

open System
open System.IO
open FSharp.Compiler.SourceCodeServices
open FSharp.Compiler.Text
open Npgsql.FSharp.Analyzers.Core

let checker =
FSharpChecker.Create(
keepAllBackgroundResolutions = true,
keepAssemblyContents = true,
ImplicitlyStartBackgroundWork = true)

let dumpOpts (opts : FSharpProjectOptions) =
printfn "FSharpProjectOptions.OtherOptions ->"
opts.OtherOptions
|> Array.iter(printfn "%s")

let loadProject file =
async {
let! source = IO.File.ReadAllTextAsync file |> Async.AwaitTask
let! (opts, error) = checker.GetProjectOptionsFromScript(file, SourceText.ofString source, assumeDotNetFramework = false, useSdkRefs = true, useFsiAuxLib = true, otherFlags = [|"--targetprofile:netstandard" |])
let newOO =
opts.OtherOptions
|> Array.map(fun i ->
if i.StartsWith("-r:") then
let path = i.Split("-r:", StringSplitOptions.RemoveEmptyEntries).[0]

sprintf "-r:%s" (IO.FileInfo(path).FullName)
else
i
)
// dumpOpts opts
return file, opts
} |> Async.RunSynchronously

let typeCheckFile (file,opts) =
let text = File.ReadAllText file
let st = SourceText.ofString text
let (parseRes, checkAnswer) =
checker.ParseAndCheckFileInProject(file, 1, st, opts)
|> Async.RunSynchronously

match checkAnswer with
| FSharpCheckFileAnswer.Aborted ->
printfn "Checking of file %s aborted because %A" file parseRes.Errors
None
| FSharpCheckFileAnswer.Succeeded(c) ->
Some (file, text, parseRes, c)

let entityCache = EntityCache()

let getAllEntities (checkResults: FSharpCheckFileResults) (publicOnly: bool) : AssemblySymbol list =
try
let res = [
yield! AssemblyContentProvider.getAssemblySignatureContent AssemblyContentType.Full checkResults.PartialAssemblySignature
let ctx = checkResults.ProjectContext
let assembliesByFileName =
ctx.GetReferencedAssemblies()
|> Seq.groupBy (fun asm -> asm.FileName)
|> Seq.map (fun (fileName, asms) -> fileName, List.ofSeq asms)
|> Seq.toList
|> List.rev // if mscorlib.dll is the first then FSC raises exception when we try to
// get Content.Entities from it.

for fileName, signatures in assembliesByFileName do
let contentType = if publicOnly then Public else Full
let content = AssemblyContentProvider.getAssemblyContent entityCache.Locking contentType fileName signatures
yield! content
]
res
with
| _ -> []

let createContext (file, text: string, p: FSharpParseFileResults,c: FSharpCheckFileResults) =
match p.ParseTree with
| Some parseTree ->
let context : SqlAnalyzerContext = {
FileName = file
Content = text.Split([|'\n'|])
ParseTree = parseTree
Symbols = c.PartialAssemblySignature.Entities |> Seq.toList
}

Some context
| _ ->
None

let context proj =
let path =
Path.Combine(Environment.CurrentDirectory, proj)
|> Path.GetFullPath

loadProject path
|> typeCheckFile
|> Option.bind createContext
Loading

0 comments on commit d105d7d

Please sign in to comment.