flutter/agent-pluginsCheck passed
SKILL DETAIL
dart-use-path-package
flutter/agent-plugins/dart-use-path-package
>-
Installs · 127View source
Installation
npx skills add https://github.com/flutter/agent-plugins --skill dart-use-path-package
Skill files
SKILL.md
Last synced · Sep 19, 2026
examples/cross_platform_paths.dart›
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'package:path/path.dart' as p;
/// Checks if [file] resides inside a 'canvaskit' directory and has a '.wasm' extension.
bool isCanvasKitWasmAsset(File file) {
final segments = p.split(file.path);
return segments.contains('canvaskit') && p.extension(file.path) == '.wasm';
}
/// Converts an OS-native file path into a POSIX-style web asset key.
String computeWebAssetKey(String filePath, String projectRoot) {
final relative = p.relative(filePath, from: projectRoot);
final segments = p.split(relative);
return switch (segments) {
['assets', ...] => p.posix.joinAll(segments),
_ => p.posix.joinAll(['assets', ...segments]),
};
}
/// Inserts a content hash before file extensions, safely handling compound
/// extensions (e.g. '.js.map') versus multi-dot stems (e.g. 'main.dart.wasm').
String insertContentHash(String filename, String hash) {
final compoundExt = p.extension(filename, 2);
final ext = compoundExt.endsWith('.map')
? compoundExt
: p.extension(filename);
final stem = filename.substring(0, filename.length - ext.length);
return '$stem.$hash$ext';
}
/// Converts an OS-native relative path to a POSIX path suitable for
/// `.gitignore`, `.gitattributes`, or Git tree objects.
String toGitPath(String relativeNativePath) =>
p.posix.joinAll(p.split(relativeNativePath));
void main() {
print('Git path: ${toGitPath(r'lib\src\file.dart')}');
print('Asset key: ${computeWebAssetKey('assets/icon.png', '.')}');
print('Content hash: ${insertContentHash('main.dart.js.map', 'a1b2c3')}');
}
examples/file_system_context.dart›
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:file/file.dart';
import 'package:file/memory.dart';
/// Lists subdirectory names using the [FileSystem]'s own path context
/// rather than the host OS's global `p.*` functions.
List<String> listSubdirectoryNames(Directory dir) {
final pathContext = dir.fileSystem.path;
return dir
.listSync()
.whereType<Directory>()
.map((d) => pathContext.basename(d.path))
.toList();
}
void main() {
// Simulates a Windows filesystem running inside a unit test on Linux/macOS.
final fs = MemoryFileSystem(style: FileSystemStyle.windows);
final projectDir = fs.directory(r'C:\workspace\app\lib')
..createSync(recursive: true);
fs.directory(r'C:\workspace\app\lib\src').createSync();
final subdirs = listSubdirectoryNames(projectDir);
print('Subdirectories: $subdirs');
}
SKILL.md›
---
name: dart-use-path-package
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.
metadata:
model: models/gemini-3.1-pro-preview
last_modified: Sun, 06 Sep 2026 07:14:00 GMT
---
# Safe Cross-Platform Path Manipulation in Dart
## Contents
* [1. Core Principles & Cross-Platform Rules](#1-core-principles--cross-platform-rules)
* [2. Recommended package:path Idioms vs. String Anti-Patterns](#2-recommended-packagepath-idioms-vs-string-anti-patterns)
* [3. Bridging Native Paths to POSIX, Git, & URL Contexts](#3-bridging-native-paths-to-posix-git--url-contexts)
* [4. Mockable File Systems (`package:file` vs. Global `p.*`)](#4-mockable-file-systems-packagefile-vs-global-p)
* [5. Extensions, Compound Extensions & Stem Extraction](#5-extensions-compound-extensions--stem-extraction)
* [6. Workflows & Audit Checklist](#6-workflows--audit-checklist)
* [References & Examples](#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 `.contains('foo/')`, `.startsWith('foo/')`, or `.split('/')` silently fail on Windows native paths.
* String interpolation like `'$dir/$file'` injects forward slashes on Windows and produces duplicate slashes (`//`) when `$dir` ends with a trailing slash.
**Rule**: Always decompose paths into segments using `p.split(path)` before inspecting directory hierarchy or segment names, and always join path components using `p.join(...)`.
### Pragmatic Boundary Joining vs. Multi-Segment Decomposition (`p.join`)
* **Cross-Platform Libraries (Windows + POSIX)**: Pass individual path segments to `p.join(dir, 'sub', 'file.json')` so `package:path` inserts OS-native separators (`\` on Windows, `/` on POSIX) between every component.
* **POSIX-Only Tools & Static Subpath Greppability**: 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 (`p.join(home, '.local', 'share', 'app', 'bin', 'config.json')`) causes `dart format` to wrap across 6–8 vertical lines and **destroys substring greppability** (`grep` / `code_search` for `.local/share/app/bin`).
* **Rule for POSIX Targets**: Prefer **2-argument boundary joining** (`p.join(home, '.local/share/app/bin/config.json')`). This prevents duplicate-slash bugs (`//`) at variable boundaries while preserving single-line readability and exact string searchability.
### Normalization vs. Canonicalization (`p.normalize` vs. `p.canonicalize`)
* `p.normalize(path)` resolves `.` and `..` segments purely lexically without consulting the filesystem or standardizing case.
* When deduplicating directory paths or comparing physical file identity across symlinks, relative roots, or case-insensitive filesystems, use `p.canonicalize(path)`.
### Strip Location Specifiers & Convert URIs Safely
* Strings formatted as `<path>:<line>-<col>` or `<path>:<line>` are not pure file paths. Passing them directly to `p.normalize` or `Uri.parse` causes bugs (on Windows, `Uri.parse` mistakes `C:` for a URI scheme and `:line` for a port).
* Extract the trailing `:line-col` suffix via regular expression (`RegExp(r'^(.*?):(\d+(?:-\d+)?)$')`) *before* passing the file path to `package:path`.
* **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.
---
## 2. Recommended package:path Idioms vs. String Anti-Patterns
### Path Joining
* **Prefer**: `p.join(dir, file)`
* **Avoid**: `'$dir/$file'` or `'a/$b'`
* **Why**: String interpolation injects `/` on Windows and creates duplicate
slashes (`//`) when `$dir` ends with a trailing separator.
### Segment Matching
* **Prefer**: `p.split(path).contains('foo')`
* **Avoid**: `path.contains('foo/')`
* **Why**: String matching fails on Windows backslashes (`foo\bar`) and produces
false positives on partial substring names (e.g. `barfoo/`).
### Root and Directory Prefixes
* **Prefer**: `p.split(path).first == 'foo'` or `p.isWithin('foo', path)`
* **Avoid**: `path.startsWith('foo/')`
* **Why**: Fails on Windows separators and misses relative prefix variants such
as `./foo/`.
### File Extensions
* **Prefer**: `p.extension(path) == '.wasm'`
* **Avoid**: `path.endsWith('.wasm')`
* **Why**: Substring suffix matching falsely matches directories (`foo.wasm/`)
or non-extension suffixes.
### Extension Slicing and Compound Extensions
* **Prefer**: `p.withoutExtension(path)` and `p.extension(path, 2)`
* **Avoid**: `path.lastIndexOf('.')` and manual `substring` slicing
* **Why**: Manual arithmetic breaks on hidden dotfiles (`.gitignore`) and
compound extensions (`.js.map`, `.tar.gz`).
### POSIX and URL Path Conversion
* **Prefer**: `p.posix.joinAll(p.split(path))` or `p.url.joinAll(p.split(path))`
* **Avoid**: `path.replaceAll(r'\', '/')`
* **Why**: Ad-hoc separator replacement fails on root drives and mixes OS
context with POSIX or URL targets.
### URI Conversion
* **Prefer**: `p.toUri(path)` and `p.fromUri(uri)`
* **Avoid**: `Uri.parse(path)` and `uri.path`
* **Why**: Direct URI parsing fails on Windows drive letters (`C:`) and leaks
percent-encoding (e.g. `%20` for spaces).
### Directory Basename Helper
* **Prefer**:
`String canonicalDirName(Directory d) => p.basename(p.normalize(d.absolute.path));`
* **Avoid**: Repeating `p.basename(p.normalize(dir.absolute.path))` inline
across files.
* **Why**: Centralizes canonical directory naming logic and reduces boilerplate.
---
## 3. Bridging Native Paths to POSIX, Git, & URL Contexts
Avoid calling `.replaceAll('\\', '/')` or `.replaceAll(r'\', '/')` to convert
OS-native paths into POSIX paths (for Git, YAML, archive manifests) or URL
segments.
**Rule**: Split the relative native path using `p.split(...)`, inspect segments
with **Dart 3 list pattern matching**, and join using `p.posix.joinAll(...)` or
`p.url.joinAll(...)`. Always call `p.relative(filePath, from: root)` first so
leading root segments (`'/'` on POSIX or `r'C:\'` on Windows) do not interfere
with relative prefix patterns:
```dart
import 'package:path/path.dart' as p;
String computeWebAssetKey(String filePath, String projectRoot) {
final relative = p.relative(filePath, from: projectRoot);
final segments = p.split(relative);
return switch (segments) {
['assets', ...] => p.posix.joinAll(segments),
_ => p.posix.joinAll(['assets', ...segments]),
};
}
```
### Git Paths and Repository Metadata
* Git repository tree objects, `.gitignore` pattern rules, `.gitattributes`,
and git-tracked symlinks strictly use POSIX forward slashes (`/`), even on
Windows.
* Inserting native Windows backslashes (`\`) into `.gitignore` or git commands
causes Git to treat `\` as an escape character rather than a directory
separator, silently breaking pattern matching.
* When generating `.gitignore` entries, repository manifests, or symlink
targets programmatically from native file paths, convert the relative native
path using `p.posix.joinAll(p.split(relativePath))` or `p.posix.join(...)`.
---
## 4. Mockable File Systems (`package:file` vs. Global `p.*`)
In codebases that use `package:file` (e.g., CLI applications or services tested
with `MemoryFileSystem`), avoid calling top-level `p.*` functions on `File` or
`Directory` paths.
* Top-level `p.*` functions bind to the *host operating system* running the test.
* 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.
**Rule**: Always use the `Context` attached to the `FileSystem` (`file.fileSystem.path`):
```dart
import 'package:file/file.dart';
List<String> listSubdirectoryNames(Directory dir) {
final pathContext = dir.fileSystem.path;
return dir
.listSync()
.whereType<Directory>()
.map((d) => pathContext.basename(d.path))
.toList();
}
```
---
## 5. Extensions, Compound Extensions & Stem Extraction
Avoid 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.
* **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)`:
```dart
import 'package:path/path.dart' as p;
String insertContentHash(String filename, String hash) {
final compoundExt = p.extension(filename, 2);
// Only use the 2-level extension for true compound suffixes (e.g., '.js.map')
final ext = compoundExt.endsWith('.map')
? compoundExt
: p.extension(filename);
final stem = filename.substring(0, filename.length - ext.length);
return '$stem.$hash$ext';
}
```
---
## 6. Workflows & Audit Checklist
### Path Refactoring Checklist
- [ ] Replace string interpolation (`'$dir/$file'`) with `p.join(dir, file)`.
- [ ] Replace `.contains('dir/')` and `.startsWith('dir/')` with `p.split(path)` segment checks or `p.isWithin(parent, child)`.
- [ ] Replace `.replaceAll(r'\', '/')` with `p.posix.joinAll(p.split(path))` (or `p.url.joinAll`).
- [ ] Replace `.endsWith('.ext')` on file paths with `p.extension(path) == '.ext'`.
- [ ] Replace manual dot-index slicing with `p.withoutExtension(path)` and `p.extension(path, [level])`.
- [ ] Verify that code using `package:file` accesses `fileSystem.path` instead of global `p.*`.
- [ ] Ensure Git paths, `.gitignore` entries, and symlink targets use `p.posix` forward slashes.
---
## References & Examples
* **Cross-Platform Path & POSIX Conversion Examples**: [examples/cross_platform_paths.dart](examples/cross_platform_paths.dart)
* **Mockable FileSystem Path Context Example**: [examples/file_system_context.dart](examples/file_system_context.dart)