Skip to content

Commit 9893d33

Browse files
authored
Merge pull request #659 from vasily-kirichenko/optimize-filename
Fix ProjectCracker, optimize Filename.checkPathForIllegalChars
2 parents 81eb5b7 + e28cf5b commit 9893d33

File tree

4 files changed

+70
-49
lines changed

4 files changed

+70
-49
lines changed

src/fsharp/FSharp.Compiler.Service.ProjectCracker/ProjectCracker.fs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ open System.IO
1010
open System
1111

1212
type ProjectCracker =
13-
1413
static member GetProjectOptionsFromProjectFileLogged(projectFileName : string, ?properties : (string * string) list, ?loadedTimeStamp, ?enableLogging) =
1514
let loadedTimeStamp = defaultArg loadedTimeStamp DateTime.MaxValue // Not 'now', we don't want to force reloading
1615
let properties = defaultArg properties []
@@ -19,10 +18,23 @@ type ProjectCracker =
1918

2019
let rec convert (opts: Microsoft.FSharp.Compiler.SourceCodeServices.ProjectCrackerTool.ProjectOptions) : FSharpProjectOptions =
2120
let referencedProjects = Array.map (fun (a, b) -> a, convert b) opts.ReferencedProjectOptions
21+
22+
let sourceFiles, otherOptions =
23+
opts.Options |> Array.partition (fun x -> Path.GetExtension(x).ToLower() = ".fs")
24+
25+
let sepChar = Path.DirectorySeparatorChar
26+
27+
let sourceFiles = sourceFiles |> Array.map (fun x ->
28+
match sepChar with
29+
| '\\' -> x.Replace('/', '\\')
30+
| '/' -> x.Replace('\\', '/')
31+
| _ -> x
32+
)
33+
2234
logMap := Map.add opts.ProjectFile opts.LogOutput !logMap
2335
{ ProjectFileName = opts.ProjectFile
24-
ProjectFileNames = [| |]
25-
OtherOptions = opts.Options
36+
ProjectFileNames = sourceFiles
37+
OtherOptions = otherOptions
2638
ReferencedProjects = referencedProjects
2739
IsIncompleteTypeCheckEnvironment = false
2840
UseScriptResolutionRules = false

src/utils/filename.fs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,28 @@ let illegalPathChars =
1515
let chars = Path.GetInvalidPathChars ()
1616
chars
1717

18-
let checkPathForIllegalChars (path:string) =
19-
let len = path.Length
20-
for i = 0 to len - 1 do
21-
let c = path.[i]
18+
type private PathState =
19+
| Legal
20+
| Illegal of path: string * illegalChar: char
21+
22+
let private checkPathForIllegalChars =
23+
let cache = System.Collections.Concurrent.ConcurrentDictionary<string, PathState>()
24+
fun (path: string) ->
25+
match cache.TryGetValue path with
26+
| true, Legal -> ()
27+
| true, Illegal (path, c) -> raise(IllegalFileNameChar(path, c))
28+
| _ ->
29+
let len = path.Length
30+
for i = 0 to len - 1 do
31+
let c = path.[i]
2232

23-
// Determine if this character is disallowed within a path by
24-
// attempting to find it in the array of illegal path characters.
25-
for badChar in illegalPathChars do
26-
if c = badChar then
27-
raise(IllegalFileNameChar(path, c))
33+
// Determine if this character is disallowed within a path by
34+
// attempting to find it in the array of illegal path characters.
35+
for badChar in illegalPathChars do
36+
if c = badChar then
37+
cache.[path] <- Illegal(path, c)
38+
raise(IllegalFileNameChar(path, c))
39+
cache.[path] <- Legal
2840

2941
// Case sensitive (original behaviour preserved).
3042
let checkSuffix (x:string) (y:string) = x.EndsWith(y,System.StringComparison.Ordinal)

tests/service/ExprTests.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ let ``Check use of type provider that provides calls to F# code`` () =
872872
|> checker.ParseAndCheckProject
873873
|> Async.RunSynchronously
874874

875-
res.Errors.Length |> shouldEqual 0
875+
Assert.AreEqual ([||], res.Errors, sprintf "Should not be errors, but: %A" res.Errors)
876876

877877
let results =
878878
[ for f in res.AssemblyContents.ImplementationFiles do

tests/service/ProjectOptionsTests.fs

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,22 @@ let checkOptionNotPresent (opts:string[]) s =
3737
let getReferencedFilenames = Array.choose (fun (o:string) -> if o.StartsWith("-r:") then o.[3..] |> (Path.GetFileName >> Some) else None)
3838
let getReferencedFilenamesAndContainingFolders = Array.choose (fun (o:string) -> if o.StartsWith("-r:") then o.[3..] |> (fun r -> ((r |> Path.GetFileName), (r |> Path.GetDirectoryName |> Path.GetFileName)) |> Some) else None)
3939
let getOutputFile = Array.pick (fun (o:string) -> if o.StartsWith("--out:") then o.[6..] |> Some else None)
40-
let getCompiledFilenames = Array.choose (fun (o:string) -> if o.EndsWith(".fs") then o |> (Path.GetFileName >> Some) else None)
40+
41+
let getCompiledFilenames =
42+
Array.choose (fun (opt: string) ->
43+
if opt.EndsWith ".fs" then
44+
opt |> Path.GetFileName |> Some
45+
else None)
46+
>> Array.distinct
4147

4248
[<Test>]
4349
let ``Project file parsing example 1 Default Configuration`` () =
4450
let projectFile = __SOURCE_DIRECTORY__ + @"/FSharp.Compiler.Service.Tests.fsproj"
4551
let options = ProjectCracker.GetProjectOptionsFromProjectFile(projectFile)
4652

53+
checkOption options.ProjectFileNames "FileSystemTests.fs"
54+
4755
checkOption options.OtherOptions "FSharp.Compiler.Service.dll"
48-
checkOption options.OtherOptions "FileSystemTests.fs"
4956
checkOption options.OtherOptions "--define:TRACE"
5057
checkOption options.OtherOptions "--define:DEBUG"
5158
checkOption options.OtherOptions "--flaterrors"
@@ -58,8 +65,9 @@ let ``Project file parsing example 1 Release Configuration`` () =
5865
// Check with Configuration = Release
5966
let options = ProjectCracker.GetProjectOptionsFromProjectFile(projectFile, [("Configuration", "Release")])
6067

68+
checkOption options.ProjectFileNames "FileSystemTests.fs"
69+
6170
checkOption options.OtherOptions "FSharp.Compiler.Service.dll"
62-
checkOption options.OtherOptions "FileSystemTests.fs"
6371
checkOption options.OtherOptions "--define:TRACE"
6472
checkOptionNotPresent options.OtherOptions "--define:DEBUG"
6573
checkOption options.OtherOptions "--debug:pdbonly"
@@ -68,11 +76,11 @@ let ``Project file parsing example 1 Release Configuration`` () =
6876
let ``Project file parsing example 1 Default configuration relative path`` () =
6977
let projectFile = "FSharp.Compiler.Service.Tests.fsproj"
7078
Directory.SetCurrentDirectory(__SOURCE_DIRECTORY__)
71-
7279
let options = ProjectCracker.GetProjectOptionsFromProjectFile(projectFile)
7380

81+
checkOption options.ProjectFileNames "FileSystemTests.fs"
82+
7483
checkOption options.OtherOptions "FSharp.Compiler.Service.dll"
75-
checkOption options.OtherOptions "FileSystemTests.fs"
7684
checkOption options.OtherOptions "--define:TRACE"
7785
checkOption options.OtherOptions "--define:DEBUG"
7886
checkOption options.OtherOptions "--flaterrors"
@@ -110,21 +118,16 @@ let ``Project file parsing Sample_VS2013_FSharp_Portable_Library_net451_adjusted
110118

111119
[<Test>]
112120
let ``Project file parsing -- compile files 1``() =
113-
let p = ProjectCracker.GetProjectOptionsFromProjectFile(__SOURCE_DIRECTORY__ + @"/data/Test1.fsproj")
114-
115-
p.OtherOptions
116-
|> getCompiledFilenames
117-
|> set
118-
|> should equal (set [ "Test1File1.fs"; "Test1File2.fs" ])
121+
let opts = ProjectCracker.GetProjectOptionsFromProjectFile(__SOURCE_DIRECTORY__ + @"/data/Test1.fsproj")
122+
CollectionAssert.AreEqual (["Test1File2.fs"; "Test1File1.fs"], opts.ProjectFileNames |> Array.map Path.GetFileName)
123+
CollectionAssert.IsEmpty (getCompiledFilenames opts.OtherOptions)
119124

120125
[<Test>]
121126
let ``Project file parsing -- compile files 2``() =
122-
let p = ProjectCracker.GetProjectOptionsFromProjectFile(__SOURCE_DIRECTORY__ + @"/data/Test2.fsproj")
127+
let opts = ProjectCracker.GetProjectOptionsFromProjectFile(__SOURCE_DIRECTORY__ + @"/data/Test2.fsproj")
123128

124-
p.OtherOptions
125-
|> getCompiledFilenames
126-
|> set
127-
|> should equal (set [ "Test2File1.fs"; "Test2File2.fs" ])
129+
CollectionAssert.AreEqual (["Test2File2.fs"; "Test2File1.fs"], opts.ProjectFileNames |> Array.map Path.GetFileName)
130+
CollectionAssert.IsEmpty (getCompiledFilenames opts.OtherOptions)
128131

129132
[<Test>]
130133
let ``Project file parsing -- bad project file``() =
@@ -192,18 +195,16 @@ let ``Project file parsing -- reference project output file``() =
192195
|> Array.choose (fun (o:string) -> if o.StartsWith("-r:") then o.[3..] |> Some else None)
193196
|> should contain (normalizePath (__SOURCE_DIRECTORY__ + @"/data/DifferingOutputDir/Dir1/OutputDir1/Test1.dll"))
194197

195-
196198
[<Test>]
197199
let ``Project file parsing -- Tools Version 12``() =
198-
let p = ProjectCracker.GetProjectOptionsFromProjectFile(__SOURCE_DIRECTORY__ + @"/data/ToolsVersion12.fsproj")
199-
200-
checkOption (getReferencedFilenames p.OtherOptions) "System.Core.dll"
200+
let opts = ProjectCracker.GetProjectOptionsFromProjectFile(__SOURCE_DIRECTORY__ + @"/data/ToolsVersion12.fsproj")
201+
checkOption (getReferencedFilenames opts.OtherOptions) "System.Core.dll"
201202

202203
[<Test>]
203204
let ``Project file parsing -- Logging``() =
204-
let f = normalizePath (__SOURCE_DIRECTORY__ + @"/data/ToolsVersion12.fsproj")
205-
let p, logMap = ProjectCracker.GetProjectOptionsFromProjectFileLogged(f, enableLogging=true)
206-
let log = logMap.[f]
205+
let projectFileName = normalizePath (__SOURCE_DIRECTORY__ + @"/data/ToolsVersion12.fsproj")
206+
let _, logMap = ProjectCracker.GetProjectOptionsFromProjectFileLogged(projectFileName, enableLogging=true)
207+
let log = logMap.[projectFileName]
207208

208209
if runningOnMono then
209210
Assert.That(log, Is.StringContaining("Reference System.Core resolved"))
@@ -386,30 +387,26 @@ let ``Project file parsing -- Exe with a PCL reference``() =
386387

387388
[<Test>]
388389
let ``Project file parsing -- project file contains project reference to out-of-solution project and is used in release mode``() =
389-
390-
let f = normalizePath(__SOURCE_DIRECTORY__ + @"/data/Test2.fsproj")
391-
let p = ProjectCracker.GetProjectOptionsFromProjectFile(f,[("Configuration","Release")])
392-
let references = getReferencedFilenamesAndContainingFolders p.OtherOptions |> set
390+
let projectFileName = normalizePath(__SOURCE_DIRECTORY__ + @"/data/Test2.fsproj")
391+
let opts = ProjectCracker.GetProjectOptionsFromProjectFile(projectFileName,[("Configuration","Release")])
392+
let references = getReferencedFilenamesAndContainingFolders opts.OtherOptions |> set
393393
// Check the reference is to a release DLL
394394
references |> should contain ("Test1.dll", "Release")
395395

396396
[<Test>]
397397
let ``Project file parsing -- project file contains project reference to out-of-solution project and is used in debug mode``() =
398398

399-
let f = normalizePath(__SOURCE_DIRECTORY__ + @"/data/Test2.fsproj")
400-
let p = ProjectCracker.GetProjectOptionsFromProjectFile(f,[("Configuration","Debug")])
401-
let references = getReferencedFilenamesAndContainingFolders p.OtherOptions |> set
399+
let projectFileName = normalizePath(__SOURCE_DIRECTORY__ + @"/data/Test2.fsproj")
400+
let opts = ProjectCracker.GetProjectOptionsFromProjectFile(projectFileName,[("Configuration","Debug")])
401+
let references = getReferencedFilenamesAndContainingFolders opts.OtherOptions |> set
402402
// Check the reference is to a debug DLL
403403
references |> should contain ("Test1.dll", "Debug")
404404

405405
[<Test>]
406406
let ``Project file parsing -- space in file name``() =
407-
let p = ProjectCracker.GetProjectOptionsFromProjectFile(__SOURCE_DIRECTORY__ + @"/data/Space in name.fsproj")
408-
409-
p.OtherOptions
410-
|> getCompiledFilenames
411-
|> set
412-
|> should equal (set [ "Test2File1.fs"; "Test2File2.fs" ])
407+
let opts = ProjectCracker.GetProjectOptionsFromProjectFile(__SOURCE_DIRECTORY__ + @"/data/Space in name.fsproj")
408+
CollectionAssert.AreEqual (["Test2File2.fs"; "Test2File1.fs"], opts.ProjectFileNames |> Array.map Path.GetFileName)
409+
CollectionAssert.IsEmpty (getCompiledFilenames opts.OtherOptions)
413410

414411
[<Test>]
415412
let ``Project file parsing -- report files``() =

0 commit comments

Comments
 (0)