diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 6564b1301e..3c55d0440f 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "fsharp-analyzers": { - "version": "0.21.0", + "version": "0.23.0", "commands": [ "fsharp-analyzers" ] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2a8078f5cb..2aed4b3424 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,10 @@ on: pull_request: branches: [ main ] +permissions: + id-token: write + security-events: write + jobs: # Separate job that verifies if all code was formatted correctly # Run `dotnet fantomas .` to format all code. @@ -200,3 +204,40 @@ jobs: - name: Fable Tests - Dart run: ./build.sh test dart + + # Separate job to run F# analyzers + analyzers: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + + - name: Restore tools + run: dotnet tool restore + + - name: Build solution + run: dotnet build -c Release Fable.sln + + - name: Run analyzers for Fable.AST + run: dotnet msbuild /t:AnalyzeFSharpProject src/Fable.AST/Fable.AST.fsproj + continue-on-error: true + + - name: Run analyzers for Rust.AST + run: dotnet msbuild /t:AnalyzeFSharpProject src/Fable.Transforms/Rust/AST/Rust.AST.fsproj + continue-on-error: true + + - name: Run analyzers Fable.Transforms + run: dotnet msbuild /t:AnalyzeFSharpProject src/Fable.Transforms/Fable.Transforms.fsproj + continue-on-error: true + + - name: Run analyzers Fable.Compiler + run: dotnet msbuild /t:AnalyzeFSharpProject src/Fable.Compiler/Fable.Compiler.fsproj + continue-on-error: true + + - name: Upload SARIF files + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: src/reports diff --git a/Fable.sln b/Fable.sln index b9aba20c23..5c41b5143a 100644 --- a/Fable.sln +++ b/Fable.sln @@ -64,6 +64,8 @@ Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fable.Build", "src\Fable.Bu EndProject Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fable.Compiler", "src\Fable.Compiler\Fable.Compiler.fsproj", "{942DD29B-07C0-4ACF-891E-85C1235A9BE0}" EndProject +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fable.Analyzers", "src\Fable.Analyzers\Fable.Analyzers.fsproj", "{75B5084B-C267-47EC-B4A1-7764A1F2A2FF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -166,6 +168,10 @@ Global {942DD29B-07C0-4ACF-891E-85C1235A9BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU {942DD29B-07C0-4ACF-891E-85C1235A9BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU {942DD29B-07C0-4ACF-891E-85C1235A9BE0}.Release|Any CPU.Build.0 = Release|Any CPU + {75B5084B-C267-47EC-B4A1-7764A1F2A2FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75B5084B-C267-47EC-B4A1-7764A1F2A2FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75B5084B-C267-47EC-B4A1-7764A1F2A2FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75B5084B-C267-47EC-B4A1-7764A1F2A2FF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -196,6 +202,7 @@ Global {C90E23AF-4B5B-44A7-ADCC-3BF89547395B} = {DA29278E-3808-42DE-8333-964F129F295D} {F2E323CE-FDF3-4A1E-AE97-B723D2E63763} = {C8CB96CF-68A8-4083-A0F8-319275CF8097} {942DD29B-07C0-4ACF-891E-85C1235A9BE0} = {C8CB96CF-68A8-4083-A0F8-319275CF8097} + {75B5084B-C267-47EC-B4A1-7764A1F2A2FF} = {C8CB96CF-68A8-4083-A0F8-319275CF8097} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {58DF9285-8523-4EAC-B598-BE5B02A76A00} diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 6eac0dec54..be0ce718f2 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -24,11 +24,15 @@ - + all build - + + all + analyzers + + all analyzers diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 1cc288a1b3..300447dcbd 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,11 +1,12 @@ + $([System.IO.Path]::GetDirectoryName($(DirectoryBuildTargetsPath))) --analyzers-path "$(PkgG-Research_FSharp_Analyzers)/analyzers/dotnet/fs" $(FSharpAnalyzersOtherFlags) --analyzers-path "$(PkgIonide_Analyzers)/analyzers/dotnet/fs" - $([System.IO.Path]::GetDirectoryName($(DirectoryBuildTargetsPath))) + $(FSharpAnalyzersOtherFlags) --analyzers-path "$(CodeRoot)/Fable.Analyzers/bin/Release/net6.0" $(CodeRoot)/reports/ $(FSharpAnalyzersOtherFlags) --code-root "$(CodeRoot)" $(FSharpAnalyzersOtherFlags) --report "$(SarifOutput)$(MSBuildProjectName)-$(TargetFramework).sarif" - $(FSharpAnalyzersOtherFlags) --exclude-analyzer PartialAppAnalyzer + $(FSharpAnalyzersOtherFlags) --exclude-analyzers PartialAppAnalyzer diff --git a/src/Fable.Analyzers/Fable.Analyzers.fsproj b/src/Fable.Analyzers/Fable.Analyzers.fsproj new file mode 100644 index 0000000000..08149e949b --- /dev/null +++ b/src/Fable.Analyzers/Fable.Analyzers.fsproj @@ -0,0 +1,17 @@ + + + + net6.0 + true + + + + + + + + + + + + diff --git a/src/Fable.Analyzers/StdOutAnalyzer.fs b/src/Fable.Analyzers/StdOutAnalyzer.fs new file mode 100644 index 0000000000..27172d2670 --- /dev/null +++ b/src/Fable.Analyzers/StdOutAnalyzer.fs @@ -0,0 +1,62 @@ +module Fable.Analyzers.StdOutAnalyzer + +open System +open FSharp.Analyzers.SDK.TASTCollecting +open FSharp.Compiler.Symbols +open FSharp.Compiler.Text +open FSharp.Analyzers.SDK + +let namesToAvoid = + set + [ + "System.Console.Write" + "System.Console.WriteLine" + "System.Console.WriteAsync" + "System.Console.WriteLineAsync" + "Microsoft.FSharp.Core.ExtraTopLevelOperators.printf" + "Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn" + ] + +[] +let stdOutAnalyzer: Analyzer = + fun (context: CliContext) -> + async { + let usages = ResizeArray() + + let walker = + { new TypedTreeCollectorBase() with + override _.WalkCall + _ + (m: FSharpMemberOrFunctionOrValue) + _ + _ + (args: FSharpExpr list) + (range: range) + = + match m.DeclaringEntity with + | None -> () + | Some de -> + let name = + String.Join(".", de.FullName, m.DisplayName) + + if Set.contains name namesToAvoid then + usages.Add(range, m.DisplayName) + } + + Option.iter (walkTast walker) context.TypedTree + + return + usages + |> Seq.map (fun (m, name) -> + { + Type = "StdOut analyzer" + Message = + $"Writing to the standard output from this location should absolutely be avoided. Replace `%s{name}` with an ILogger call." + Code = "FABLE_001" + Severity = Error + Range = m + Fixes = [] + } + ) + |> Seq.toList + } diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index 93fefbaad2..29322938ec 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -33,6 +33,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed 'System.Collections.Generic.Queue' bug (by @PierreYvesR) * Added support for generic comparers (by @ncave) +### Changed + +#### All + +* [GH-3671](https://github.com/fable-compiler/Fable/pull/3671) Use `Microsoft.Extensions.Logging` (by @nojaf) + ## 4.9.0 - 2023-12-14 ### Fixed diff --git a/src/Fable.Cli/Entry.fs b/src/Fable.Cli/Entry.fs index bbc46c958e..00738ddc16 100644 --- a/src/Fable.Cli/Entry.fs +++ b/src/Fable.Cli/Entry.fs @@ -4,6 +4,7 @@ open System open Main open Fable open Fable.Compiler.Util +open Microsoft.Extensions.Logging type CliArgs(args: string list) = let argsMap = @@ -249,6 +250,7 @@ type Runner = language: Language, rootDir: string, runProc: RunProcess option, + verbosity: Fable.Verbosity, ?fsprojPath: string, ?watch, ?precompile @@ -355,13 +357,6 @@ type Runner = else Ok() - let verbosity = - if args.FlagEnabled "--verbose" then - Log.makeVerbose () - Verbosity.Verbose - else - Verbosity.Normal - let configuration = let defaultConfiguration = if watch then @@ -445,6 +440,7 @@ type Runner = |> Map RunProcess = runProc CompilerOptions = compilerOptions + Verbosity = verbosity } let watchDelay = @@ -603,12 +599,29 @@ let main argv = | Some rootDir -> File.getExactFullPath rootDir | None -> IO.Directory.GetCurrentDirectory() - do + let verbosity = match commands with - | [ "--version" ] -> () + | [ "--version" ] -> Verbosity.Normal | _ -> - if args.FlagEnabled "--verbose" then - Log.makeVerbose () + let verbosity = + let level, verbosity = + if args.FlagEnabled "--verbose" then + LogLevel.Debug, Fable.Verbosity.Verbose + else + LogLevel.Information, Fable.Verbosity.Normal + + use factory = + LoggerFactory.Create(fun builder -> + builder + .SetMinimumLevel(level) + .AddSimpleConsole(fun options -> + options.SingleLine <- true + ) + |> ignore + ) + + Log.setLogger (factory.CreateLogger("")) + verbosity let status = match getStatus language with @@ -636,6 +649,8 @@ let main argv = + "\n" ) + verbosity + match commands with | [ "--help" ] -> return printHelp () | [ "--version" ] -> return Log.always Literals.VERSION @@ -648,11 +663,20 @@ let main argv = language, rootDir, runProc, + verbosity, fsprojPath = path, watch = true ) | [ "watch" ] -> - return! Runner.Run(args, language, rootDir, runProc, watch = true) + return! + Runner.Run( + args, + language, + rootDir, + runProc, + verbosity, + watch = true + ) | [ "precompile"; path ] -> return! Runner.Run( @@ -660,12 +684,20 @@ let main argv = language, rootDir, runProc, + verbosity, fsprojPath = path, precompile = true ) | [ "precompile" ] -> return! - Runner.Run(args, language, rootDir, runProc, precompile = true) + Runner.Run( + args, + language, + rootDir, + runProc, + verbosity, + precompile = true + ) | [ path ] -> return! Runner.Run( @@ -673,6 +705,7 @@ let main argv = language, rootDir, runProc, + verbosity, fsprojPath = path, watch = args.FlagEnabled("--watch") ) @@ -683,6 +716,7 @@ let main argv = language, rootDir, runProc, + verbosity, watch = args.FlagEnabled("--watch") ) | _ -> diff --git a/src/Fable.Cli/Fable.Cli.fsproj b/src/Fable.Cli/Fable.Cli.fsproj index efa9f0e4d8..82f6c25faa 100644 --- a/src/Fable.Cli/Fable.Cli.fsproj +++ b/src/Fable.Cli/Fable.Cli.fsproj @@ -52,6 +52,7 @@ + diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index 134b7ac7a9..58f583fcd7 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -43,36 +43,6 @@ module private Util = s :> _ - let loadType (cliArgs: CliArgs) (r: PluginRef) : Type = - /// Prevent ReflectionTypeLoadException - /// From http://stackoverflow.com/a/7889272 - let getTypes (asm: System.Reflection.Assembly) = - let mutable types: Option = None - - try - types <- Some(asm.GetTypes()) - with :? System.Reflection.ReflectionTypeLoadException as e -> - types <- Some e.Types - - match types with - | None -> Seq.empty - | Some types -> types |> Seq.filter ((<>) null) - - // The assembly may be already loaded, so use `LoadFrom` which takes - // the copy in memory unlike `LoadFile`, see: http://stackoverflow.com/a/1477899 - System.Reflection.Assembly.LoadFrom(r.DllPath) - |> getTypes - // Normalize type name - |> Seq.tryFind (fun t -> t.FullName.Replace("+", ".") = r.TypeFullName) - |> function - | Some t -> - $"Loaded %s{r.TypeFullName} from %s{IO.Path.GetRelativePath(cliArgs.RootDir, r.DllPath)}" - |> Log.always - - t - | None -> - failwith $"Cannot find %s{r.TypeFullName} in %s{r.DllPath}" - let splitVersion (version: string) = match Version.TryParse(version) with | true, v -> v.Major, v.Minor, v.Revision @@ -123,9 +93,7 @@ module private Util = let logErrors rootDir (logs: LogEntry seq) = logs |> Seq.filter (fun log -> log.Severity = Severity.Error) - |> Seq.iter (fun log -> - Fable.Compiler.Util.Log.error (formatLog rootDir log) - ) + |> Seq.iter (fun log -> Log.error (formatLog rootDir log)) let getFSharpDiagnostics (diagnostics: FSharpDiagnostic array) = diagnostics @@ -837,7 +805,51 @@ and FableCompiler if projCracked.CliArgs.NoParallelTypeCheck then Log.always msg else - Log.inSameLineIfNotCI msg + let isCi = + String.IsNullOrEmpty( + Environment.GetEnvironmentVariable( + "CI" + ) + ) + |> not + + match projCracked.CliArgs.Verbosity with + | Verbosity.Silent -> () + | Verbosity.Verbose -> + Console.Out.WriteLine(msg) + | Verbosity.Normal -> + // Avoid log pollution in CI. Also, if output is redirected don't try to rewrite + // the same line as it seems to cause problems, see #2727 + if + not isCi + && not Console.IsOutputRedirected + then + // If the message is longer than the terminal width it will jump to next line + let msg = + if msg.Length > 80 then + msg.[..80] + "..." + else + msg + + let curCursorLeft = Console.CursorLeft + + Console.SetCursorPosition( + 0, + Console.CursorTop + ) + + Console.Out.Write(msg) + let diff = curCursorLeft - msg.Length + + if diff > 0 then + Console.Out.Write( + String.replicate diff " " + ) + + Console.SetCursorPosition( + msg.Length, + Console.CursorTop + ) FableCompiler.CheckIfCompilationIsFinished(state) return! loop state @@ -899,7 +911,6 @@ and FableCompiler state.ReplyChannel with | true, true, Some channel -> - Log.inSameLineIfNotCI "" // Fable results are not guaranteed to be in order but revert them to make them closer to the original order let fableResults = state.FableResults |> List.rev Ok(state.FSharpLogs, fableResults) |> channel.Reply @@ -920,7 +931,7 @@ and FableCompiler ?precompiledInfo = (projCracked.PrecompiledInfo |> Option.map (fun i -> i :> _)), - getPlugin = loadType projCracked.CliArgs + getPlugin = Reflection.loadType projCracked.CliArgs ) return FableCompiler(checker, projCracked, fableProj) diff --git a/src/Fable.Compiler/CHANGELOG.md b/src/Fable.Compiler/CHANGELOG.md index 2a0d67eadd..093f8b3385 100644 --- a/src/Fable.Compiler/CHANGELOG.md +++ b/src/Fable.Compiler/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +* Respect file extension from `CliArgs` +* Use `Microsoft.Extensions.Logging` +* Load Fable plugins + ## 4.0.0-alpha-001 - 2023-12-14 * Initial release diff --git a/src/Fable.Compiler/Library.fs b/src/Fable.Compiler/Library.fs index 1ff8c3cca4..2b13be1260 100644 --- a/src/Fable.Compiler/Library.fs +++ b/src/Fable.Compiler/Library.fs @@ -16,12 +16,13 @@ type BabelWriter pathResolver: PathResolver, projectFile: string, sourcePath: string, - targetPath: string + targetPath: string, + ?fileExt: string ) = // In imports *.ts extensions have to be converted to *.js extensions instead // TODO: incomplete - let fileExt = ".js" + let fileExt = Option.defaultValue ".js" fileExt let sourceDir = Path.GetDirectoryName(sourcePath) let targetDir = Path.GetDirectoryName(targetPath) let memoryStream = new MemoryStream() @@ -64,15 +65,10 @@ type BabelWriter (Some targetDir) path + // Here, we deviate from what the regular compiler does as we are never writing to disk. + // We don't have the original file name collision problem. if path.EndsWith(".fs", StringComparison.Ordinal) then - let isInFableModules = - Path.Combine(targetDir, path) |> Naming.isInFableModules - - File.changeExtensionButUseDefaultExtensionInFableModules - JavaScript - isInFableModules - path - fileExt + Fable.Path.ChangeExtension(path, fileExt) else path @@ -107,6 +103,7 @@ let compileFileToJs (com: Compiler) (pathResolver: PathResolver) (outPath: string) + (fileExt: string) : Async = async { @@ -121,7 +118,8 @@ let compileFileToJs pathResolver, com.ProjectFile, com.CurrentFile, - outPath + outPath, + fileExt = fileExt ) do! BabelPrinter.run writer babel @@ -155,10 +153,10 @@ let compileProjectToJavaScript crackerResponse.ProjectOptions.SourceFiles, checkProjectResult.AssemblyContents.ImplementationFiles, assemblies, - Log.log + Log.log, + getPlugin = Reflection.loadType cliArgs // ?precompiledInfo = // (projCracked.PrecompiledInfo |> Option.map (fun i -> i :> _)), - // getPlugin = loadType projCracked.CliArgs ) let opts = cliArgs.CompilerOptions @@ -185,8 +183,19 @@ let compileProjectToJavaScript ?outDir = cliArgs.OutDir ) - let outputPath = Path.ChangeExtension(currentFile, ".js") - let! js = compileFileToJs compiler pathResolver outputPath + let outputPath = + Path.ChangeExtension( + currentFile, + cliArgs.CompilerOptions.FileExtension + ) + + let! js = + compileFileToJs + compiler + pathResolver + outputPath + cliArgs.CompilerOptions.FileExtension + return currentFile, js } ) @@ -235,7 +244,8 @@ let compileFileToJavaScript crackerResponse.ProjectOptions.SourceFiles, checkProjectResult.AssemblyContents.ImplementationFiles, assemblies, - Log.log + Log.log, + getPlugin = Reflection.loadType cliArgs ) let opts = cliArgs.CompilerOptions @@ -263,7 +273,14 @@ let compileFileToJavaScript ) let outputPath = Path.ChangeExtension(currentFile, ".js") - let! js = compileFileToJs compiler pathResolver outputPath + + let! js = + compileFileToJs + compiler + pathResolver + outputPath + cliArgs.CompilerOptions.FileExtension + return currentFile, js } ) diff --git a/src/Fable.Compiler/Util.fs b/src/Fable.Compiler/Util.fs index b47815b152..2d5dc0ce9a 100644 --- a/src/Fable.Compiler/Util.fs +++ b/src/Fable.Compiler/Util.fs @@ -31,6 +31,7 @@ type CliArgs = Replace: Map RunProcess: RunProcess option CompilerOptions: Fable.CompilerOptions + Verbosity: Fable.Verbosity } member this.ProjectFileAsRelativePath = @@ -82,82 +83,27 @@ type Agent<'T> [] module Log = - let newLine = Environment.NewLine - - let isCi = - String.IsNullOrEmpty(Environment.GetEnvironmentVariable("CI")) |> not + open Microsoft.Extensions.Logging + open Microsoft.Extensions.Logging.Abstractions + let mutable logger: ILogger = NullLogger.Instance + let setLogger newLogger = logger <- newLogger + let newLine = Environment.NewLine let mutable private verbosity = Fable.Verbosity.Normal - - /// To be called only at the beginning of the app - let makeVerbose () = verbosity <- Fable.Verbosity.Verbose - - let makeSilent () = verbosity <- Fable.Verbosity.Silent - let isVerbose () = verbosity = Fable.Verbosity.Verbose - - let canLog msg = - verbosity <> Fable.Verbosity.Silent && not (String.IsNullOrEmpty(msg)) - - let inSameLineIfNotCI (msg: string) = - match verbosity with - | Fable.Verbosity.Silent -> () - | Fable.Verbosity.Verbose -> Console.Out.WriteLine(msg) - | Fable.Verbosity.Normal -> - // Avoid log pollution in CI. Also, if output is redirected don't try to rewrite - // the same line as it seems to cause problems, see #2727 - if not isCi && not Console.IsOutputRedirected then - // If the message is longer than the terminal width it will jump to next line - let msg = - if msg.Length > 80 then - msg.[..80] + "..." - else - msg - - let curCursorLeft = Console.CursorLeft - Console.SetCursorPosition(0, Console.CursorTop) - Console.Out.Write(msg) - let diff = curCursorLeft - msg.Length - - if diff > 0 then - Console.Out.Write(String.replicate diff " ") - Console.SetCursorPosition(msg.Length, Console.CursorTop) - - let alwaysWithColor color (msg: string) = - if canLog msg then - Console.ForegroundColor <- color - Console.Out.WriteLine(msg) - Console.ResetColor() - - let always (msg: string) = - if canLog msg then - Console.Out.WriteLine(msg) + let always (msg: string) = logger.LogInformation msg let verbose (msg: Lazy) = if isVerbose () then always msg.Value - let verboseOrIf condition (msg: string) = - if canLog msg && (condition || verbosity = Fable.Verbosity.Verbose) then - always msg + let warning (msg: string) = logger.LogWarning msg - let warning (msg: string) = - if canLog msg then - Console.ForegroundColor <- ConsoleColor.DarkYellow - Console.Out.WriteLine(msg) - Console.ResetColor() + let error (msg: string) = logger.LogError msg - let error (msg: string) = - if canLog msg then - Console.ForegroundColor <- ConsoleColor.DarkRed - Console.Error.WriteLine(msg) - Console.ResetColor() + let mutable private femtoMsgShown = false - let info (msg: string) = - if canLog msg then - Console.ForegroundColor <- ConsoleColor.Gray - Console.Out.WriteLine(msg) - Console.ResetColor() + let info (msg: string) = logger.LogInformation msg let log (sev: Fable.Severity) (msg: string) = match sev with @@ -165,15 +111,13 @@ module Log = | Fable.Severity.Warning -> warning msg | Fable.Severity.Error -> error msg - let mutable private femtoMsgShown = false - let showFemtoMsg (show: unit -> bool) : unit = if not femtoMsgShown && verbosity <> Fable.Verbosity.Silent then if show () then femtoMsgShown <- true "Some Nuget packages contain information about NPM dependencies that can be managed by Femto: https://github.com/Zaid-Ajaj/Femto" - |> alwaysWithColor ConsoleColor.Blue + |> logger.LogInformation module File = open System.IO @@ -1134,3 +1078,38 @@ type PrecompiledInfoImpl(fableModulesDir: string, info: PrecompiledInfoJson) = InlineExprHeaders = inlineExprHeaders } |> Json.write precompiledInfoPath + +module Reflection = + let loadType + (cliArgs: CliArgs) + (r: Fable.Transforms.State.PluginRef) + : Type + = + /// Prevent ReflectionTypeLoadException + /// From http://stackoverflow.com/a/7889272 + let getTypes (asm: System.Reflection.Assembly) = + let mutable types: Option = None + + try + types <- Some(asm.GetTypes()) + with :? System.Reflection.ReflectionTypeLoadException as e -> + types <- Some e.Types + + match types with + | None -> Seq.empty + | Some types -> types |> Seq.filter ((<>) null) + + // The assembly may be already loaded, so use `LoadFrom` which takes + // the copy in memory unlike `LoadFile`, see: http://stackoverflow.com/a/1477899 + System.Reflection.Assembly.LoadFrom(r.DllPath) + |> getTypes + // Normalize type name + |> Seq.tryFind (fun t -> t.FullName.Replace("+", ".") = r.TypeFullName) + |> function + | Some t -> + $"Loaded %s{r.TypeFullName} from %s{IO.Path.GetRelativePath(cliArgs.RootDir, r.DllPath)}" + |> Log.always + + t + | None -> + failwith $"Cannot find %s{r.TypeFullName} in %s{r.DllPath}" diff --git a/src/Fable.Compiler/Util.fsi b/src/Fable.Compiler/Util.fsi index af64528359..b4f8b09fd8 100644 --- a/src/Fable.Compiler/Util.fsi +++ b/src/Fable.Compiler/Util.fsi @@ -32,6 +32,7 @@ type CliArgs = Replace: Map RunProcess: RunProcess option CompilerOptions: Fable.CompilerOptions + Verbosity: Fable.Verbosity } member ProjectFileAsRelativePath: string @@ -39,11 +40,11 @@ type CliArgs = [] module Log = - val newLine: string + open Microsoft.Extensions.Logging + /// To be called only at the beginning of the app - val makeVerbose: unit -> unit - val makeSilent: unit -> unit - val inSameLineIfNotCI: msg: string -> unit + val setLogger: ILogger -> unit + val newLine: string val always: msg: string -> unit val verbose: msg: Lazy -> unit val warning: msg: string -> unit @@ -202,3 +203,7 @@ type PrecompiledInfoImpl = fableModulesDir: string * fableLibDir: string -> unit + +module Reflection = + val loadType: + cliArgs: CliArgs -> r: Fable.Transforms.State.PluginRef -> Type diff --git a/src/quicktest/Quicktest.fsproj b/src/quicktest/QuickTest.fsproj similarity index 100% rename from src/quicktest/Quicktest.fsproj rename to src/quicktest/QuickTest.fsproj diff --git a/tests/Integration/Compiler/Util/Compiler.fs b/tests/Integration/Compiler/Util/Compiler.fs index 4bc21bf1c9..919b855bed 100644 --- a/tests/Integration/Compiler/Util/Compiler.fs +++ b/tests/Integration/Compiler/Util/Compiler.fs @@ -32,7 +32,6 @@ module Compiler = let sourceFile = IO.Path.Join(projDir, "Program.fs" ) |> Path.normalizeFullPath let cliArgs = - Log.makeSilent() let compilerOptions = CompilerOptionsHelper.Make() { CliArgs.ProjectFile = projFile FableLibraryPath = None @@ -51,7 +50,8 @@ module Compiler = Exclude = ["Fable.Core"] Replace = Map.empty RunProcess = None - CompilerOptions = compilerOptions } + CompilerOptions = compilerOptions + Verbosity = Verbosity.Normal } let mutable private state = State.Create(cliArgs, recompileAllFiles=true) diff --git a/tests/Js/Adaptive/Fable.Tests.Adaptive.fsproj b/tests/Js/Adaptive/Fable.Tests.Adaptive.fsproj index e74eb1fd8b..404bedc899 100644 --- a/tests/Js/Adaptive/Fable.Tests.Adaptive.fsproj +++ b/tests/Js/Adaptive/Fable.Tests.Adaptive.fsproj @@ -1,6 +1,7 @@  - netstandard2.0 + Exe + net8.0