use-fullpath
Ensure .NET projects use `Meziantou.Framework.FullPath` for local file path manipulation instead of raw strings. Use when: reviewing or writing code that constructs, combines, compares, or passes around local file/directory paths as strings in .NET/C# projects.
소스 정보
- 저장소
- meziantou/Meziantou.Framework
- 최근 소스 활동
- 2026년 5월 4일 03:33
- 감지된 SKILL.md 언어
- 영어
- 스타
- 868
- 포크
- 113
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
SKILL.md 표시 중
SKILL.md
소스 지침 · 읽기 전용 미리보기- name
- use-fullpath
- description
- Ensure .NET projects use `Meziantou.Framework.FullPath` for local file path manipulation instead of raw strings. Use when: reviewing or writing code that constructs, combines, compares, or passes around local file/directory paths as strings in .NET/C# projects.
# Use FullPath for Local Path Manipulation
Use this skill when reviewing or writing .NET code that manipulates local file or directory paths.
## Why
Raw `string` paths are a common source of bugs:
- A path can be absolute or relative (`c:\a\b` vs `.\a\b`)
- A path that looks absolute can actually be relative (`c:a` is relative)
- Paths may contain `.` or `..` segments that aren't resolved
- Trailing directory separators cause inconsistent comparisons (`c:\a` vs `c:\a\`)
- Path comparison must be case-insensitive on Windows and case-sensitive on Linux
- Reserved device names on Windows (`CON`, `PRN`, `NUL`) can cause subtle failures
- When a `FileNotFoundException` occurs, relative paths make debugging difficult
- A `string` parameter gives no signal whether a full path or a relative path is expected
The `FullPath` struct from `Meziantou.Framework.FullPath` solves these problems by guaranteeing the stored value is always a fully-resolved absolute path with normalized separators.
## Installation
```shell
# .NET 10+
dotnet package add Meziantou.Framework.FullPath
# .NET 9 and earlier
dotnet add package Meziantou.Framework.FullPath
```
The namespace is `Meziantou.Framework`.
## Core Patterns
### Creating a FullPath
```csharp
// From a string (relative paths are resolved against the current directory)
FullPath path = FullPath.FromPath("demo");
// Well-known locations
FullPath temp = FullPath.GetTempPath();
FullPath cwd = FullPath.CurrentDirectory();
FullPath docs = FullPath.GetFolderPath(Environment.SpecialFolder.MyDocuments);
```
### Combining Paths
Use the `/` operator or `FullPath.Combine` — never `string.Concat` or string interpolation.
```csharp
FullPath root = FullPath.FromPath("/repo");
// / operator (preferred for readability)
FullPath file = root / "src" / "Program.cs";
// Combine (useful when the number of segments is dynamic)
FullPath file2 = FullPath.Combine(root, "src", "Program.cs");
```
### Comparing Paths
Comparisons are case-insensitive on Windows/macOS and case-sensitive on Linux by default. Navigation segments and trailing separators are already resolved, so equality just works.
```csharp
// == and != use the OS-default comparison
if (pathA == pathB) { /* same file */ }
// Explicit case-sensitivity control
bool equal = pathA.Equals(pathB, ignoreCase: false);
```
### Navigating the Path Tree
```csharp
FullPath file = FullPath.FromPath("/repo/src/Program.cs");
FullPath dir = file.Parent; // /repo/src
string name = file.Name; // Program.cs
string nameNoExt = file.NameWithoutExtension; // Program
string ext = file.Extension; // .cs
FullPath renamed = file.ChangeExtension(".vb"); // /repo/src/Program.vb
```
### Checking Hierarchy
```csharp
FullPath root = FullPath.FromPath("/repo");
FullPath file = root / "src" / "Program.cs";
bool isChild = file.IsChildOf(root); // true
```
### Making a Relative Path
```csharp
FullPath root = FullPath.FromPath("/repo");
FullPath file = root / "src" / "Program.cs";
string relative = file.MakePathRelativeTo(root); // src/Program.cs (or src\Program.cs on Windows)
```
### Walking Up the Tree
```csharp
FullPath start = FullPath.FromPath("/repo/src/deep/nested");
// Find the first ancestor (or self) matching a predicate
if (start.TryFindFirstAncestorOrSelf(
p => File.Exists(p / ".editorconfig"), out FullPath match))
{
// match is the closest directory containing .editorconfig
}
// Shortcut: find the Git repository root
if (start.TryFindGitRepositoryRoot(out FullPath gitRoot))
{
// gitRoot is the closest directory containing .git
}
```
### Interop with System.IO
`FullPath` has an implicit conversion to `string`, so it works directly with `File`, `Directory`, and most APIs that accept a string path.
```csharp
FullPath config = root / "appsettings.json";
// No .Value or .ToString() needed
string json = File.ReadAllText(config);
File.WriteAllText(config, json);
Directory.CreateDirectory(config.Parent);
```
When you need the raw string explicitly (e.g., for logging or interpolation):
```csharp
logger.LogInformation("Loading config from {Path}", config.Value);
```
### Ensuring the Parent Directory Exists
```csharp
FullPath output = root / "artifacts" / "report.html";
output.CreateParentDirectory(); // creates "artifacts" if it doesn't exist
File.WriteAllText(output, html);
```
### JSON Serialization
`FullPath` has a built-in `System.Text.Json` converter. It serializes to a plain JSON string and deserializes back through `FullPath.FromPath`.
```csharp
public sealed class AppSettings
{
public FullPath OutputDirectory { get; set; }
}
// { "OutputDirectory": "C:\\repo\\output" }
var settings = JsonSerializer.Deserialize<AppSettings>(json);
```
### Using FullPath in Method Signatures
Prefer `FullPath` over `string` in APIs that expect an absolute path. This makes intent explicit at the type level.
```csharp
// ❌ Ambiguous — is this a full path or a relative fragment?
void Export(string outputPath) { }
// ✅ Clear contract — the caller must provide a resolved path
void Export(FullPath outputPath) { }
```
### Collections and Dictionaries
Use `FullPathComparer` when storing paths in sets or dictionaries to get correct OS-aware equality.
```csharp
var seen = new HashSet<FullPath>(); // uses default OS comparison
var map = new Dictionary<FullPath, int>();
// Explicit comparer if needed
var caseSensitiveSet = new HashSet<FullPath>(FullPathComparer.CaseSensitive);
```
## What to Look For in Reviews
| Smell | Refactor to |
|---|---|
| `string filePath = Path.Combine(root, "sub", "file.txt");` | `FullPath filePath = root / "sub" / "file.txt";` |
| `string fullPath = Path.GetFullPath(relative);` | `FullPath fullPath = FullPath.FromPath(relative);` |
| `if (path1.Equals(path2, StringComparison.OrdinalIgnoreCase))` | `if (path1 == path2)` (with `FullPath`) |
| `path.StartsWith(root)` to check containment | `path.IsChildOf(root)` |
| `Path.GetDirectoryName(path)` | `path.Parent` |
| `Path.GetFileName(path)` | `path.Name` |
| `Path.GetExtension(path)` | `path.Extension` |
| `Path.ChangeExtension(path, ext)` | `path.ChangeExtension(ext)` |
| `Directory.CreateDirectory(Path.GetDirectoryName(path))` | `path.CreateParentDirectory()` |
| `void Foo(string path)` for an absolute-path parameter | `void Foo(FullPath path)` |
## Scope
This skill targets **local file-system paths** only. It does not apply to:
- URLs or URIs
- Cloud/blob storage paths
- Database connection strings
- Paths that must remain relative by design (e.g., entries inside a ZIP archive)
GitHub에서 보기