Safe Cross-Platform Path Manipulation in Dart Contents 1. Core Principles & Cross-Platform Rules 2. Recommended package:path Idioms vs. String Anti-Patterns 3. Bridging Native Paths to POSIX, Git, & URL Contexts 4. Mockable File Systems ( vs. Global ) 5. Extensions, Compound Extensions & Stem Extraction 6. Workflows & Audit Checklist References & Examples --- 1. Core Principles & Cross-Platform Rules Avoid Treating File Paths as Raw Strings Native file paths on Windows use backslashes ( ), whereas macOS and Linux use forward slashes ( ). String operations like , , or silently fail on Windows…

)`) *before* passing the file path to `package:path`.\n* **URI Boundary Conversions**: When converting between file paths and `Uri` objects, always use `p.toUri(path)` and `p.fromUri(uri)` rather than `Uri.parse(path)` or manual string concatenation.\n\n---\n\n## 2. Recommended package:path Idioms vs. String Anti-Patterns\n\n### Path Joining\n* **Prefer**: `p.join(dir, file)`\n* **Avoid**: `'$dir/$file'` or `'a/$b'`\n* **Why**: String interpolation injects `/` on Windows and creates duplicate\n slashes (`//`) when `$dir` ends with a trailing separator.\n\n### Segment Matching\n* **Prefer**: `p.split(path).contains('foo')`\n* **Avoid**: `path.contains('foo/')`\n* **Why**: String matching fails on Windows backslashes (`foo\\bar`) and produces\n false positives on partial substring names (e.g. `barfoo/`).\n\n### Root and Directory Prefixes\n* **Prefer**: `if (p.split(path) case ['foo', ...])` (or `case ['foo', ...final rest]` when extracting tail segments), or `p.isWithin('foo', path)`\n* **Avoid**: `path.startsWith('foo/')` or `p.split(path).first == 'foo'`\n* **Why**: String prefix matching fails on Windows separators (`foo\\bar`). Calling\n `p.split(path).first` throws a `StateError` on empty lists and requires\n separate `.skip(1)` slicing, whereas list patterns safely check non-emptiness,\n match multi-segment prefixes, and optionally bind `...final rest` in a single\n step. When unnormalized relative prefixes like `./foo/bar` may appear, use\n `p.isWithin('foo', path)` (or `p.split(p.normalize(path))`).\n\n### File Extensions\n* **Prefer**: `p.extension(path) == '.wasm'`\n* **Avoid**: `path.endsWith('.wasm')`\n* **Why**: Substring suffix matching falsely matches directories (`foo.wasm/`)\n or non-extension suffixes.\n\n### Extension Slicing and Compound Extensions\n* **Prefer**: `p.withoutExtension(path)` and `p.extension(path, 2)`\n* **Avoid**: `path.lastIndexOf('.')` and manual `substring` slicing\n* **Why**: Manual arithmetic breaks on hidden dotfiles (`.gitignore`) and\n compound extensions (`.js.map`, `.tar.gz`).\n\n### POSIX and URL Path Conversion\n* **Prefer**: `p.posix.joinAll(p.split(path))` or `p.url.joinAll(p.split(path))`\n* **Avoid**: `path.replaceAll(r'\\', '/')`\n* **Why**: Ad-hoc separator replacement fails on root drives and mixes OS\n context with POSIX or URL targets.\n\n### URI Conversion\n* **Prefer**: `p.toUri(path)` and `p.fromUri(uri)`\n* **Avoid**: `Uri.parse(path)` and `uri.path`\n* **Why**: Direct URI parsing fails on Windows drive letters (`C:`) and leaks\n percent-encoding (e.g. `%20` for spaces).\n\n### Directory Basename Helper\n* **Prefer**:\n `String canonicalDirName(Directory d) => p.basename(p.normalize(d.absolute.path));`\n* **Avoid**: Repeating `p.basename(p.normalize(dir.absolute.path))` inline\n across files.\n* **Why**: Centralizes canonical directory naming logic and reduces boilerplate.\n\n---\n\n## 3. Bridging Native Paths to POSIX, Git, & URL Contexts\n\nAvoid calling `.replaceAll('\\\\', '/')` or `.replaceAll(r'\\', '/')` to convert\nOS-native paths into POSIX paths (for Git, YAML, archive manifests) or URL\nsegments.\n\n**Rule**: Split the relative native path using `p.split(...)`, inspect segments\nwith **Dart 3 list pattern matching**, and join using `p.posix.joinAll(...)` or\n`p.url.joinAll(...)`. Always call `p.relative(filePath, from: root)` first so\nleading root segments (`'/'` on POSIX or `r'C:\\'` on Windows) do not interfere\nwith relative prefix patterns:\n\n```dart\nimport 'package:path/path.dart' as p;\n\nString computeWebAssetKey(String filePath, String projectRoot) {\n final relative = p.relative(filePath, from: projectRoot);\n final segments = p.split(relative);\n return switch (segments) {\n ['assets', ...] => p.posix.joinAll(segments),\n _ => p.posix.joinAll(['assets', ...segments]),\n };\n}\n```\n\n### Git Paths and Repository Metadata\n* Git repository tree objects, `.gitignore` pattern rules, `.gitattributes`,\n and git-tracked symlinks strictly use POSIX forward slashes (`/`), even on\n Windows.\n* Inserting native Windows backslashes (`\\`) into `.gitignore` or git commands\n causes Git to treat `\\` as an escape character rather than a directory\n separator, silently breaking pattern matching.\n* When generating `.gitignore` entries, repository manifests, or symlink\n targets programmatically from native file paths, convert the relative native\n path using `p.posix.joinAll(p.split(relativePath))` or `p.posix.join(...)`.\n\n---\n\n## 4. Mockable File Systems (`package:file` vs. Global `p.*`)\n\nIn codebases that use `package:file` (e.g., CLI applications or services tested\nwith `MemoryFileSystem`), avoid calling top-level `p.*` functions on `File` or\n`Directory` paths.\n\n* Top-level `p.*` functions bind to the *host operating system* running the test.\n* If a unit test creates a `MemoryFileSystem(style: FileSystemStyle.windows)` on a Linux or macOS runner, global `p.split(file.path)` will split on `/` instead of `\\`, breaking the test.\n\n**Rule**: Always use the `Context` attached to the `FileSystem` (`file.fileSystem.path`):\n\n```dart\nimport 'package:file/file.dart';\n\nList\u003cString> listSubdirectoryNames(Directory dir) {\n final pathContext = dir.fileSystem.path;\n return dir\n .listSync()\n .whereType\u003cDirectory>()\n .map((d) => pathContext.basename(d.path))\n .toList();\n}\n```\n\n---\n\n## 5. Extensions, Compound Extensions & Stem Extraction\n\nAvoid manual `.lastIndexOf('.')` and `.substring()` arithmetic when extracting file extensions or inserting content hashes. `p.extension` natively supports multi-level extensions via its optional `level` parameter.\n\n* **Multi-Dot Stem Nuance**: Calling `p.extension('main.dart.wasm', 2)` returns `'.dart.wasm'` because it blindly captures the last two dot-separated segments. When hashing or stripping extensions on files that may have multi-dot stems (e.g., `main.dart.wasm` vs. `main.dart.js.map`), check whether `p.extension(filename, 2)` matches a known compound extension (or `.endsWith('.map')`) before falling back to single-level `p.extension(filename)`:\n\n```dart\nimport 'package:path/path.dart' as p;\n\nString insertContentHash(String filename, String hash) {\n final compoundExt = p.extension(filename, 2);\n // Only use the 2-level extension for true compound suffixes (e.g., '.js.map')\n final ext = compoundExt.endsWith('.map')\n ? compoundExt\n : p.extension(filename);\n final stem = filename.substring(0, filename.length - ext.length);\n return '$stem.$hash$ext';\n}\n```\n\n---\n\n## 6. Workflows & Audit Checklist\n\n### Path Refactoring Checklist\n- [ ] Replace string interpolation (`'$dir/$file'`) with `p.join(dir, file)`.\n- [ ] Replace `.contains('dir/')` and `.startsWith('dir/')` with `p.split(path)` list pattern checks (`case ['dir', ...final rest]`) or `p.isWithin(parent, child)`.\n- [ ] Replace `.replaceAll(r'\\', '/')` with `p.posix.joinAll(p.split(path))` (or `p.url.joinAll`).\n- [ ] Replace `.endsWith('.ext')` on file paths with `p.extension(path) == '.ext'`.\n- [ ] Replace manual dot-index slicing with `p.withoutExtension(path)` and `p.extension(path, [level])`.\n- [ ] Verify that code using `package:file` accesses `fileSystem.path` instead of global `p.*`.\n- [ ] Ensure Git paths, `.gitignore` entries, and symlink targets use `p.posix` forward slashes.\n\n---\n\n## References & Examples\n\n* **Cross-Platform Path & POSIX Conversion Examples**: [examples/cross_platform_paths.dart](examples/cross_platform_paths.dart)\n* **Mockable FileSystem Path Context Example**: [examples/file_system_context.dart](examples/file_system_context.dart)","attachment_filenames":[],"attachments":[],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Safe Cross-Platform Path Manipulation in Dart","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Contents","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"1. Core Principles & Cross-Platform Rules","type":"text","marks":[{"type":"link","attrs":{"href":"#1-core-principles--cross-platform-rules","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"2. Recommended package:path Idioms vs. String Anti-Patterns","type":"text","marks":[{"type":"link","attrs":{"href":"#2-recommended-packagepath-idioms-vs-string-anti-patterns","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"3. Bridging Native Paths to POSIX, Git, & URL Contexts","type":"text","marks":[{"type":"link","attrs":{"href":"#3-bridging-native-paths-to-posix-git--url-contexts","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"4. Mockable File Systems (","type":"text","marks":[{"type":"link","attrs":{"href":"#4-mockable-file-systems-packagefile-vs-global-p","title":null}}]},{"text":"package:file","type":"text","marks":[{"type":"link","attrs":{"href":"#4-mockable-file-systems-packagefile-vs-global-p","title":null}},{"type":"code_inline"}]},{"text":" vs. Global ","type":"text","marks":[{"type":"link","attrs":{"href":"#4-mockable-file-systems-packagefile-vs-global-p","title":null}}]},{"text":"p.*","type":"text","marks":[{"type":"link","attrs":{"href":"#4-mockable-file-systems-packagefile-vs-global-p","title":null}},{"type":"code_inline"}]},{"text":")","type":"text","marks":[{"type":"link","attrs":{"href":"#4-mockable-file-systems-packagefile-vs-global-p","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"5. Extensions, Compound Extensions & Stem Extraction","type":"text","marks":[{"type":"link","attrs":{"href":"#5-extensions-compound-extensions--stem-extraction","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"6. Workflows & Audit Checklist","type":"text","marks":[{"type":"link","attrs":{"href":"#6-workflows--audit-checklist","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"References & Examples","type":"text","marks":[{"type":"link","attrs":{"href":"#references--examples","title":null}}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"1. Core Principles & Cross-Platform Rules","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Avoid Treating File Paths as Raw Strings","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Native file paths on Windows use backslashes (","type":"text"},{"text":"\\","type":"text","marks":[{"type":"code_inline"}]},{"text":"), whereas macOS and Linux use forward slashes (","type":"text"},{"text":"/","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"String operations like ","type":"text"},{"text":".contains('foo/')","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".startsWith('foo/')","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":".split('/')","type":"text","marks":[{"type":"code_inline"}]},{"text":" silently fail on Windows native paths.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"String interpolation like ","type":"text"},{"text":"'$dir/$file'","type":"text","marks":[{"type":"code_inline"}]},{"text":" injects forward slashes on Windows and produces duplicate slashes (","type":"text"},{"text":"//","type":"text","marks":[{"type":"code_inline"}]},{"text":") when ","type":"text"},{"text":"$dir","type":"text","marks":[{"type":"code_inline"}]},{"text":" ends with a trailing slash.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Rule","type":"text","marks":[{"type":"strong"}]},{"text":": Always decompose paths into segments using ","type":"text"},{"text":"p.split(path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" before inspecting directory hierarchy or segment names, and always join path components using ","type":"text"},{"text":"p.join(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Pragmatic Boundary Joining vs. Multi-Segment Decomposition (","type":"text"},{"text":"p.join","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cross-Platform Libraries (Windows + POSIX)","type":"text","marks":[{"type":"strong"}]},{"text":": Pass individual path segments to ","type":"text"},{"text":"p.join(dir, 'sub', 'file.json')","type":"text","marks":[{"type":"code_inline"}]},{"text":" so ","type":"text"},{"text":"package:path","type":"text","marks":[{"type":"code_inline"}]},{"text":" inserts OS-native separators (","type":"text"},{"text":"\\","type":"text","marks":[{"type":"code_inline"}]},{"text":" on Windows, ","type":"text"},{"text":"/","type":"text","marks":[{"type":"code_inline"}]},{"text":" on POSIX) between every component.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"POSIX-Only Tools & Static Subpath Greppability","type":"text","marks":[{"type":"strong"}]},{"text":": In codebases exclusively targeting Linux/macOS (or when joining a dynamic base path to a known static subpath), decomposing 5–6 static segments into separate arguments (","type":"text"},{"text":"p.join(home, '.local', 'share', 'app', 'bin', 'config.json')","type":"text","marks":[{"type":"code_inline"}]},{"text":") causes ","type":"text"},{"text":"dart format","type":"text","marks":[{"type":"code_inline"}]},{"text":" to wrap across 6–8 vertical lines and ","type":"text"},{"text":"destroys substring greppability","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"grep","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"code_search","type":"text","marks":[{"type":"code_inline"}]},{"text":" for ","type":"text"},{"text":".local/share/app/bin","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rule for POSIX Targets","type":"text","marks":[{"type":"strong"}]},{"text":": Prefer ","type":"text"},{"text":"2-argument boundary joining","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"p.join(home, '.local/share/app/bin/config.json')","type":"text","marks":[{"type":"code_inline"}]},{"text":"). This prevents duplicate-slash bugs (","type":"text"},{"text":"//","type":"text","marks":[{"type":"code_inline"}]},{"text":") at variable boundaries while preserving single-line readability and exact string searchability.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Normalization vs. Canonicalization (","type":"text"},{"text":"p.normalize","type":"text","marks":[{"type":"code_inline"}]},{"text":" vs. ","type":"text"},{"text":"p.canonicalize","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"p.normalize(path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" resolves ","type":"text"},{"text":".","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"..","type":"text","marks":[{"type":"code_inline"}]},{"text":" segments purely lexically without consulting the filesystem or standardizing case.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When deduplicating directory paths or comparing physical file identity across symlinks, relative roots, or case-insensitive filesystems, use ","type":"text"},{"text":"p.canonicalize(path)","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Strip Location Specifiers & Convert URIs Safely","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Strings formatted as ","type":"text"},{"text":"\u003cpath>:\u003cline>-\u003ccol>","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"\u003cpath>:\u003cline>","type":"text","marks":[{"type":"code_inline"}]},{"text":" are not pure file paths. Passing them directly to ","type":"text"},{"text":"p.normalize","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"Uri.parse","type":"text","marks":[{"type":"code_inline"}]},{"text":" causes bugs (on Windows, ","type":"text"},{"text":"Uri.parse","type":"text","marks":[{"type":"code_inline"}]},{"text":" mistakes ","type":"text"},{"text":"C:","type":"text","marks":[{"type":"code_inline"}]},{"text":" for a URI scheme and ","type":"text"},{"text":":line","type":"text","marks":[{"type":"code_inline"}]},{"text":" for a port).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract the trailing ","type":"text"},{"text":":line-col","type":"text","marks":[{"type":"code_inline"}]},{"text":" suffix via regular expression (","type":"text"},{"text":"RegExp(r'^(.*?):(\\d+(?:-\\d+)?)

