Skip to content

Commit 73722c0

Browse files
authoredDec 21, 2024··
Fix input path directory handling #1842 (#2684)
1 parent a32ad2f commit 73722c0

File tree

5 files changed

+134
-140
lines changed

5 files changed

+134
-140
lines changed
 

‎docs/CHANGELOG-v3.md

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ What's changed since pre-release v3.0.0-B0351:
3838
- Bug fixes:
3939
- Fixed string formatting of semantic version and constraints by @BernieWhite.
4040
[#1828](https://github.com/microsoft/PSRule/issues/1828)
41+
- Fixed directory handling of input paths without trailing slash by @BernieWhite.
42+
[#1842](https://github.com/microsoft/PSRule/issues/1842)
4143

4244
## v3.0.0-B0351 (pre-release)
4345

‎src/PSRule/Pipeline/InputPathBuilder.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33

44
namespace PSRule.Pipeline;
55

6-
internal sealed class InputPathBuilder : PathBuilder
6+
/// <summary>
7+
/// A builder for input paths.
8+
/// </summary>
9+
internal sealed class InputPathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required)
10+
: PathBuilder(logger, basePath, searchPattern, filter, required)
711
{
8-
public InputPathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required)
9-
: base(logger, basePath, searchPattern, filter, required) { }
1012
}

‎src/PSRule/Pipeline/PathBuilder.cs

+27-39
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,7 @@
77

88
namespace PSRule.Pipeline;
99

