Skip to main content

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에서 보기