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
- آخر نشاط في المصدر
- ٤ مايو ٢٠٢٦ في ٠٣:٣٣
- لغة SKILL.md المكتشفة
- الإنجليزية
- النجوم
- ٨٦٩
- التفرعات
- ١١٣
خيارات التثبيت
يُحدَّد 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