diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index d5c110124c..6564b1301e 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -13,6 +13,12 @@ "commands": [ "husky" ] + }, + "fsharp-analyzers": { + "version": "0.21.0", + "commands": [ + "fsharp-analyzers" + ] } } } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9f8dbf071e..285dabb62d 100644 --- a/.gitignore +++ b/.gitignore @@ -218,3 +218,6 @@ __pycache__/ # Compilation tests tests/**/*.actual + +# Analyzers +*.sarif diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 33364963ee..6eac0dec54 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -24,5 +24,13 @@ + + all + build + + + all + analyzers + diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 0000000000..1cc288a1b3 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,11 @@ + + + --analyzers-path "$(PkgG-Research_FSharp_Analyzers)/analyzers/dotnet/fs" + $(FSharpAnalyzersOtherFlags) --analyzers-path "$(PkgIonide_Analyzers)/analyzers/dotnet/fs" + $([System.IO.Path]::GetDirectoryName($(DirectoryBuildTargetsPath))) + $(CodeRoot)/reports/ + $(FSharpAnalyzersOtherFlags) --code-root "$(CodeRoot)" + $(FSharpAnalyzersOtherFlags) --report "$(SarifOutput)$(MSBuildProjectName)-$(TargetFramework).sarif" + $(FSharpAnalyzersOtherFlags) --exclude-analyzer PartialAppAnalyzer + + diff --git a/src/Fable.AST/Common.fs b/src/Fable.AST/Common.fs index e5f271f8fe..e9730edcf6 100644 --- a/src/Fable.AST/Common.fs +++ b/src/Fable.AST/Common.fs @@ -1,5 +1,7 @@ namespace Fable.AST +open System + /// Each Position object consists of a line number (1-indexed) and a column number (0-indexed): type Position = { @@ -24,7 +26,7 @@ type SourceLocation = member this.DisplayName = this.identifierName |> Option.bind (fun name -> - match name.IndexOf(";file:") with + match name.IndexOf(";file:", StringComparison.Ordinal) with | -1 -> Some name | 0 -> None | i -> name.Substring(0, i) |> Some @@ -33,7 +35,7 @@ type SourceLocation = member this.File = this.identifierName |> Option.bind (fun name -> - match name.IndexOf(";file:") with + match name.IndexOf(";file:", StringComparison.Ordinal) with | -1 -> None | i -> name.Substring(i + ";file:".Length) |> Some ) diff --git a/src/Fable.Cli/CHANGELOG.md b/src/Fable.Cli/CHANGELOG.md index a829bd8c4c..9ef3dc893f 100644 --- a/src/Fable.Cli/CHANGELOG.md +++ b/src/Fable.Cli/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +#### All + +* Overall performance improvements + * [PR 3620](https://github.com/fable-compiler/Fable/pull/3620) Removed double-dictionary lookups (by @Thorium) + * [PR 3624](https://github.com/fable-compiler/Fable/pull/3624) Add G-Research analyzers and fix reported issues (by @nojaf) + ### Fixed #### Python diff --git a/src/Fable.Cli/Entry.fs b/src/Fable.Cli/Entry.fs index 93f113a6f8..d9394f949a 100644 --- a/src/Fable.Cli/Entry.fs +++ b/src/Fable.Cli/Entry.fs @@ -9,17 +9,17 @@ type CliArgs(args: string list) = let args = // Assume last arg has true value in case it's a flag match List.tryLast args with - | Some key when key.StartsWith("-") -> args @ [ "true" ] + | Some key when key.StartsWith('-') -> args @ [ "true" ] | _ -> args (Map.empty, List.windowed 2 args) ||> List.fold (fun map pair -> match pair with - | [ key; value ] when key.StartsWith("-") -> + | [ key; value ] when key.StartsWith('-') -> let key = key.ToLower() let value = - if value.StartsWith("-") then + if value.StartsWith('-') then "true" else value @@ -279,11 +279,15 @@ type Runner = |> Seq.toList files - |> List.filter (fun file -> file.EndsWith(".fsproj")) + |> List.filter (fun file -> + file.EndsWith(".fsproj", StringComparison.Ordinal) + ) |> function | [] -> files - |> List.filter (fun file -> file.EndsWith(".fsx")) + |> List.filter (fun file -> + file.EndsWith(".fsx", StringComparison.Ordinal) + ) | candidates -> candidates |> function | [] -> @@ -388,7 +392,7 @@ type Runner = let fileExt = args.Value("-e", "--extension") |> Option.map (fun e -> - if e.StartsWith(".") then + if e.StartsWith('.') then e else "." + e @@ -537,7 +541,7 @@ let clean (args: CliArgs) language rootDir = "No files have been deleted. If Fable output is in another directory, pass it as argument." ) else - Log.always ("Clean completed! Files deleted: " + string fileCount) + Log.always ("Clean completed! Files deleted: " + string fileCount) let getStatus = function @@ -563,7 +567,9 @@ let main argv = let! argv, runProc = argv |> List.ofArray - |> List.splitWhile (fun a -> not (a.StartsWith("--run"))) + |> List.splitWhile (fun a -> + not (a.StartsWith("--run", StringComparison.Ordinal)) + ) |> function | argv, flag :: runArgs -> match flag, runArgs with @@ -584,7 +590,7 @@ let main argv = | ("help" | "--help" | "-h") :: _ -> [ "--help" ], [] | "--version" :: _ -> [ "--version" ], [] | argv -> - argv |> List.splitWhile (fun x -> x.StartsWith("-") |> not) + argv |> List.splitWhile (fun x -> x.StartsWith('-') |> not) let! args = parseCliArgs args let! language = argLanguage args diff --git a/src/Fable.Cli/Globbing.fs b/src/Fable.Cli/Globbing.fs index c881613f4f..12ba2152d6 100644 --- a/src/Fable.Cli/Globbing.fs +++ b/src/Fable.Cli/Globbing.fs @@ -1,5 +1,6 @@ namespace Fable.Cli +open System open System.Collections.Generic open System.IO @@ -141,7 +142,7 @@ module Globbing = let globRoot = // If we dropped "/" from the beginning of the path in the 'Split' call, put it back! - if normPattern.StartsWith("/") then + if normPattern.StartsWith('/') then "/" + globRoot else globRoot @@ -163,9 +164,9 @@ module Globbing = // names (as one folder name could be a substring of the other) let start = baseDir.TrimEnd([| Path.DirectorySeparatorChar |]) - + string Path.DirectorySeparatorChar + + string Path.DirectorySeparatorChar // See https://github.com/fsharp/FAKE/issues/1925 - if input.StartsWith start then + if input.StartsWith(start, StringComparison.Ordinal) then input.Substring start.Length else input @@ -183,7 +184,10 @@ module Globbing = let baseItems = let start, rest = - if input.StartsWith "\\\\" && splits.Length >= 4 then + if + input.StartsWith("\\\\", StringComparison.Ordinal) + && splits.Length >= 4 + then let serverName = splits.[2] let share = splits.[3] @@ -198,12 +202,12 @@ module Globbing = elif splits.Length >= 2 && Path.IsPathRooted input - && input.StartsWith "/" + && input.StartsWith '/' then [ Directory("/") ], splits |> Array.toSeq else if Path.IsPathRooted input then - if input.StartsWith "\\" then + if input.StartsWith '\\' then failwithf "Please remove the leading '\\' or '/' and replace them with \ '.\\' or './' if you want to use a relative path. Leading \ @@ -415,13 +419,13 @@ module Globbing = let included = this.Includes - |> Seq.exists (fun fileInclude -> + |> List.exists (fun fileInclude -> Glob.isMatch (fullDir fileInclude) fullPath ) let excluded = this.Excludes - |> Seq.exists (fun fileExclude -> + |> List.exists (fun fileExclude -> Glob.isMatch (fullDir fileExclude) fullPath ) @@ -468,7 +472,10 @@ module Globbing = |> Seq.filter (fun d -> directoryIncludes |> Seq.exists (fun p -> - d.StartsWith(p + string Path.DirectorySeparatorChar) + d.StartsWith( + p + string Path.DirectorySeparatorChar, + StringComparison.Ordinal + ) && p <> d ) |> not diff --git a/src/Fable.Cli/Main.fs b/src/Fable.Cli/Main.fs index 2281094915..20c7ba90ba 100644 --- a/src/Fable.Cli/Main.fs +++ b/src/Fable.Cli/Main.fs @@ -31,7 +31,8 @@ module private Util = } let isImplementationFile (fileName: string) = - fileName.EndsWith(".fs") || fileName.EndsWith(".fsx") + fileName.EndsWith(".fs", StringComparison.Ordinal) + || fileName.EndsWith(".fsx", StringComparison.Ordinal) let caseInsensitiveSet (items: string seq) : ISet = let s = HashSet(items) @@ -312,7 +313,7 @@ module FileWatcherUtil = // See https://github.com/fable-compiler/Fable/pull/2725#issuecomment-1015123642 |> List.filter (fun file -> not ( - file.EndsWith(".fsproj.fsx") + file.EndsWith(".fsproj.fsx", StringComparison.Ordinal) // It looks like latest F# compiler puts generated files for resolution of packages // in scripts in $HOME/.packagemanagement. See #3222 || file.Contains(".packagemanagement") @@ -334,7 +335,8 @@ module FileWatcherUtil = if restDirs |> List.forall (fun d -> - (withTrailingSep d).StartsWith dir' + (withTrailingSep d) + .StartsWith(dir', StringComparison.Ordinal) ) then dir @@ -800,8 +802,14 @@ and FableCompiler let filesToCompile = state.FilesToCompile |> Array.filter (fun file -> - file.EndsWith(".fs") - || file.EndsWith(".fsx") + file.EndsWith( + ".fs", + StringComparison.Ordinal + ) + || file.EndsWith( + ".fsx", + StringComparison.Ordinal + ) ) (state, filesToCompile) @@ -1084,7 +1092,8 @@ let private areCompiledFilesUpToDate (state: State) (filesToCompile: string[]) = let upToDate = filesToCompile |> Array.filter (fun file -> - file.EndsWith(".fs") || file.EndsWith(".fsx") + file.EndsWith(".fs", StringComparison.Ordinal) + || file.EndsWith(".fsx", StringComparison.Ordinal) ) |> Array.forall (fun source -> let outPath = getOutPath state.CliArgs pathResolver source @@ -1208,7 +1217,10 @@ let private compilationCycle (state: State) (changes: ISet) = | Some(projCracked, fableCompiler) -> // For performance reasons, don't crack .fsx scripts for every change let fsprojChanged = - changes |> Seq.exists (fun c -> c.EndsWith(".fsproj")) + changes + |> Seq.exists (fun c -> + c.EndsWith(".fsproj", StringComparison.Ordinal) + ) if fsprojChanged then let oldProjCracked = projCracked diff --git a/src/Fable.Cli/Pipeline.fs b/src/Fable.Cli/Pipeline.fs index d428c37433..bf2e7355da 100644 --- a/src/Fable.Cli/Pipeline.fs +++ b/src/Fable.Cli/Pipeline.fs @@ -102,7 +102,7 @@ module Js = let fileExt = let fileExt = cliArgs.CompilerOptions.FileExtension - if fileExt.EndsWith(".ts") then + if fileExt.EndsWith(".ts", StringComparison.Ordinal) then Path.ChangeExtension(fileExt, ".js") else fileExt @@ -172,7 +172,7 @@ module Js = cliArgs.OutDir path - if path.EndsWith(".fs") then + if path.EndsWith(".fs", StringComparison.Ordinal) then let isInFableModules = Path.Combine(targetDir, path) |> Naming.isInFableModules @@ -488,7 +488,7 @@ module Php = cliArgs.OutDir path - if path.EndsWith(".fs") then + if path.EndsWith(".fs", StringComparison.Ordinal) then Path.ChangeExtension(path, fileExt) else path @@ -546,7 +546,7 @@ module Dart = cliArgs.OutDir path - if path.EndsWith(".fs") then + if path.EndsWith(".fs", StringComparison.Ordinal) then Path.ChangeExtension(path, fileExt) else path @@ -607,7 +607,7 @@ module Rust = cliArgs.OutDir path - if path.EndsWith(".fs") then + if path.EndsWith(".fs", StringComparison.Ordinal) then Path.ChangeExtension(path, fileExt) else path diff --git a/src/Fable.Cli/ProjectCracker.fs b/src/Fable.Cli/ProjectCracker.fs index b5d72072a2..637998b72b 100644 --- a/src/Fable.Cli/ProjectCracker.fs +++ b/src/Fable.Cli/ProjectCracker.fs @@ -185,9 +185,9 @@ type CrackerResponse = } let isSystemPackage (pkgName: string) = - pkgName.StartsWith("System.") - || pkgName.StartsWith("Microsoft.") - || pkgName.StartsWith("runtime.") + pkgName.StartsWith("System.", StringComparison.Ordinal) + || pkgName.StartsWith("Microsoft.", StringComparison.Ordinal) + || pkgName.StartsWith("runtime.", StringComparison.Ordinal) || pkgName = "NETStandard.Library" || pkgName = "FSharp.Core" || pkgName = "Fable.Core" @@ -295,7 +295,9 @@ let tryGetFablePackage (opts: CrackerOptions) (dllPath: string) = let fsprojPath = match Map.tryFind pkgId opts.Replace with | Some replaced -> - if replaced.EndsWith(".fsproj") then + if + replaced.EndsWith(".fsproj", StringComparison.Ordinal) + then replaced else tryFileWithPattern replaced "*.fsproj" @@ -468,18 +470,24 @@ let private extractUsefulOptionsAndSources (line: string) (accSources: string list, accOptions: string list) = - if line.StartsWith("-") then + if line.StartsWith('-') then // "--warnaserror" // Disable for now to prevent unexpected errors, see #2288 - if line.StartsWith("--langversion:") && isMainProj then + if + line.StartsWith("--langversion:", StringComparison.Ordinal) + && isMainProj + then let v = line.Substring("--langversion:".Length).ToLowerInvariant() if v = "preview" then accSources, line :: accOptions else accSources, accOptions - elif line.StartsWith("--nowarn") || line.StartsWith("--warnon") then + elif + line.StartsWith("--nowarn", StringComparison.Ordinal) + || line.StartsWith("--warnon", StringComparison.Ordinal) + then accSources, line :: accOptions - elif line.StartsWith("--define:") then + elif line.StartsWith("--define:", StringComparison.Ordinal) then // When parsing the project as .csproj there will be multiple defines in the same line, // but the F# compiler seems to accept only one per line let defines = @@ -533,7 +541,7 @@ let getCrackedMainFsproj let sourceFiles, otherOpts = (projOpts, ([], [])) ||> Array.foldBack (fun line (src, otherOpts) -> - if line.StartsWith("-r:") then + if line.StartsWith("-r:", StringComparison.Ordinal) then let line = Path.normalizePath (line[3..]) let dllName = getDllName line dllRefs.Add(dllName, line) @@ -663,7 +671,12 @@ let getProjectOptionsFromProjectFile = r.CompilerArguments |> Array.map (fun line -> if reg.IsMatch(line) then - if line.StartsWith("/reference") then + if + line.StartsWith( + "/reference", + StringComparison.Ordinal + ) + then "-r" + line.Substring(10) else "--" + line.Substring(1) @@ -708,9 +721,12 @@ let getProjectOptionsFromProjectFile = let projOpts = compilerArgs - |> Array.skipWhile (fun line -> not (line.StartsWith("-"))) + |> Array.skipWhile (fun line -> not (line.StartsWith('-'))) |> Array.map (fun f -> - if f.EndsWith(".fs") || f.EndsWith(".fsi") then + if + f.EndsWith(".fs", StringComparison.Ordinal) + || f.EndsWith(".fsi", StringComparison.Ordinal) + then if Path.IsPathRooted f then f else @@ -810,7 +826,7 @@ let retryGetCrackedProjects opts = // Replace the .fsproj extension with .fableproj for files in fable_modules // We do this to avoid conflicts with other F# tooling that scan for .fsproj files let changeFsprojToFableproj (path: string) = - if path.EndsWith(".fsproj") then + if path.EndsWith(".fsproj", StringComparison.Ordinal) then IO.Path.ChangeExtension(path, Naming.fableProjExt) else path @@ -857,7 +873,7 @@ let getFableLibraryPath (opts: CrackerOptions) = | Python, Some Py.Naming.sitePackages -> "fable-library-py", "fable-library" | _, Some path -> - if path.StartsWith("./") then + if path.StartsWith("./", StringComparison.Ordinal) then "", Path.normalizeFullPath path elif IO.Path.IsPathRooted(path) then "", Path.normalizePath path @@ -968,7 +984,7 @@ let loadPrecompiledInfo (opts: CrackerOptions) otherOptions sourceFiles = // (e.g. fable_modules/Fable.Promise.2.1.0/Promise.fs) so we assume they're the same wherever they are // TODO: Check if this holds true also for Python which may not include the version number in the path let normalizePath (path: string) = - let i = path.IndexOf(Naming.fableModules) + let i = path.IndexOf(Naming.fableModules, StringComparison.Ordinal) if i >= 0 then path[i..] @@ -1237,7 +1253,7 @@ let getFullProjectOpts (opts: CrackerOptions) = if ignoredRefs.Contains(name) - || (name.StartsWith("System.") + || (name.StartsWith("System.", StringComparison.Ordinal) && not (coreRefs.Contains(name))) then None diff --git a/src/Fable.Cli/Util.fs b/src/Fable.Cli/Util.fs index f60c3634c9..8ef93d9b6a 100644 --- a/src/Fable.Cli/Util.fs +++ b/src/Fable.Cli/Util.fs @@ -624,7 +624,8 @@ module Imports = path.Replace("../", "").Replace("./", "").Replace(":", "") let isRelativePath (path: string) = - path.StartsWith("./") || path.StartsWith("../") + path.StartsWith("./", StringComparison.Ordinal) + || path.StartsWith("../", StringComparison.Ordinal) let isAbsolutePath (path: string) = path.StartsWith('/') || path.IndexOf(':') = 1 @@ -735,7 +736,7 @@ module Imports = importPath if isAbsolutePath importPath then - if importPath.EndsWith(".fs") then + if importPath.EndsWith(".fs", StringComparison.Ordinal) then getTargetRelativePath pathResolver importPath diff --git a/src/Fable.PublishUtils/PublishUtils.fs b/src/Fable.PublishUtils/PublishUtils.fs index 721e93cadd..ff3d83fd8f 100644 --- a/src/Fable.PublishUtils/PublishUtils.fs +++ b/src/Fable.PublishUtils/PublishUtils.fs @@ -403,7 +403,7 @@ module Publish = let VERSION_HEADER = "#+ " + VERSION let splitPrerelease (version: string) = - let i = version.IndexOf("-") + let i = version.IndexOf('-') if i > 0 then version.Substring(0, i), Some(version.Substring(i + 1)) @@ -519,8 +519,8 @@ module Publish = let private findFileWithExt (dir: string) (ext: string) = IO.Directory.GetFiles(dir) - |> Seq.tryPick (fun path -> - if path.EndsWith(ext) then + |> Array.tryPick (fun path -> + if path.EndsWith(ext, StringComparison.Ordinal) then Some(dir path) else None @@ -723,7 +723,7 @@ let getDotNetSDKVersionFromGlobalJson () : string = let getNpmVersion (projDir: string) = let pkgJsonPath = - if projDir.EndsWith("package.json") then + if projDir.EndsWith("package.json", StringComparison.Ordinal) then projDir else projDir "package.json" diff --git a/src/Fable.Transforms/BabelPrinter.fs b/src/Fable.Transforms/BabelPrinter.fs index 1e0ac59924..ab3f900d26 100644 --- a/src/Fable.Transforms/BabelPrinter.fs +++ b/src/Fable.Transforms/BabelPrinter.fs @@ -1027,7 +1027,7 @@ module PrinterExtensions = let i = int m.Groups[1].Value for j = i to args.Length - 1 do - rep.Add("$" + string j) + rep.Add("$" + string j) String.concat ", " rep ) diff --git a/src/Fable.Transforms/Dart/DartPrinter.fs b/src/Fable.Transforms/Dart/DartPrinter.fs index 41cf7b74dc..6c7b0a9499 100644 --- a/src/Fable.Transforms/Dart/DartPrinter.fs +++ b/src/Fable.Transforms/Dart/DartPrinter.fs @@ -105,7 +105,7 @@ module PrinterExtensions = let i = int m.Groups[1].Value for j = i to args.Length - 1 do - rep.Add("$" + string j) + rep.Add("$" + string j) String.concat ", " rep ) diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index 97a89e7c5e..66f3bfe4a8 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -2,6 +2,7 @@ module Fable.Transforms.Dart.Replacements #nowarn "1182" +open System open System.Text.RegularExpressions open Fable open Fable.AST @@ -96,7 +97,7 @@ let toChar (arg: Expr) = let charToString = function - | Value(CharConstant v, r) -> Value(StringConstant(string v), r) + | Value(CharConstant v, r) -> Value(StringConstant(string v), r) | e -> Helper.GlobalCall("String", String, [ e ], memb = "fromCharCode") let toString com (ctx: Context) r (args: Expr list) = @@ -953,7 +954,7 @@ let tryReplacedEntityRef (com: Compiler) entFullName = let entFullName = entFullName[entFullName.LastIndexOf(".") + 1 ..] let entFullName = - match entFullName.IndexOf("`") with + match entFullName.IndexOf("`", StringComparison.Ordinal) with | -1 -> entFullName | i -> entFullName[0 .. i - 1] @@ -2370,7 +2371,10 @@ let formattableString |} ) - printJsTaggedTemplate str holes (fun i -> "$" + string (i + offset)) + printJsTaggedTemplate + str + holes + (fun i -> "$" + string (i + offset)) emitExpr r t args (callMacro + jsTaggedTemplate) |> Some | "get_Format", Some x, _ -> @@ -2695,7 +2699,12 @@ let tuples match i.CompiledName, thisArg with | (".ctor" | "Create"), _ -> - let isStruct = i.DeclaringEntityFullName.StartsWith("System.ValueTuple") + let isStruct = + i.DeclaringEntityFullName.StartsWith( + "System.ValueTuple", + StringComparison.Ordinal + ) + Value(NewTuple(args, isStruct), r) |> Some | "get_Item1", Some x -> Get(x, TupleIndex 0, t, r) |> Some | "get_Item2", Some x -> Get(x, TupleIndex 1, t, r) |> Some @@ -3636,7 +3645,7 @@ let bigints ?loc = r ) |> Some - | None, meth when meth.StartsWith("get_") -> + | None, meth when meth.StartsWith("get_", StringComparison.Ordinal) -> Helper.LibValue(com, "BigInt", meth, t) |> Some | callee, meth -> let args = @@ -5682,7 +5691,7 @@ let guids = let parseGuid (literalGuid: string) = try - System.Guid.Parse(literalGuid) |> string |> makeStrConst + System.Guid.Parse(literalGuid) |> string |> makeStrConst with e -> e.Message |> addErrorAndReturnNull com ctx.InlinePath r |> Some @@ -6439,7 +6448,9 @@ let tryCall | "Microsoft.FSharp.Reflection.FSharpReflectionExtensions" -> // In netcore F# Reflection methods become extensions // with names like `FSharpType.GetExceptionFields.Static` - let isFSharpType = info.CompiledName.StartsWith("FSharpType") + let isFSharpType = + info.CompiledName.StartsWith("FSharpType", StringComparison.Ordinal) + let methName = info.CompiledName |> Naming.extensionMethodName if isFSharpType then @@ -6505,7 +6516,8 @@ let tryBaseConstructor | Types.exception_ -> Some(makeImportLib com Any "Exception" "Types", args) | Types.attribute -> Some(makeImportLib com Any "Attribute" "Types", args) | fullName when - fullName.StartsWith("Fable.Core.") && fullName.EndsWith("Attribute") + fullName.StartsWith("Fable.Core.", StringComparison.Ordinal) + && fullName.EndsWith("Attribute", StringComparison.Ordinal) -> Some(makeImportLib com Any "Attribute" "Types", args) | Types.dictionary -> diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index 0793837c5f..4260e12af7 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -111,7 +111,7 @@ type FsField(fi: FSharpField) = | Some ent -> match countConflictingCases 0 ent name with | 0 -> name - | n -> name + "_" + (string n) + | n -> name + "_" + (string n) [] type CompiledValue = @@ -126,7 +126,7 @@ type FsUnionCase(uci: FSharpUnionCase) = uci.Attributes |> Helpers.tryFindAttrib Atts.compiledName |> Option.map (fun (att: FSharpAttribute) -> - att.ConstructorArguments[0] |> snd |> string + att.ConstructorArguments[0] |> snd |> string ) static member FullName(uci: FSharpUnionCase) = @@ -353,7 +353,7 @@ type FsEnt(maybeAbbrevEnt: FSharpEntity) = if ent.IsArrayType then let rank = match ent.ArrayRank with - | rank when rank > 1 -> "`" + string rank + | rank when rank > 1 -> "`" + string rank | _ -> "" Some("System.Array" + rank) @@ -648,7 +648,7 @@ module Helpers = (Fable.SourcePath sourcePath | Fable.PrecompiledLib(sourcePath, _)) -> let rootMod = com.GetRootModule(sourcePath) - if fullName.StartsWith(rootMod) then + if fullName.StartsWith(rootMod, StringComparison.Ordinal) then fullName.Substring(rootMod.Length).TrimStart('.') else fullName @@ -946,7 +946,9 @@ module Helpers = let parentHasSignatureFile () = v.DeclaringEntity |> Option.bind (fun p -> p.SignatureLocation) - |> Option.map (fun m -> m.FileName.EndsWith(".fsi")) + |> Option.map (fun m -> + m.FileName.EndsWith(".fsi", StringComparison.Ordinal) + ) |> Option.defaultValue false v.IsModuleValueOrMember @@ -1468,7 +1470,7 @@ module Patterns = Some(memb, None, "toString", membArgTypes, membArgs) // work-around for optimized hash operator (Operators.hash) | Call(Some expr, memb, _, [], [ Call(None, comp, [], [], []) ]) when - memb.FullName.EndsWith(".GetHashCode") + memb.FullName.EndsWith(".GetHashCode", StringComparison.Ordinal) && comp.FullName = "Microsoft.FSharp.Core.LanguagePrimitives.GenericEqualityERComparer" -> Some(memb, Some comp, "GenericHash", [ expr.Type ], [ expr ]) @@ -1478,7 +1480,7 @@ module Patterns = _, [], [ Coerce(t2, e2); Call(None, comp, [], [], []) ]) when - memb.FullName.EndsWith(".Equals") + memb.FullName.EndsWith(".Equals", StringComparison.Ordinal) && t2.HasTypeDefinition && t2.TypeDefinition.CompiledName = "obj" && comp.FullName = "Microsoft.FSharp.Core.LanguagePrimitives.GenericEqualityComparer" @@ -2367,7 +2369,13 @@ module Util = |> Some match attributes with - | _ when entRef.FullName.StartsWith("Fable.Core.JS.") -> globalRef None + | _ when + entRef.FullName.StartsWith( + "Fable.Core.JS.", + StringComparison.Ordinal + ) + -> + globalRef None | GlobalAtt customName -> globalRef customName | ImportAtt(selector, path) -> let selector = @@ -2464,15 +2472,23 @@ module Util = let private isReplacementCandidatePrivate isFromDll (entFullName: string) = if - entFullName.StartsWith("System.") - || entFullName.StartsWith("Microsoft.FSharp.") + entFullName.StartsWith("System.", StringComparison.Ordinal) + || entFullName.StartsWith( + "Microsoft.FSharp.", + StringComparison.Ordinal + ) then isFromDll () // When compiling Fable itself, Fable.Core entities will be part of the code base, but still need to be replaced else - entFullName.StartsWith("Fable.Core.") - && (not (entFullName.StartsWith("Fable.Core.JS.")) - || entFullName.EndsWith("Attribute")) + entFullName.StartsWith("Fable.Core.", StringComparison.Ordinal) + && (not ( + entFullName.StartsWith( + "Fable.Core.JS.", + StringComparison.Ordinal + ) + ) + || entFullName.EndsWith("Attribute", StringComparison.Ordinal)) let isReplacementCandidate (ent: Fable.EntityRef) = let isFromDll () = isFromDllNotPrecompiled ent @@ -2691,7 +2707,9 @@ module Util = match ent.TryFullName with // By default mangle interfaces in System namespace as they are not meant to interact with JS // except those that are used in fable-library Typescript files - | Some fullName when fullName.StartsWith("System.") -> + | Some fullName when + fullName.StartsWith("System.", StringComparison.Ordinal) + -> match fullName with | Types.object | Types.idisposable @@ -2825,7 +2843,12 @@ module Util = (thisArg: Fable.Expr option) = let msg = - if info.DeclaringEntityFullName.StartsWith("Fable.Core.") then + if + info.DeclaringEntityFullName.StartsWith( + "Fable.Core.", + StringComparison.Ordinal + ) + then $"{info.DeclaringEntityFullName}.{info.CompiledName} is not supported, try updating fable tool" else com.WarnOnlyOnce( diff --git a/src/Fable.Transforms/FSharp2Fable.fs b/src/Fable.Transforms/FSharp2Fable.fs index a0810cd49f..6f9faa9b13 100644 --- a/src/Fable.Transforms/FSharp2Fable.fs +++ b/src/Fable.Transforms/FSharp2Fable.fs @@ -1,5 +1,6 @@ module rec Fable.Transforms.FSharp2Fable.Compiler +open System open System.Collections.Generic open FSharp.Compiler.Symbols @@ -278,7 +279,7 @@ let private transformTraitCall ) sourceTypes - |> Seq.tryPick (fun t -> + |> List.tryPick (fun t -> let typeOpt = Replacements.Api.tryType com t match typeOpt with @@ -299,7 +300,7 @@ let private transformTraitCall let fieldName = Naming.removeGetSetPrefix traitName entity.FSharpFields - |> Seq.tryPick (fun fi -> + |> List.tryPick (fun fi -> if fi.Name = fieldName then let kind = Fable.FieldInfo.Create( @@ -442,12 +443,12 @@ let private getImplementedSignatureInfo ) |> Option.defaultWith (fun () -> let isGetter = - sign.Name.StartsWith("get_") + sign.Name.StartsWith("get_", StringComparison.Ordinal) && countNonCurriedParamsForSignature sign = 0 let isSetter = not isGetter - && sign.Name.StartsWith("set_") + && sign.Name.StartsWith("set_", StringComparison.Ordinal) && countNonCurriedParamsForSignature sign = 1 let name = diff --git a/src/Fable.Transforms/Fable2Babel.fs b/src/Fable.Transforms/Fable2Babel.fs index 66030a48ce..6a3cdb8495 100644 --- a/src/Fable.Transforms/Fable2Babel.fs +++ b/src/Fable.Transforms/Fable2Babel.fs @@ -1,5 +1,6 @@ module rec Fable.Transforms.Fable2Babel +open System open Fable open Fable.AST open Fable.AST.Babel @@ -1386,7 +1387,7 @@ module Util = match line.Trim() with | "" -> true | Naming.Regex IMPORT_REGEX (_ :: selector :: path :: _) -> - if selector.StartsWith("{") then + if selector.StartsWith("{", StringComparison.Ordinal) then for selector in selector.TrimStart('{').TrimEnd('}').Split(',') do com.GetImportExpr( @@ -1401,7 +1402,9 @@ module Util = true else let selector = - if selector.StartsWith("*") then + if + selector.StartsWith("*", StringComparison.Ordinal) + then selector else $"default as {selector}" @@ -1555,7 +1558,7 @@ module Util = = match memberName with | "ToString" -> Expression.identifier ("toString"), false - | n when n.StartsWith("Symbol.") -> + | n when n.StartsWith("Symbol.", StringComparison.Ordinal) -> Expression.memberExpression ( Expression.identifier ("Symbol"), Expression.identifier (n[7..]), @@ -2185,7 +2188,8 @@ module Util = Expression.nullLiteral (?loc = r) | Fable.UnitConstant -> undefined r None | Fable.BoolConstant x -> Expression.booleanLiteral (x, ?loc = r) - | Fable.CharConstant x -> Expression.stringLiteral (string x, ?loc = r) + | Fable.CharConstant x -> + Expression.stringLiteral (string x, ?loc = r) | Fable.StringConstant x -> Expression.stringLiteral (x, ?loc = r) | Fable.StringTemplate(tag, parts, values) -> let tag = tag |> Option.map (fun e -> com.TransformAsExpr(ctx, e)) @@ -2200,17 +2204,17 @@ module Util = JS.Replacements.makeDecimal com r value.Type x |> transformAsExpr com ctx | BigInt, (:? bigint as x) -> - Expression.bigintLiteral (string x, ?loc = r) + Expression.bigintLiteral (string x, ?loc = r) | Int64, (:? int64 as x) -> - Expression.bigintLiteral (string x, ?loc = r) + Expression.bigintLiteral (string x, ?loc = r) | UInt64, (:? uint64 as x) -> - Expression.bigintLiteral (string x, ?loc = r) + Expression.bigintLiteral (string x, ?loc = r) // | Int128, (:? System.Int128 as x) -> Expression.bigintLiteral(string x, ?loc=r) // | UInt128, (:? System.UInt128 as x) -> Expression.bigintLiteral(string x, ?loc=r) | NativeInt, (:? nativeint as x) -> - Expression.bigintLiteral (string x, ?loc = r) + Expression.bigintLiteral (string x, ?loc = r) | UNativeInt, (:? unativeint as x) -> - Expression.bigintLiteral (string x, ?loc = r) + Expression.bigintLiteral (string x, ?loc = r) | Int8, (:? int8 as x) -> Expression.numericLiteral (float x, ?loc = r) | UInt8, (:? uint8 as x) -> @@ -4556,7 +4560,7 @@ module Util = let genArgs = Array.init (ent.GenericParameters.Length) - (fun i -> "gen" + string i |> makeIdent) + (fun i -> "gen" + string i |> makeIdent) let generics = genArgs |> Array.map identAsExpr let body = transformReflectionInfo com ctx None ent generics diff --git a/src/Fable.Transforms/Global/Compiler.fs b/src/Fable.Transforms/Global/Compiler.fs index 1ae73a0cc0..f8b161cd71 100644 --- a/src/Fable.Transforms/Global/Compiler.fs +++ b/src/Fable.Transforms/Global/Compiler.fs @@ -1,5 +1,7 @@ namespace Fable +open System + module Literals = [] let VERSION = "4.5.0" @@ -222,7 +224,12 @@ module CompilerExt = file let relPath = - if relPath.StartsWith("./") then + if + relPath.StartsWith( + "./", + StringComparison.Ordinal + ) + then relPath[2..] else relPath diff --git a/src/Fable.Transforms/Global/Naming.fs b/src/Fable.Transforms/Global/Naming.fs index e3a724eda2..94dcf04969 100644 --- a/src/Fable.Transforms/Global/Naming.fs +++ b/src/Fable.Transforms/Global/Naming.fs @@ -9,13 +9,13 @@ module Naming = open System.Text.RegularExpressions let (|StartsWith|_|) (pattern: string) (txt: string) = - if txt.StartsWith(pattern) then + if txt.StartsWith(pattern, StringComparison.Ordinal) then txt.Substring(pattern.Length) |> Some else None let (|EndsWith|_|) (pattern: string) (txt: string) = - if txt.EndsWith(pattern) then + if txt.EndsWith(pattern, StringComparison.Ordinal) then txt.Substring(0, txt.Length - pattern.Length) |> Some else None @@ -91,7 +91,7 @@ module Naming = let c = ident.[i] if isIdentChar i c then - string c + string c else replace c ) @@ -109,30 +109,33 @@ module Naming = Regex.Replace(input, pattern, value) let replacePrefix (prefix: string) (value: string) (input: string) = - if input.StartsWith(prefix) then + if input.StartsWith(prefix, StringComparison.Ordinal) then value + (input.Substring(prefix.Length)) else input let replaceSuffix (suffix: string) (value: string) (input: string) = - if input.EndsWith(suffix) then + if input.EndsWith(suffix, StringComparison.Ordinal) then (input.Substring(0, input.Length - suffix.Length)) + value else input let removeGetSetPrefix (s: string) = - if s.StartsWith("get_") || s.StartsWith("set_") then + if + s.StartsWith("get_", StringComparison.Ordinal) + || s.StartsWith("set_", StringComparison.Ordinal) + then s.Substring(4) else s let extensionMethodName (s: string) = - let i1 = s.IndexOf(".") + let i1 = s.IndexOf(".", StringComparison.Ordinal) if i1 < 0 then s else - let i2 = s.IndexOf(".", i1 + 1) + let i2 = s.IndexOf(".", i1 + 1, StringComparison.Ordinal) if i2 < 0 then s @@ -358,7 +361,7 @@ module Naming = let rec check originalName n = let name = if n > 0 then - originalName + "_" + (string n) + originalName + "_" + (string n) else originalName diff --git a/src/Fable.Transforms/Global/Prelude.fs b/src/Fable.Transforms/Global/Prelude.fs index e25455260e..6520697026 100644 --- a/src/Fable.Transforms/Global/Prelude.fs +++ b/src/Fable.Transforms/Global/Prelude.fs @@ -9,7 +9,10 @@ module Extensions = type String with member str.StartsWithAny([] patterns: string[]) = - patterns |> Array.exists (fun p -> str.StartsWith(p)) + patterns + |> Array.exists (fun p -> + str.StartsWith(p, StringComparison.Ordinal) + ) module Dictionary = open System.Collections.Generic @@ -359,7 +362,7 @@ module Path = /// If path belongs to a signature file (.fsi), replace the extension with .fs let ensureFsExtension (path: string) = - if path.EndsWith(".fsi") then + if path.EndsWith(".fsi", StringComparison.Ordinal) then path.Substring(0, path.Length - 1) else path diff --git a/src/Fable.Transforms/OverloadSuffix.fs b/src/Fable.Transforms/OverloadSuffix.fs index d9a4f04368..bf3f084114 100644 --- a/src/Fable.Transforms/OverloadSuffix.fs +++ b/src/Fable.Transforms/OverloadSuffix.fs @@ -184,7 +184,7 @@ let getHash // and implementation files, use the position instead let genParams = entityGenericParams - |> List.mapi (fun i p -> p, string i) + |> List.mapi (fun i p -> p, string i) |> dict getHashPrivate paramTypes genParams diff --git a/src/Fable.Transforms/Php/Fable2Php.fs b/src/Fable.Transforms/Php/Fable2Php.fs index a6ffc03366..25ff48dfe3 100644 --- a/src/Fable.Transforms/Php/Fable2Php.fs +++ b/src/Fable.Transforms/Php/Fable2Php.fs @@ -1,5 +1,6 @@ module Fable.Transforms.Fable2Php +open System open FSharp.Compiler.Symbols open Fable open Fable.AST @@ -1552,7 +1553,9 @@ let rec convertExpr (com: IPhpCompiler) (expr: Fable.Expr) = PhpGlobal(name) | _ -> com.AddRequire(info.Path) - let sepPos = info.Selector.IndexOf("__") + + let sepPos = + info.Selector.IndexOf("__", StringComparison.Ordinal) if sepPos >= 0 then PhpIdent( @@ -1829,7 +1832,7 @@ and convertValue (com: IPhpCompiler) (value: Fable.ValueKind) range = | Fable.StringConstant(s) -> PhpConst(PhpConstString s) | Fable.BoolConstant(b) -> PhpConst(PhpConstBool b) | Fable.UnitConstant -> PhpConst(PhpConstNull) - | Fable.CharConstant(c) -> PhpConst(PhpConstString(string c)) + | Fable.CharConstant(c) -> PhpConst(PhpConstString(string c)) | Fable.Null _ -> PhpConst(PhpConstNull) | Fable.NewList(Some(head, tail), _) -> libCall @@ -2472,7 +2475,7 @@ type PhpCompiler(com: Fable.Compiler) = member this.MakeUniqueVar(name) = id <- id + 1 - "_" + name + "__" + string id + "_" + name + "__" + string id member this.NewScope() = let oldScope = scope @@ -2524,7 +2527,7 @@ type PhpCompiler(com: Fable.Compiler) = basePath (fullPhpPath (fixExt file)) - if p.StartsWith "./" then + if p.StartsWith("./", StringComparison.Ordinal) then p.Substring 2 else p diff --git a/src/Fable.Transforms/Php/PhpPrinter.fs b/src/Fable.Transforms/Php/PhpPrinter.fs index 3c2e617a20..05e2fc4c2a 100644 --- a/src/Fable.Transforms/Php/PhpPrinter.fs +++ b/src/Fable.Transforms/Php/PhpPrinter.fs @@ -202,7 +202,7 @@ module Output = ) | PhpConst cst -> match cst with - | PhpConstNumber n -> write ctx (string n) + | PhpConstNumber n -> write ctx (string n) | PhpConstString s -> writeStr ctx s | PhpConstBool true -> write ctx "true" | PhpConstBool false -> write ctx "false" @@ -390,7 +390,7 @@ module Output = write ctx s write ctx "' => " | PhpArrayInt n -> - write ctx (string n) + write ctx (string n) write ctx " => " | PhpArrayNoIndex -> () @@ -423,7 +423,7 @@ module Output = match case with | IntCase i -> writei casesCtx "case " - write casesCtx (string i) + write casesCtx (string i) | StringCase s -> writei casesCtx "case '" write casesCtx s @@ -442,7 +442,7 @@ module Output = match level with | Some l -> write ctx " " - write ctx (string level) + write ctx (string level) | None -> () writeln ctx ";" @@ -708,7 +708,7 @@ module Output = } for i, d in file.Decls do - writeln ctx ("#" + string i) + writeln ctx ("#" + string i) writeDecl ctx d writeln ctx "" diff --git a/src/Fable.Transforms/Python/Fable2Python.fs b/src/Fable.Transforms/Python/Fable2Python.fs index 6c55abdaee..952ea0676c 100644 --- a/src/Fable.Transforms/Python/Fable2Python.fs +++ b/src/Fable.Transforms/Python/Fable2Python.fs @@ -942,7 +942,9 @@ module Annotation = match t with | Fable.Measure _ | Fable.Any -> stdlibModuleTypeHint com ctx "typing" "Any" [] - | Fable.GenericParam(name = name) when name.StartsWith("$$") -> + | Fable.GenericParam(name = name) when + name.StartsWith("$$", StringComparison.Ordinal) + -> stdlibModuleTypeHint com ctx "typing" "Any" [] | Fable.GenericParam(name = name) -> match repeatedGenerics with @@ -1526,8 +1528,9 @@ module Util = | "get" -> Expression.identifier "__getitem__" | "has" -> Expression.identifier "__contains__" | "delete" -> Expression.identifier "__delitem__" - | n when n.EndsWith "get_Count" -> Expression.identifier "__len__" // TODO: find a better way - | n when n.StartsWith("Symbol.iterator") -> + | n when n.EndsWith("get_Count", StringComparison.Ordinal) -> + Expression.identifier "__len__" // TODO: find a better way + | n when n.StartsWith("Symbol.iterator", StringComparison.Ordinal) -> let name = Identifier "__iter__" Expression.name name | n -> @@ -2223,7 +2226,7 @@ module Util = | Fable.UnitConstant -> undefined r, [] | Fable.BoolConstant x -> Expression.boolConstant (x, ?loc = r), [] | Fable.CharConstant x -> - Expression.stringConstant (string x, ?loc = r), [] + Expression.stringConstant (string x, ?loc = r), [] | Fable.StringConstant x -> Expression.stringConstant (x, ?loc = r), [] | Fable.StringTemplate(_, parts, values) -> match parts with @@ -2809,7 +2812,7 @@ module Util = let exprs, _, stmts' = transformCallArgs com ctx info - if macro.StartsWith("functools") then + if macro.StartsWith("functools", StringComparison.Ordinal) then com.GetImportExpr(ctx, "functools") |> ignore let args = exprs |> List.append thisArg @@ -4505,7 +4508,7 @@ module Util = Id = Identifier name } }) when - name.StartsWith("_") + name.StartsWith("_", StringComparison.Ordinal) -> Expression.subscript ( value, @@ -4980,7 +4983,7 @@ module Util = let genArgs = Array.init ent.GenericParameters.Length - (fun i -> "gen" + string i |> makeIdent) + (fun i -> "gen" + string i |> makeIdent) let args = genArgs @@ -5719,9 +5722,12 @@ module Util = | _ -> "" match name with - | name when name.StartsWith("__") -> "A" + name - | name when name.StartsWith("fable") -> "C" + name - | name when name.StartsWith(".") -> "D" + name + | name when name.StartsWith("__", StringComparison.Ordinal) -> + "A" + name + | name when name.StartsWith("fable", StringComparison.Ordinal) -> + "C" + name + | name when name.StartsWith(".", StringComparison.Ordinal) -> + "D" + name | _ -> "B" + name ) diff --git a/src/Fable.Transforms/Python/Prelude.fs b/src/Fable.Transforms/Python/Prelude.fs index 723c73fadb..c01b8cc957 100644 --- a/src/Fable.Transforms/Python/Prelude.fs +++ b/src/Fable.Transforms/Python/Prelude.fs @@ -169,7 +169,7 @@ module Naming = let rec check originalName n = let name = if n > 0 then - originalName + "_" + (string n) + originalName + "_" + (string n) else originalName @@ -200,7 +200,7 @@ module Naming = let c = ident.[i] if isIdentChar i c then - string c + string c elif c = '$' || c = '_' @@ -250,7 +250,7 @@ module Naming = let sanitizeIdent conflicts (name: string) part = let name = - if name.EndsWith("@") then + if name.EndsWith("@", StringComparison.Ordinal) then $"_{name.Substring(0, name.Length - 1)}" else name diff --git a/src/Fable.Transforms/Python/PythonPrinter.fs b/src/Fable.Transforms/Python/PythonPrinter.fs index 09dae126a7..30bd1fdb7a 100644 --- a/src/Fable.Transforms/Python/PythonPrinter.fs +++ b/src/Fable.Transforms/Python/PythonPrinter.fs @@ -370,7 +370,7 @@ module PrinterExtensions = let i = int m.Groups[1].Value for j = i to node.Args.Length - 1 do - rep.Add("$" + string j) + rep.Add("$" + string j) String.concat ", " rep ) @@ -598,7 +598,7 @@ module PrinterExtensions = printer.Print(Naming.escapeString (fun _ -> false) value) printer.Print("\"") | Constant(value = FloatLiteral value) -> - let value = string value + let value = string value printer.Print(value) // Make sure it's a valid Python float (not int) @@ -615,8 +615,9 @@ module PrinterExtensions = else "False" ) - | Constant(value = IntLiteral value) -> printer.Print(string value) - | Constant(value = value) -> printer.Print(string value) + | Constant(value = IntLiteral value) -> + printer.Print(string value) + | Constant(value = value) -> printer.Print(string value) | IfExp ex -> printer.Print(ex) | Call ex -> printer.Print(ex) diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 225d43bbc0..d403384386 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -3,6 +3,7 @@ module Fable.Transforms.Py.Replacements #nowarn "1182" +open System open Fable open Fable.AST open Fable.AST.Fable @@ -3188,7 +3189,11 @@ let tuples match i.CompiledName, thisArg with | (".ctor" | "Create"), _ -> - let isStruct = i.DeclaringEntityFullName.StartsWith("System.ValueTuple") + let isStruct = + i.DeclaringEntityFullName.StartsWith( + "System.ValueTuple", + StringComparison.Ordinal + ) Value(NewTuple(args, isStruct), r) |> Some | "get_Item1", Some x -> Get(x, TupleIndex 0, t, r) |> Some @@ -4129,7 +4134,7 @@ let bigints ?loc = r ) |> Some - | None, meth when meth.StartsWith("get_") -> + | None, meth when meth.StartsWith("get_", StringComparison.Ordinal) -> Helper.LibValue(com, "big_int", meth, t) |> Some | callee, meth -> let args = @@ -6943,7 +6948,9 @@ let tryCall | "Microsoft.FSharp.Reflection.FSharpReflectionExtensions" -> // In netcore F# Reflection methods become extensions // with names like `FSharpType.GetExceptionFields.Static` - let isFSharpType = info.CompiledName.StartsWith("FSharpType") + let isFSharpType = + info.CompiledName.StartsWith("FSharpType", StringComparison.Ordinal) + let methName = info.CompiledName |> Naming.extensionMethodName if isFSharpType then diff --git a/src/Fable.Transforms/Replacements.Util.fs b/src/Fable.Transforms/Replacements.Util.fs index bdf885b438..7cf8f295c1 100644 --- a/src/Fable.Transforms/Replacements.Util.fs +++ b/src/Fable.Transforms/Replacements.Util.fs @@ -2,6 +2,7 @@ module Fable.Transforms.Replacements.Util #nowarn "1182" +open System open System.Text.RegularExpressions open Fable @@ -296,7 +297,7 @@ let str txt = Value(StringConstant txt, None) let genArg (com: ICompiler) (ctx: Context) r i (genArgs: Type list) = List.tryItem i genArgs |> Option.defaultWith (fun () -> - "Couldn't find generic argument in position " + (string i) + "Couldn't find generic argument in position " + (string i) |> addError com ctx.InlinePath r Any @@ -436,7 +437,7 @@ let changeRangeToCallSite let splitFullName (fullname: string) = let fullname = - match fullname.IndexOf("[") with + match fullname.IndexOf("[", StringComparison.Ordinal) with | -1 -> fullname | i -> fullname[.. i - 1] @@ -446,7 +447,7 @@ let splitFullName (fullname: string) = let getTypeNameFromFullName (fullname: string) = let fullname = - match fullname.IndexOf("[") with + match fullname.IndexOf("[", StringComparison.Ordinal) with | -1 -> fullname | i -> fullname[.. i - 1] diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index 88e070f8cc..cd28bc2c91 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -2,6 +2,7 @@ module Fable.Transforms.JS.Replacements #nowarn "1182" +open System open System.Text.RegularExpressions open Fable open Fable.AST @@ -1435,12 +1436,14 @@ let fableCoreLib = let fixDynamicImportPath = function - | Value(StringConstant path, r) when path.EndsWith(".fs") -> + | Value(StringConstant path, r) when + path.EndsWith(".fs", StringComparison.Ordinal) + -> // In imports *.ts extensions have to be converted to *.js extensions instead let fileExt = com.Options.FileExtension let fileExt = - if fileExt.EndsWith(".ts") then + if fileExt.EndsWith(".ts", StringComparison.Ordinal) then Path.ChangeExtension(fileExt, ".js") else fileExt @@ -2546,7 +2549,19 @@ let strings | [ ExprType Char ] | [ ExprType String ] | [ ExprType Char; ExprType(Number(Int32, NumberInfo.Empty)) ] - | [ ExprType String; ExprType(Number(Int32, NumberInfo.Empty)) ] -> + | [ ExprType String; ExprType(Number(Int32, NumberInfo.Empty)) ] + | [ ExprType String; StringComparisonEnumValue ] + | [ ExprType String + ExprType(Number(Int32, NumberInfo.Empty)) + StringComparisonEnumValue ] -> + let args = + args + |> List.filter ( + function + | StringComparisonEnumValue -> false + | _ -> true + ) + Helper.InstanceCall( c, Naming.lowerFirst i.CompiledName, @@ -3261,7 +3276,12 @@ let tuples match i.CompiledName, thisArg with | (".ctor" | "Create"), _ -> - let isStruct = i.DeclaringEntityFullName.StartsWith("System.ValueTuple") + let isStruct = + i.DeclaringEntityFullName.StartsWith( + "System.ValueTuple", + StringComparison.Ordinal + ) + Value(NewTuple(args, isStruct), r) |> Some | "get_Item1", Some x -> Get(x, TupleIndex 0, t, r) |> Some | "get_Item2", Some x -> Get(x, TupleIndex 1, t, r) |> Some @@ -6283,7 +6303,7 @@ let guids = let parseGuid (literalGuid: string) = try - System.Guid.Parse(literalGuid) |> string |> makeStrConst + System.Guid.Parse(literalGuid) |> string |> makeStrConst with e -> e.Message |> addErrorAndReturnNull com ctx.InlinePath r |> Some @@ -7042,7 +7062,9 @@ let tryCall | "Microsoft.FSharp.Reflection.FSharpReflectionExtensions" -> // In netcore F# Reflection methods become extensions // with names like `FSharpType.GetExceptionFields.Static` - let isFSharpType = info.CompiledName.StartsWith("FSharpType") + let isFSharpType = + info.CompiledName.StartsWith("FSharpType", StringComparison.Ordinal) + let methName = info.CompiledName |> Naming.extensionMethodName if isFSharpType then @@ -7108,7 +7130,8 @@ let tryBaseConstructor | Types.exception_ -> Some(makeImportLib com Any "Exception" "Types", args) | Types.attribute -> Some(makeImportLib com Any "Attribute" "Types", args) | fullName when - fullName.StartsWith("Fable.Core.") && fullName.EndsWith("Attribute") + fullName.StartsWith("Fable.Core.", StringComparison.Ordinal) + && fullName.EndsWith("Attribute", StringComparison.Ordinal) -> Some(makeImportLib com Any "Attribute" "Types", args) | Types.dictionary -> diff --git a/src/Fable.Transforms/Rust/Fable2Rust.fs b/src/Fable.Transforms/Rust/Fable2Rust.fs index 57585ae2bf..a74ee2c1bc 100644 --- a/src/Fable.Transforms/Rust/Fable2Rust.fs +++ b/src/Fable.Transforms/Rust/Fable2Rust.fs @@ -1,5 +1,6 @@ module rec Fable.Transforms.Rust.Fable2Rust +open System open Fable open Fable.AST open Fable.Transforms @@ -393,11 +394,11 @@ module TypeInfo = let hasMutableFields (com: IRustCompiler) (ent: Fable.Entity) = if ent.IsFSharpUnion then ent.UnionCases - |> Seq.exists (fun uci -> + |> List.exists (fun uci -> uci.UnionCaseFields |> List.exists (fun fi -> fi.IsMutable) ) else - ent.FSharpFields |> Seq.exists (fun fi -> fi.IsMutable) + ent.FSharpFields |> List.exists (fun fi -> fi.IsMutable) let isEntityOfType (com: IRustCompiler) @@ -839,7 +840,7 @@ module TypeInfo = | [ Fable.Unit ] -> [] | _ -> argTypes - let argCount = string (List.length argTypes) + let argCount = string (List.length argTypes) let genArgs = argTypes @ [ returnType ] transformImportType com ctx genArgs "Native" ("Func" + argCount) @@ -989,7 +990,12 @@ module TypeInfo = let (|HasEmitAttribute|_|) (ent: Fable.Entity) = ent.Attributes |> Seq.tryPick (fun att -> - if att.Entity.FullName.StartsWith(Atts.emit) then + if + att.Entity.FullName.StartsWith( + Atts.emit, + StringComparison.Ordinal + ) + then match att.ConstructorArgs with | [ :? string as macro ] -> Some macro | _ -> None @@ -1006,7 +1012,12 @@ module TypeInfo = let (|HasReferenceTypeAttribute|_|) (ent: Fable.Entity) = ent.Attributes |> Seq.tryPick (fun att -> - if att.Entity.FullName.StartsWith(Atts.referenceType) then + if + att.Entity.FullName.StartsWith( + Atts.referenceType, + StringComparison.Ordinal + ) + then match att.ConstructorArgs with | [ :? int as ptrType ] -> match ptrType with @@ -1058,7 +1069,7 @@ module TypeInfo = transformGenericType com ctx genArgs (rawIdent "Result") let transformChoiceType com ctx genArgs : Rust.Ty = - let argCount = string (List.length genArgs) + let argCount = string (List.length genArgs) transformImportType com ctx genArgs "Choice" ("Choice`" + argCount) let transformRefCellType com ctx genArg : Rust.Ty = @@ -1368,7 +1379,7 @@ module Util = let isUnitArg (ident: Fable.Ident) = ident.IsCompilerGenerated && ident.Type = Fable.Unit - && (ident.DisplayName.StartsWith("unitVar") + && (ident.DisplayName.StartsWith("unitVar", StringComparison.Ordinal) || ident.DisplayName.Contains("@")) let discardUnitArg (genArgs: Fable.Type list) (args: Fable.Ident list) = @@ -1753,7 +1764,7 @@ module Util = | [ Fable.Unit ] -> [] | _ -> argTypes - let argCount = string (List.length argTypes) + let argCount = string (List.length argTypes) let funcWrap = getLibraryImportName com ctx "Native" ("Func" + argCount) let expr = transformIdent com ctx None ident @@ -2007,35 +2018,35 @@ module Util = None | NativeInt, (:? nativeint as x) -> - let expr = mkIsizeLitExpr (abs x |> string) + let expr = mkIsizeLitExpr (abs x |> string) if x < 0n then expr |> mkNegExpr else expr | Int8, (:? int8 as x) -> - let expr = mkInt8LitExpr (abs x |> string) + let expr = mkInt8LitExpr (abs x |> string) if x < 0y then expr |> mkNegExpr else expr | Int16, (:? int16 as x) -> - let expr = mkInt16LitExpr (abs x |> string) + let expr = mkInt16LitExpr (abs x |> string) if x < 0s then expr |> mkNegExpr else expr | Int32, (:? int32 as x) -> - let expr = mkInt32LitExpr (abs x |> string) + let expr = mkInt32LitExpr (abs x |> string) if x < 0 then expr |> mkNegExpr else expr | Int64, (:? int64 as x) -> - let expr = mkInt64LitExpr (abs x |> string) + let expr = mkInt64LitExpr (abs x |> string) if x < 0 then expr |> mkNegExpr @@ -2044,36 +2055,37 @@ module Util = | Int128, x -> // (:? System.Int128 as x) -> // let expr = mkInt128LitExpr (System.Int128.Abs(x) |> string) // if x < 0 then expr |> mkNegExpr else expr - let s = string x + let s = string x let expr = mkInt128LitExpr (s.TrimStart('-')) - if s.StartsWith("-") then + if s.StartsWith("-", StringComparison.Ordinal) then expr |> mkNegExpr else expr - | UNativeInt, (:? unativeint as x) -> mkUsizeLitExpr (x |> string) - | UInt8, (:? uint8 as x) -> mkUInt8LitExpr (x |> string) - | UInt16, (:? uint16 as x) -> mkUInt16LitExpr (x |> string) - | UInt32, (:? uint32 as x) -> mkUInt32LitExpr (x |> string) - | UInt64, (:? uint64 as x) -> mkUInt64LitExpr (x |> string) + | UNativeInt, (:? unativeint as x) -> + mkUsizeLitExpr (x |> string) + | UInt8, (:? uint8 as x) -> mkUInt8LitExpr (x |> string) + | UInt16, (:? uint16 as x) -> mkUInt16LitExpr (x |> string) + | UInt32, (:? uint32 as x) -> mkUInt32LitExpr (x |> string) + | UInt64, (:? uint64 as x) -> mkUInt64LitExpr (x |> string) | UInt128, x -> // (:? System.UInt128 as x) -> - mkUInt128LitExpr (x |> string) + mkUInt128LitExpr (x |> string) | Float16, (:? float32 as x) -> - let expr = mkFloat32LitExpr (abs x |> string) + let expr = mkFloat32LitExpr (abs x |> string) if x < 0.0f then expr |> mkNegExpr else expr | Float32, (:? float32 as x) -> - let expr = mkFloat32LitExpr (abs x |> string) + let expr = mkFloat32LitExpr (abs x |> string) if x < 0.0f then expr |> mkNegExpr else expr | Float64, (:? float as x) -> - let expr = mkFloat64LitExpr (abs x |> string) + let expr = mkFloat64LitExpr (abs x |> string) if x < 0.0 then expr |> mkNegExpr @@ -2085,7 +2097,7 @@ module Util = $"Expected literal of type %A{kind} but got {x.GetType().FullName}" |> addError com [] r - mkFloat64LitExpr (string 0.) + mkFloat64LitExpr (string 0.) let makeStaticString com ctx (value: Rust.Expr) = makeLibCall com ctx None "String" "string" [ value ] @@ -2245,7 +2257,12 @@ module Util = | "FSharp.Core.FSharpResult`2.Ok" -> rawIdent "Ok" |> Some | "FSharp.Core.FSharpResult`2.Error" -> rawIdent "Err" |> Some | _ -> - if fullName.StartsWith("FSharp.Core.FSharpChoice`") then + if + fullName.StartsWith( + "FSharp.Core.FSharpChoice`", + StringComparison.Ordinal + ) + then fullName |> Fable.Naming.replacePrefix "FSharp.Core.FSharp" "" |> Some @@ -2674,7 +2691,7 @@ module Util = let info = emitInfo.CallInfo let macro = emitInfo.Macro // if it ends with '!', it's a Rust macro - if macro.EndsWith("!") then + if macro.EndsWith("!", StringComparison.Ordinal) then transformMacro com ctx range emitInfo else // otherwise it's an Emit let thisArg = @@ -4392,7 +4409,7 @@ module Util = ctx.ScopedSymbols |> Helpers.Map.except closedOverCloneableIdents let ctx = { ctx with ScopedSymbols = scopedSymbols } //; HasMultipleUses = true } - let argCount = args |> List.length |> string + let argCount = args |> List.length |> string let fnBody = transformFunctionBody com ctx args body let fnBody = @@ -4665,11 +4682,16 @@ module Util = | [ :? string as name; :? string as value ] -> [ mkEqAttr name value ] | [ :? string as name; :? (obj[]) as items ] -> - [ mkAttr name (items |> Array.map string) ] + [ mkAttr name (items |> Array.map string) ] | _ -> [] // translate test methods attributes // TODO: support more test frameworks - elif a.Entity.FullName.EndsWith(".FactAttribute") then + elif + a.Entity.FullName.EndsWith( + ".FactAttribute", + StringComparison.Ordinal + ) + then [ mkAttr "test" [] ] else [] @@ -4686,7 +4708,7 @@ module Util = | [ :? string as name; :? string as value ] -> [ mkInnerEqAttr name value ] | [ :? string as name; :? (obj[]) as items ] -> - [ mkInnerAttr name (items |> Array.map string) ] + [ mkInnerAttr name (items |> Array.map string) ] | _ -> [] else [] @@ -5827,13 +5849,14 @@ module Util = let isFableLibraryPath (com: IRustCompiler) (path: string) = not (isFableLibrary com) - && (path.StartsWith(com.LibraryDir) || path = "fable_library_rust") + && (path.StartsWith(com.LibraryDir, StringComparison.Ordinal) + || path = "fable_library_rust") let getImportModulePath (com: IRustCompiler) (path: string) = let isAbsolutePath = - path.StartsWith("/") - || path.StartsWith("\\") - || path.IndexOf(":") = 1 + path.StartsWith("/", StringComparison.Ordinal) + || path.StartsWith("\\", StringComparison.Ordinal) + || path.IndexOf(":", StringComparison.Ordinal) = 1 let modulePath = if isAbsolutePath || (isFableLibraryPath com path) then @@ -5900,7 +5923,7 @@ module Util = |> getUniqueNameInRootScope ctx let fixFileExtension (com: IRustCompiler) (path: string) = - if path.EndsWith(".fs") then + if path.EndsWith(".fs", StringComparison.Ordinal) then let fileExt = com.Options.FileExtension Path.ChangeExtension(path, fileExt) else @@ -5930,7 +5953,7 @@ module Compiler = "`importMember` must be assigned to a variable" |> addError com [] r - let isMacro = selector.EndsWith("!") + let isMacro = selector.EndsWith("!", StringComparison.Ordinal) let selector = selector |> Fable.Naming.replaceSuffix "!" "" let path = fixFileExtension self path diff --git a/src/Fable.Transforms/Rust/Replacements.fs b/src/Fable.Transforms/Rust/Replacements.fs index 134e008d39..1ecd2e240c 100644 --- a/src/Fable.Transforms/Rust/Replacements.fs +++ b/src/Fable.Transforms/Rust/Replacements.fs @@ -3,6 +3,7 @@ module Fable.Transforms.Rust.Replacements #nowarn "1182" +open System open System.Text.RegularExpressions open Fable open Fable.AST @@ -3116,7 +3117,12 @@ let tuples match i.CompiledName, thisArg with | (".ctor" | "Create"), _ -> - let isStruct = i.DeclaringEntityFullName.StartsWith("System.ValueTuple") + let isStruct = + i.DeclaringEntityFullName.StartsWith( + "System.ValueTuple", + StringComparison.Ordinal + ) + Value(NewTuple(args, isStruct), r) |> Some | "get_Item1", Some x -> Get(x, TupleIndex 0, t, r) |> Some | "get_Item2", Some x -> Get(x, TupleIndex 1, t, r) |> Some @@ -4087,7 +4093,7 @@ let bigints ?loc = r ) |> Some - | meth, None, _ when meth.StartsWith("get_") -> + | meth, None, _ when meth.StartsWith("get_", StringComparison.Ordinal) -> let meth = meth |> Naming.removeGetSetPrefix |> Naming.lowerFirst Helper.LibCall(com, "BigInt", meth, t, []) |> Some | meth, None, _ -> @@ -6569,7 +6575,9 @@ let tryCall | "Microsoft.FSharp.Reflection.FSharpReflectionExtensions" -> // In netcore F# Reflection methods become extensions // with names like `FSharpType.GetExceptionFields.Static` - let isFSharpType = info.CompiledName.StartsWith("FSharpType") + let isFSharpType = + info.CompiledName.StartsWith("FSharpType", StringComparison.Ordinal) + let methName = info.CompiledName |> Naming.extensionMethodName if isFSharpType then diff --git a/src/Fable.Transforms/Transforms.Util.fs b/src/Fable.Transforms/Transforms.Util.fs index bbcb8114ed..4cbde5d27e 100644 --- a/src/Fable.Transforms/Transforms.Util.fs +++ b/src/Fable.Transforms/Transforms.Util.fs @@ -1,5 +1,7 @@ namespace Fable.Transforms +open System + [] module Atts = [] @@ -663,7 +665,7 @@ module Log = let attachRange (range: SourceLocation option) msg = match range with - | Some range -> msg + " " + (string range) + | Some range -> msg + " " + (string range) | None -> msg let attachRangeAndFile @@ -672,7 +674,8 @@ module Log = msg = match range with - | Some range -> msg + " " + (string range) + " (" + fileName + ")" + | Some range -> + msg + " " + (string range) + " (" + fileName + ")" | None -> msg + " (" + fileName + ")" @@ -851,6 +854,15 @@ module AST = | MaybeCasted(Value(Null _, _)) -> Some() | _ -> None + let (|StringComparisonEnumValue|_|) e = + match e with + | Expr.Value( + kind = NumberConstant( + info = NumberInfo.IsEnum({ + FullName = "System.StringComparison" + }))) -> Some() + | _ -> None + // TODO: Improve this, see https://github.com/fable-compiler/Fable/issues/1659#issuecomment-445071965 // This is mainly used for inlining so a computation or a reference to a mutable value are understood // as a side effects too (because we don't want to duplicate or change the order of execution) @@ -1141,7 +1153,11 @@ module AST = match com.Options.Language with | Rust -> if - moduleName = "System" || moduleName.StartsWith("System.") + moduleName = "System" + || moduleName.StartsWith( + "System.", + StringComparison.Ordinal + ) then moduleName + "::" + memberName else diff --git a/src/fable-library-dart/Seq.fs b/src/fable-library-dart/Seq.fs index f585ddcc16..a1be4f23df 100644 --- a/src/fable-library-dart/Seq.fs +++ b/src/fable-library-dart/Seq.fs @@ -37,7 +37,7 @@ module Enumerator = if i > 0 then str <- str + "; " - str <- str + (string e.Current) + str <- str + (string<'T> e.Current) i <- i + 1 if i = maxCount then diff --git a/src/fable-library/Seq.fs b/src/fable-library/Seq.fs index 62d4126216..abe177b489 100644 --- a/src/fable-library/Seq.fs +++ b/src/fable-library/Seq.fs @@ -56,7 +56,7 @@ module Enumerator = if i > 0 then str <- str + "; " - str <- str + (string e.Current) + str <- str + (string<'T> e.Current) i <- i + 1 if i = maxCount then diff --git a/src/fable-library/System.Text.fs b/src/fable-library/System.Text.fs index 5c21436631..f47b8c7e1e 100644 --- a/src/fable-library/System.Text.fs +++ b/src/fable-library/System.Text.fs @@ -22,23 +22,23 @@ type StringBuilder(value: string, capacity: int) = x member x.Append(c: char) = - buf.Add(string c) + buf.Add(string c) x member x.Append(o: int) = - buf.Add(string o) + buf.Add(string o) x member x.Append(o: float) = - buf.Add(string o) + buf.Add(string o) x member x.Append(o: bool) = - buf.Add(string o) + buf.Add(string o) x member x.Append(o: obj) = - buf.Add(string o) + buf.Add(string o) x member x.Append(cs: char[]) = diff --git a/src/quicktest-dart/QuickTest.fs b/src/quicktest-dart/QuickTest.fs index e71add1a83..8ca2a7234d 100644 --- a/src/quicktest-dart/QuickTest.fs +++ b/src/quicktest-dart/QuickTest.fs @@ -27,7 +27,7 @@ let throwsAnyError (f: unit -> 'a) : unit = f () |> ignore true with e -> - print $"Got expected error: %s{string e}" + print $"Got expected error: %s{string e}" false if success then diff --git a/src/quicktest/QuickTest.fs b/src/quicktest/QuickTest.fs index 64f5b9c1a8..3db32de935 100644 --- a/src/quicktest/QuickTest.fs +++ b/src/quicktest/QuickTest.fs @@ -42,7 +42,9 @@ let testCase (msg: string) f : unit = printfn "%s" ex.Message if - ex.Message <> null && ex.Message.StartsWith("[ASSERT ERROR]") |> not + ex.Message <> null + && ex.Message.StartsWith("[ASSERT ERROR]", StringComparison.Ordinal) + |> not then printfn "%s" (ex.StackTrace ??= "") @@ -60,7 +62,11 @@ let testCaseAsync msg f = if ex.Message <> null - && ex.Message.StartsWith("[ASSERT ERROR]") |> not + && ex.Message.StartsWith( + "[ASSERT ERROR]", + StringComparison.Ordinal + ) + |> not then printfn "%s" (ex.StackTrace ??= "") } diff --git a/tests/Js/Main/StringTests.fs b/tests/Js/Main/StringTests.fs index 92ce6496f6..a7c7eb271c 100644 --- a/tests/Js/Main/StringTests.fs +++ b/tests/Js/Main/StringTests.fs @@ -719,6 +719,14 @@ let tests = "abcdbc".IndexOf('b', 3) |> equal 4 + testCase "String.IndexOf with StringComparison" <| fun () -> + "abcdbc".IndexOf("b", StringComparison.Ordinal) + |> equal 1 + + testCase "String.IndexOf with index and StringComparison" <| fun () -> + "abcdbc".IndexOf("b", 3, StringComparison.Ordinal) + |> equal 4 + testCase "String.LastIndexOf char works" <| fun () -> "abcdbc".LastIndexOf('b') * 100 + "abcd".LastIndexOf('e') |> equal 399 @@ -727,6 +735,14 @@ let tests = "abcdbcebc".LastIndexOf('b', 3) |> equal 1 + testCase "String.LastIndexOf with StringComparison" <| fun () -> + "abcdbc".LastIndexOf("b", StringComparison.Ordinal) + |> equal 4 + + testCase "String.LastIndexOf with index and StringComparison" <| fun () -> + "abcdbc".LastIndexOf("b", 3, StringComparison.Ordinal) + |> equal 1 + testCase "String.IndexOf works" <| fun () -> "abcd".IndexOf("bc") * 100 + "abcd".IndexOf("bd") |> equal 99