Safe Cross-Platform Path Manipulation in Dart Contents 1. Core Principles & Cross-Platform Rules 2. Recommended package:path Idioms vs. String Anti-Patterns 3. Bridging Native Paths to POSIX, Git, & URL Contexts 4. Mockable File Systems ( vs. Global ) 5. Extensions, Compound Extensions & Stem Extraction 6. Workflows & Audit Checklist References & Examples --- 1. Core Principles & Cross-Platform Rules Avoid Treating File Paths as Raw Strings Native file paths on Windows use backslashes ( ), whereas macOS and Linux use forward slashes ( ). String operations like , , or silently fail on Windows…

)","type":"text","marks":[{"type":"code_inline"}]},{"text":") ","type":"text"},{"text":"before","type":"text","marks":[{"type":"em"}]},{"text":" passing the file path to ","type":"text"},{"text":"package:path","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"URI Boundary Conversions","type":"text","marks":[{"type":"strong"}]},{"text":": When converting between file paths and ","type":"text"},{"text":"Uri","type":"text","marks":[{"type":"code_inline"}]},{"text":" objects, always use ","type":"text"},{"text":"p.toUri(path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"p.fromUri(uri)","type":"text","marks":[{"type":"code_inline"}]},{"text":" rather than ","type":"text"},{"text":"Uri.parse(path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" or manual string concatenation.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"2. Recommended package:path Idioms vs. String Anti-Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Path Joining","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"p.join(dir, file)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"'$dir/$file'","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"'a/$b'","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Why","type":"text","marks":[{"type":"strong"}]},{"text":": String interpolation injects ","type":"text"},{"text":"/","type":"text","marks":[{"type":"code_inline"}]},{"text":" on Windows and creates duplicate slashes (","type":"text"},{"text":"//","type":"text","marks":[{"type":"code_inline"}]},{"text":") when ","type":"text"},{"text":"$dir","type":"text","marks":[{"type":"code_inline"}]},{"text":" ends with a trailing separator.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Segment Matching","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"p.split(path).contains('foo')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"path.contains('foo/')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Why","type":"text","marks":[{"type":"strong"}]},{"text":": String matching fails on Windows backslashes (","type":"text"},{"text":"foo\\bar","type":"text","marks":[{"type":"code_inline"}]},{"text":") and produces false positives on partial substring names (e.g. ","type":"text"},{"text":"barfoo/","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Root and Directory Prefixes","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"if (p.split(path) case ['foo', ...])","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"case ['foo', ...final rest]","type":"text","marks":[{"type":"code_inline"}]},{"text":" when extracting tail segments), or ","type":"text"},{"text":"p.isWithin('foo', path)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"path.startsWith('foo/')","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"p.split(path).first == 'foo'","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Why","type":"text","marks":[{"type":"strong"}]},{"text":": String prefix matching fails on Windows separators (","type":"text"},{"text":"foo\\bar","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Calling ","type":"text"},{"text":"p.split(path).first","type":"text","marks":[{"type":"code_inline"}]},{"text":" throws a ","type":"text"},{"text":"StateError","type":"text","marks":[{"type":"code_inline"}]},{"text":" on empty lists and requires separate ","type":"text"},{"text":".skip(1)","type":"text","marks":[{"type":"code_inline"}]},{"text":" slicing, whereas list patterns safely check non-emptiness, match multi-segment prefixes, and optionally bind ","type":"text"},{"text":"...final rest","type":"text","marks":[{"type":"code_inline"}]},{"text":" in a single step. When unnormalized relative prefixes like ","type":"text"},{"text":"./foo/bar","type":"text","marks":[{"type":"code_inline"}]},{"text":" may appear, use ","type":"text"},{"text":"p.isWithin('foo', path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"p.split(p.normalize(path))","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"File Extensions","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"p.extension(path) == '.wasm'","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"path.endsWith('.wasm')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Why","type":"text","marks":[{"type":"strong"}]},{"text":": Substring suffix matching falsely matches directories (","type":"text"},{"text":"foo.wasm/","type":"text","marks":[{"type":"code_inline"}]},{"text":") or non-extension suffixes.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Extension Slicing and Compound Extensions","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"p.withoutExtension(path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"p.extension(path, 2)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"path.lastIndexOf('.')","type":"text","marks":[{"type":"code_inline"}]},{"text":" and manual ","type":"text"},{"text":"substring","type":"text","marks":[{"type":"code_inline"}]},{"text":" slicing","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Why","type":"text","marks":[{"type":"strong"}]},{"text":": Manual arithmetic breaks on hidden dotfiles (","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":") and compound extensions (","type":"text"},{"text":".js.map","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".tar.gz","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"POSIX and URL Path Conversion","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"p.posix.joinAll(p.split(path))","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"p.url.joinAll(p.split(path))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"path.replaceAll(r'\\', '/')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Why","type":"text","marks":[{"type":"strong"}]},{"text":": Ad-hoc separator replacement fails on root drives and mixes OS context with POSIX or URL targets.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"URI Conversion","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"p.toUri(path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"p.fromUri(uri)","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"Uri.parse(path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"uri.path","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Why","type":"text","marks":[{"type":"strong"}]},{"text":": Direct URI parsing fails on Windows drive letters (","type":"text"},{"text":"C:","type":"text","marks":[{"type":"code_inline"}]},{"text":") and leaks percent-encoding (e.g. ","type":"text"},{"text":"%20","type":"text","marks":[{"type":"code_inline"}]},{"text":" for spaces).","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Directory Basename Helper","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"String canonicalDirName(Directory d) => p.basename(p.normalize(d.absolute.path));","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid","type":"text","marks":[{"type":"strong"}]},{"text":": Repeating ","type":"text"},{"text":"p.basename(p.normalize(dir.absolute.path))","type":"text","marks":[{"type":"code_inline"}]},{"text":" inline across files.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Why","type":"text","marks":[{"type":"strong"}]},{"text":": Centralizes canonical directory naming logic and reduces boilerplate.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"3. Bridging Native Paths to POSIX, Git, & URL Contexts","type":"text"}]},{"type":"paragraph","content":[{"text":"Avoid calling ","type":"text"},{"text":".replaceAll('\\\\', '/')","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":".replaceAll(r'\\', '/')","type":"text","marks":[{"type":"code_inline"}]},{"text":" to convert OS-native paths into POSIX paths (for Git, YAML, archive manifests) or URL segments.","type":"text"}]},{"type":"paragraph","content":[{"text":"Rule","type":"text","marks":[{"type":"strong"}]},{"text":": Split the relative native path using ","type":"text"},{"text":"p.split(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":", inspect segments with ","type":"text"},{"text":"Dart 3 list pattern matching","type":"text","marks":[{"type":"strong"}]},{"text":", and join using ","type":"text"},{"text":"p.posix.joinAll(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"p.url.joinAll(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":". Always call ","type":"text"},{"text":"p.relative(filePath, from: root)","type":"text","marks":[{"type":"code_inline"}]},{"text":" first so leading root segments (","type":"text"},{"text":"'/'","type":"text","marks":[{"type":"code_inline"}]},{"text":" on POSIX or ","type":"text"},{"text":"r'C:\\'","type":"text","marks":[{"type":"code_inline"}]},{"text":" on Windows) do not interfere with relative prefix patterns:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"dart"},"content":[{"text":"import 'package:path/path.dart' as p;\n\nString computeWebAssetKey(String filePath, String projectRoot) {\n final relative = p.relative(filePath, from: projectRoot);\n final segments = p.split(relative);\n return switch (segments) {\n ['assets', ...] => p.posix.joinAll(segments),\n _ => p.posix.joinAll(['assets', ...segments]),\n };\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Git Paths and Repository Metadata","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Git repository tree objects, ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":" pattern rules, ","type":"text"},{"text":".gitattributes","type":"text","marks":[{"type":"code_inline"}]},{"text":", and git-tracked symlinks strictly use POSIX forward slashes (","type":"text"},{"text":"/","type":"text","marks":[{"type":"code_inline"}]},{"text":"), even on Windows.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Inserting native Windows backslashes (","type":"text"},{"text":"\\","type":"text","marks":[{"type":"code_inline"}]},{"text":") into ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":" or git commands causes Git to treat ","type":"text"},{"text":"\\","type":"text","marks":[{"type":"code_inline"}]},{"text":" as an escape character rather than a directory separator, silently breaking pattern matching.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"When generating ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":" entries, repository manifests, or symlink targets programmatically from native file paths, convert the relative native path using ","type":"text"},{"text":"p.posix.joinAll(p.split(relativePath))","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"p.posix.join(...)","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"4. Mockable File Systems (","type":"text"},{"text":"package:file","type":"text","marks":[{"type":"code_inline"}]},{"text":" vs. Global ","type":"text"},{"text":"p.*","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"paragraph","content":[{"text":"In codebases that use ","type":"text"},{"text":"package:file","type":"text","marks":[{"type":"code_inline"}]},{"text":" (e.g., CLI applications or services tested with ","type":"text"},{"text":"MemoryFileSystem","type":"text","marks":[{"type":"code_inline"}]},{"text":"), avoid calling top-level ","type":"text"},{"text":"p.*","type":"text","marks":[{"type":"code_inline"}]},{"text":" functions on ","type":"text"},{"text":"File","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"Directory","type":"text","marks":[{"type":"code_inline"}]},{"text":" paths.","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Top-level ","type":"text"},{"text":"p.*","type":"text","marks":[{"type":"code_inline"}]},{"text":" functions bind to the ","type":"text"},{"text":"host operating system","type":"text","marks":[{"type":"em"}]},{"text":" running the test.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If a unit test creates a ","type":"text"},{"text":"MemoryFileSystem(style: FileSystemStyle.windows)","type":"text","marks":[{"type":"code_inline"}]},{"text":" on a Linux or macOS runner, global ","type":"text"},{"text":"p.split(file.path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" will split on ","type":"text"},{"text":"/","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead of ","type":"text"},{"text":"\\","type":"text","marks":[{"type":"code_inline"}]},{"text":", breaking the test.","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Rule","type":"text","marks":[{"type":"strong"}]},{"text":": Always use the ","type":"text"},{"text":"Context","type":"text","marks":[{"type":"code_inline"}]},{"text":" attached to the ","type":"text"},{"text":"FileSystem","type":"text","marks":[{"type":"code_inline"}]},{"text":" (","type":"text"},{"text":"file.fileSystem.path","type":"text","marks":[{"type":"code_inline"}]},{"text":"):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"dart"},"content":[{"text":"import 'package:file/file.dart';\n\nList\u003cString> listSubdirectoryNames(Directory dir) {\n final pathContext = dir.fileSystem.path;\n return dir\n .listSync()\n .whereType\u003cDirectory>()\n .map((d) => pathContext.basename(d.path))\n .toList();\n}","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"5. Extensions, Compound Extensions & Stem Extraction","type":"text"}]},{"type":"paragraph","content":[{"text":"Avoid manual ","type":"text"},{"text":".lastIndexOf('.')","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":".substring()","type":"text","marks":[{"type":"code_inline"}]},{"text":" arithmetic when extracting file extensions or inserting content hashes. ","type":"text"},{"text":"p.extension","type":"text","marks":[{"type":"code_inline"}]},{"text":" natively supports multi-level extensions via its optional ","type":"text"},{"text":"level","type":"text","marks":[{"type":"code_inline"}]},{"text":" parameter.","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Multi-Dot Stem Nuance","type":"text","marks":[{"type":"strong"}]},{"text":": Calling ","type":"text"},{"text":"p.extension('main.dart.wasm', 2)","type":"text","marks":[{"type":"code_inline"}]},{"text":" returns ","type":"text"},{"text":"'.dart.wasm'","type":"text","marks":[{"type":"code_inline"}]},{"text":" because it blindly captures the last two dot-separated segments. When hashing or stripping extensions on files that may have multi-dot stems (e.g., ","type":"text"},{"text":"main.dart.wasm","type":"text","marks":[{"type":"code_inline"}]},{"text":" vs. ","type":"text"},{"text":"main.dart.js.map","type":"text","marks":[{"type":"code_inline"}]},{"text":"), check whether ","type":"text"},{"text":"p.extension(filename, 2)","type":"text","marks":[{"type":"code_inline"}]},{"text":" matches a known compound extension (or ","type":"text"},{"text":".endsWith('.map')","type":"text","marks":[{"type":"code_inline"}]},{"text":") before falling back to single-level ","type":"text"},{"text":"p.extension(filename)","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"dart"},"content":[{"text":"import 'package:path/path.dart' as p;\n\nString insertContentHash(String filename, String hash) {\n final compoundExt = p.extension(filename, 2);\n // Only use the 2-level extension for true compound suffixes (e.g., '.js.map')\n final ext = compoundExt.endsWith('.map')\n ? compoundExt\n : p.extension(filename);\n final stem = filename.substring(0, filename.length - ext.length);\n return '$stem.$hash$ext';\n}","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"6. Workflows & Audit Checklist","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Path Refactoring Checklist","type":"text"}]},{"type":"checkbox_list","attrs":{"id":null},"content":[{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Replace string interpolation (","type":"text"},{"text":"'$dir/$file'","type":"text","marks":[{"type":"code_inline"}]},{"text":") with ","type":"text"},{"text":"p.join(dir, file)","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Replace ","type":"text"},{"text":".contains('dir/')","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":".startsWith('dir/')","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"p.split(path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" list pattern checks (","type":"text"},{"text":"case ['dir', ...final rest]","type":"text","marks":[{"type":"code_inline"}]},{"text":") or ","type":"text"},{"text":"p.isWithin(parent, child)","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Replace ","type":"text"},{"text":".replaceAll(r'\\', '/')","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"p.posix.joinAll(p.split(path))","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"p.url.joinAll","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Replace ","type":"text"},{"text":".endsWith('.ext')","type":"text","marks":[{"type":"code_inline"}]},{"text":" on file paths with ","type":"text"},{"text":"p.extension(path) == '.ext'","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Replace manual dot-index slicing with ","type":"text"},{"text":"p.withoutExtension(path)","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":"p.extension(path, [level])","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Verify that code using ","type":"text"},{"text":"package:file","type":"text","marks":[{"type":"code_inline"}]},{"text":" accesses ","type":"text"},{"text":"fileSystem.path","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead of global ","type":"text"},{"text":"p.*","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Ensure Git paths, ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":" entries, and symlink targets use ","type":"text"},{"text":"p.posix","type":"text","marks":[{"type":"code_inline"}]},{"text":" forward slashes.","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"References & Examples","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cross-Platform Path & POSIX Conversion Examples","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"examples/cross_platform_paths.dart","type":"text","marks":[{"type":"link","attrs":{"href":"examples/cross_platform_paths.dart","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mockable FileSystem Path Context Example","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"examples/file_system_context.dart","type":"text","marks":[{"type":"link","attrs":{"href":"examples/file_system_context.dart","title":null}}]}]}]}]}]},"metadata":{"date":"2026-11-02","name":"dart-use-path-package","author":"@skillopedia","source":{"stars":3141,"repo_name":"agent-plugins","origin_url":"https://github.com/flutter/agent-plugins/blob/HEAD/skills/dart-use-path-package/SKILL.md","repo_owner":"flutter","body_sha256":"edbc27c8764d7ae4d7dbb7d016c1ae80ab6da19c901d77de5cc6a20677f5340c","cluster_key":"450fd2f545a9739b1aac7b4aee08f0b54e789d6f5423db66379199210d8e86d8","clean_bundle":{"format":"clean-skill-bundle-v1","source":"flutter/agent-plugins/skills/dart-use-path-package/SKILL.md","text_bytes":0,"source_commit":"6f5b7b84846ebda117437f700d4be94cfb67efb6","skills_sh_rank":3362,"attachment_count":0,"text_attachments":0,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":"skills/dart-use-path-package/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"software-engineering","category_label":"Engineering"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"software-engineering","metadata":{"model":"models/gemini-3.1-pro-preview","last_modified":"Wed, 23 Sep 2026 22:10:00 GMT"},"import_tag":"skills-sh-top20k-2026-11","description":"Cross-platform file and directory path manipulation, segment splitting, extension extraction, and context conversion using `package:path` and `package:file`. Use when writing, inspecting, joining, splitting, or refactoring file paths, directory names, or extensions, or replacing raw string path operations (`.split('/')`, `'$dir/$file'`, `.endsWith('.ext')`, `.replaceAll('\\\\', '/')`). Don't use for HTTP network URI routing, database query strings, or non-path string processing."}},"renderedAt":1793661691917}

Safe Cross-Platform Path Manipulation in Dart Contents 1. Core Principles & Cross-Platform Rules 2. Recommended package:path Idioms vs. String Anti-Patterns 3. Bridging Native Paths to POSIX, Git, & URL Contexts 4. Mockable File Systems ( vs. Global ) 5. Extensions, Compound Extensions & Stem Extraction 6. Workflows & Audit Checklist References & Examples --- 1. Core Principles & Cross-Platform Rules Avoid Treating File Paths as Raw Strings Native file paths on Windows use backslashes ( ), whereas macOS and Linux use forward slashes ( ). String operations like , , or silently fail on Windows…