-
-
Notifications
You must be signed in to change notification settings - Fork 138
Description
Problem Summary
When a .csharpierignore file exists in a subdirectory, it completely overrides the patterns defined in the root .csharpierignore for all files within that subdirectory.
This behavior breaks common monorepo and multi-project workflows where you want:
- To exclude a subproject directory when formatting from the repository root.
- To include files when formatting from within that subproject directory.
Current Behavior
The current implementation in IgnoreFile.cs
(lines 112–118 @ commit 019c82e) searches for .csharpierignore files ascending from the base directory and uses only the first one found:
if (foundCSharpierIgnoreFilePath is null)
{
var csharpierIgnoreFilePath = fileSystem.Path.Combine(
directoryInfo.FullName,
".csharpierignore"
);
if (fileSystem.File.Exists(csharpierIgnoreFilePath))
{
foundCSharpierIgnoreFilePath = csharpierIgnoreFilePath;
}
}This means that once a .csharpierignore is found in a subdirectory, all parent ignore rules are ignored for files under that directory.
Example Scenario
Project structure
/project-root/
├── .csharpierignore # Root ignore file
├── src/
│ ├── MainProject/
│ └── Mobile/ # Subproject with different tooling
│ ├── .csharpierignore # Subproject-specific ignore file
│ └── *.cs files
Root .csharpierignore
src/Mobile/**src/Mobile/.csharpierignore
**/*.Designer.csExpected Behavior
-
dotnet csharpier format .from project root- 🚫
src/Mobile/**should be completely ignored
- 🚫
-
dotnet csharpier format .from src/Mobile/- ✅ Mobile files should be formatted
- 🚫
*.Designer.csfiles should be ignored
Actual Behavior
-
Running from root:
- ❌ Mobile files are formatted
(becausesrc/Mobile/.csharpierignoretakes precedence and does not ignore the directory)
- ❌ Mobile files are formatted
-
Running from
src/Mobile/:- ✅ Behavior is correct
Root Cause
In IgnoreFile.cs
(lines 102–148 @ commit 019c82e), the FindIgnorePaths logic is:
- Find the first (closest)
.csharpierignoreascending from the directory - Find all
.gitignorefiles ascending until.git/
This results in:
- Only one
.csharpierignorebeing evaluated per file - Parent
.csharpierignorefiles being completely ignored when a child file exists
Proposed Solution (Preferred)
Invert the priority order to match how most ignore systems behave
(ESLint, Prettier, EditorConfig, .gitignore):
- Start with root
.csharpierignorepatterns - Layer more specific patterns from subdirectory
.csharpierignorefiles - Allow negation (
!) in child files to override parent rules
Example:
# Root .csharpierignore
src/Mobile/**# src/Mobile/.csharpierignore
!**/*
**/*.Designer.csThis would allow directory-level re-enabling while keeping exclusions explicit.
Alternative Solution (Less Breaking)
If inverting priority is too disruptive:
- Always evaluate all
.csharpierignorefiles in the ancestor path - Apply patterns from root to leaf
- Allow negation patterns to override parent patterns
This would align .csharpierignore behavior with how .gitignore is already handled internally.
Current Workaround
The only available workaround today is to:
- ❌ Avoid using subdirectory
.csharpierignorefiles entirely - ❌ Put formatting rules into
.gitignoreinstead
This is suboptimal because it mixes version control concerns with formatting concerns.
Related Code References
-
Only first
.csharpierignoreis discovered
csharpier/Src/CSharpier.Cli/IgnoreFile.cs
Lines 112 to 118 in 019c82e
); if (fileSystem.File.Exists(csharpierIgnoreFilePath)) { foundCSharpierIgnoreFilePath = csharpierIgnoreFilePath; } } -
FindIgnorePathsimplementation
csharpier/Src/CSharpier.Cli/IgnoreFile.cs
Lines 102 to 148 in 019c82e
string? foundCSharpierIgnoreFilePath = null; var directoryInfo = fileSystem.DirectoryInfo.New(baseDirectoryPath); var includeGitIgnores = true; while (directoryInfo != null) { if (foundCSharpierIgnoreFilePath is null) { var csharpierIgnoreFilePath = fileSystem.Path.Combine( directoryInfo.FullName, ".csharpierignore" ); if (fileSystem.File.Exists(csharpierIgnoreFilePath)) { foundCSharpierIgnoreFilePath = csharpierIgnoreFilePath; } } if (includeGitIgnores) { var gitIgnoreFilePath = fileSystem.Path.Combine( directoryInfo.FullName, ".gitignore" ); if (fileSystem.File.Exists(gitIgnoreFilePath)) { result.Add(gitIgnoreFilePath); } } if (fileSystem.Directory.Exists(Path.Combine(directoryInfo.FullName, ".git"))) { includeGitIgnores = false; } directoryInfo = directoryInfo.Parent; } if (foundCSharpierIgnoreFilePath is not null) { result.Insert(0, foundCSharpierIgnoreFilePath); } return result; } } internal class InvalidIgnoreFileException(string message, Exception exception) -
Ignore evaluation logic
csharpier/Src/CSharpier.Cli/IgnoreFile.cs
Lines 17 to 29 in 019c82e
{ filePath = filePath.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); foreach (var ignore in this.Ignores) { // when using one of the ignore files to determine if a given file is ignored or not // we can only consider that file if it actually has a matching rule for the filePath var (hasMatchingRule, isIgnored) = ignore.IsIgnored(filePath); if (hasMatchingRule) { return isIgnored; } }
Impact
This affects:
- Monorepos with multiple projects
- Solutions with subprojects using different tooling
- Any setup where ignore behavior should depend on invocation context