10-
//public interface IPathBuilder
11-
//{
12-
// void Add(string path);
13-
14-
// void Add(FileInfo[] fileInfo);
15-
16-
// void Add(PathInfo[] pathInfo);
17-
18-
// InputFileInfo[] Build();
19-
//}
20-
21-
internal abstract class PathBuilder
10+
internal abstract class PathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required)
2211
{
2312
// Path separators
2413
private const char Slash = '/';
@@ -28,27 +17,16 @@ internal abstract class PathBuilder
2817
private const string CurrentPath = ".";
2918
private const string RecursiveSearchOperator = "**";
3019

31-
private static readonly char[] PathLiteralStopCharacters = new char[] { '*', '[', '?' };
32-
private static readonly char[] PathSeparatorCharacters = new char[] { '\\', '/' };
20+
private static readonly char[] PathLiteralStopCharacters = ['*', '[', '?'];
21+
private static readonly char[] PathSeparatorCharacters = ['\\', '/'];
3322

34-
private readonly IPipelineWriter _Logger;
35-
private readonly List<InputFileInfo> _Files;
36-
private readonly HashSet<string> _Paths;
37-
private readonly string _BasePath;
38-
private readonly string _DefaultSearchPattern;
39-
private readonly PathFilter _GlobalFilter;
40-
private readonly PathFilter _Required;
41-
42-
protected PathBuilder(IPipelineWriter logger, string basePath, string searchPattern, PathFilter filter, PathFilter required)
43-
{
44-
_Logger = logger;
45-
_Files = [];
46-
_Paths = [];
47-
_BasePath = NormalizePath(Environment.GetRootedBasePath(basePath));
48-
_DefaultSearchPattern = searchPattern;
49-
_GlobalFilter = filter;
50-
_Required = required;
51-
}
23+
private readonly IPipelineWriter _Logger = logger;
24+
private readonly List<InputFileInfo> _Files = [];
25+
private readonly HashSet<string> _Paths = [];
26+
private readonly string _BasePath = NormalizePath(Environment.GetRootedBasePath(basePath));
27+
private readonly string _DefaultSearchPattern = searchPattern;
28+
private readonly PathFilter _GlobalFilter = filter;
29+
private readonly PathFilter _Required = required;
5230

5331
/// <summary>
5432
/// The number of files found.
@@ -89,7 +67,7 @@ public InputFileInfo[] Build()
8967
{
9068
try
9169
{
92-
return _Files.ToArray();
70+
return [.. _Files];
9371
}
9472
finally
9573
{
@@ -105,9 +83,14 @@ private void FindFiles(string path)
10583

10684
var pathLiteral = GetSearchParameters(path, out var searchPattern, out var searchOption, out var filter);
10785
var files = Directory.EnumerateFiles(pathLiteral, searchPattern, searchOption);
86+
10887
foreach (var file in files)
88+
{
10989
if (ShouldInclude(file, filter))
90+
{
11091
AddFile(file);
92+
}
93+
}
11194
}
11295

11396
private bool TryUrl(string path)
@@ -128,9 +111,7 @@ private bool TryPath(string path, out string normalPath)
128111
var rootedPath = GetRootedPath(path);
129112
if (Directory.Exists(rootedPath) || path == CurrentPath)
130113
{
131-
if (IsBasePath(rootedPath))
132-
normalPath = CurrentPath;
133-
114+
normalPath = IsBasePath(rootedPath) ? CurrentPath : NormalizeDirectoryPath(path);
134115
return false;
135116
}
136117
if (!File.Exists(rootedPath))
@@ -144,8 +125,7 @@ private bool TryPath(string path, out string normalPath)
144125

145126
private bool IsBasePath(string path)
146127
{
147-
path = IsSeparator(path[path.Length - 1]) ? path : string.Concat(path, Path.DirectorySeparatorChar);
148-
return NormalizePath(path) == _BasePath;
128+
return NormalizeDirectoryPath(path) == _BasePath;
149129
}
150130

151131
private void ErrorNotFound(string path)
@@ -251,12 +231,20 @@ private static bool IsSeparator(char c)
251231
[DebuggerStepThrough]
252232
private static bool UseSimpleSearch(string s)
253233
{
254-
return s.IndexOf(RecursiveSearchOperator, System.StringComparison.OrdinalIgnoreCase) == -1;
234+
return s.IndexOf(RecursiveSearchOperator, StringComparison.OrdinalIgnoreCase) == -1;
255235
}
256236

257237
[DebuggerStepThrough]
258238
private static string NormalizePath(string path)
259239
{
260240
return path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
261241
}
242+
243+
[DebuggerStepThrough]
244+
private static string NormalizeDirectoryPath(string path)
245+
{
246+
return NormalizePath(
247+
IsSeparator(path[path.Length - 1]) ? path : string.Concat(path, Path.DirectorySeparatorChar)
248+
);
249+
}
262250
}

‎tests/PSRule.Tests/InputPathBuilderTests.cs

-98
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using System.IO;
6+
using System.Linq;
7+
8+
namespace PSRule.Pipeline;
9+
10+
/// <summary>
11+
/// Tests for <see cref="InputPathBuilder"/>.
12+
/// </summary>
13+
public sealed class InputPathBuilderTests
14+
{
15+
[Theory]
16+
[InlineData("./.github/*.yml", 1)]
17+
[InlineData("./.github/**/*.yaml", 9)]
18+
[InlineData("./.github/", 12)]
19+
[InlineData(".github/", 12)]
20+
[InlineData(".github", 12)]
21+
[InlineData("./*.json", 8)]
22+
public void Build_WithValidPathAdded_ShouldReturnFiles(string path, int expected)
23+
{
24+
var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, null);
25+
builder.Add(path);
26+
var actual = builder.Build();
27+
28+
Assert.Equal(expected, actual.Length);
29+
}
30+
31+
[Theory]
32+
[InlineData(".")]
33+
[InlineData("./")]
34+
[InlineData("./src")]
35+
[InlineData("./src/")]
36+
[InlineData("src/")]
37+
public void Build_WithValidPathAdded_ShouldReturnManyFiles(string path)
38+
{
39+
var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, null);
40+
builder.Add(path);
41+
var actual = builder.Build();
42+
43+
Assert.True(actual.Length > 100);
44+
}
45+
46+
[Fact]
47+
public void Build_WithWorkingPathAdded_ShouldReturnFiles()
48+
{
49+
var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, null);
50+
builder.Add(GetWorkingPath());
51+
var actual = builder.Build();
52+
53+
Assert.True(actual.Length > 100);
54+
}
55+
56+
/// <summary>
57+
/// Test that an invalid path is handled correctly.
58+
/// Should not return any files, and should log an error.
59+
/// </summary>
60+
[Fact]
61+
public void Build_WithInvalidPathAdded_ShouldReturnEmpty()
62+
{
63+
var writer = new TestWriter(new Configuration.PSRuleOption());
64+
var builder = new InputPathBuilder(writer, GetWorkingPath(), "*", null, null);
65+
builder.Add("ZZ://not/path");
66+
var actual = builder.Build();
67+
68+
Assert.Empty(actual);
69+
Assert.True(writer.Errors.Count(r => r.FullyQualifiedErrorId == "PSRule.ReadInputFailed") == 1);
70+
}
71+
72+
[Fact]
73+
public void GetPathRequired()
74+
{
75+
var required = PathFilter.Create(GetWorkingPath(), ["README.md"]);
76+
var builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, required);
77+
builder.Add(".");
78+
var actual = builder.Build();
79+
Assert.True(actual.Length == 1);
80+
81+
builder.Add(GetWorkingPath());
82+
actual = builder.Build();
83+
Assert.True(actual.Length == 1);
84+
85+
required = PathFilter.Create(GetWorkingPath(), ["**"]);
86+
builder = new InputPathBuilder(null, GetWorkingPath(), "*", null, required);
87+
builder.Add(".");
88+
actual = builder.Build();
89+
Assert.True(actual.Length > 100);
90+
}
91+
92+
#region Helper methods
93+
94+
private static string GetWorkingPath()
95+
{
96+
return Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../../.."));
97+
}
98+
99+
#endregion Helper methods
100+
}

0 commit comments

Comments
 (0)
Please sign in to comment.