dart-use-path-package

Par flutter · skills

Manipulation multiplateforme de chemins de fichiers et de répertoires, découpage en segments, extraction d'extensions et conversion de contexte avec `package:path` et `package:file`. À utiliser pour écrire, inspecter, joindre, découper ou refactoriser des chemins de fichiers, noms de répertoires ou extensions, ou pour remplacer des opérations brutes sur les chaînes de chemin (`.split('/')`, `'$dir/$file'`, `.endsWith('.ext')`, `.replaceAll('\\', '/')`). Ne pas utiliser pour le routage HTTP d'URI réseau, les chaînes de requête de base de données ou le traitement de chaînes non liées aux chemins.

npx skills add https://github.com/flutter/skills --skill dart-use-path-package

Manipulation sûre de chemins multi-plateformes en Dart

Sommaire


1. Principes fondamentaux et règles multi-plateformes

Éviter de traiter les chemins de fichiers comme des chaînes brutes

  • Les chemins de fichiers natifs sous Windows utilisent des antislashs (\), tandis que macOS et Linux utilisent des barres obliques (/).
  • Les opérations sur les chaînes comme .contains('foo/'), .startsWith('foo/') ou .split('/') échouent silencieusement sur les chemins natifs Windows.
  • L'interpolation de chaînes comme '$dir/$file' injecte des barres obliques sur Windows et produit des barres doubles (//) quand $dir se termine par un séparateur.

Règle : Décomposez toujours les chemins en segments avec p.split(path) avant d'inspecter la hiérarchie de répertoires ou les noms de segments, et joignez toujours les composants de chemin avec p.join(...).

Jointure pragmatique des frontières vs. décomposition multi-segments (p.join)

  • Bibliothèques multi-plateformes (Windows + POSIX) : Passez les segments de chemin individuels à p.join(dir, 'sub', 'file.json') pour que package:path insère les séparateurs natifs du système (\ sous Windows, / sur POSIX) entre chaque composant.
  • Outils POSIX uniquement et greppabilité statique de sous-chemins : Dans les bases de code ciblant exclusivement Linux/macOS (ou lors de la jointure d'un chemin de base dynamique à un sous-chemin statique connu), la décomposition de 5–6 segments statiques en arguments séparés (p.join(home, '.local', 'share', 'app', 'bin', 'config.json')) fait que dart format enveloppe sur 6–8 lignes verticales et détruit la greppabilité des sous-chaînes (recherche grep ou code_search pour .local/share/app/bin).
  • Règle pour les cibles POSIX : Préférez la jointure de frontière à 2 arguments (p.join(home, '.local/share/app/bin/config.json')). Cela prévient les bugs de barres doubles (//) aux frontières de variables tout en préservant la lisibilité sur une seule ligne et la greppabilité exacte.

Normalisation vs. canonicalisation (p.normalize vs. p.canonicalize)

  • p.normalize(path) résout les segments . et .. purement lexicalement sans consulter le système de fichiers ni standardiser la casse.
  • Lors de la déduplication de chemins de répertoires ou de la comparaison de l'identité physique de fichiers à travers des liens symboliques, des racines relatives ou des systèmes de fichiers insensibles à la casse, utilisez p.canonicalize(path).

Supprimer les spécificateurs de localisation et convertir les URI de manière sûre

  • Les chaînes formatées comme <path>:<line>-<col> ou <path>:<line> ne sont pas des chemins purs. Les passer directement à p.normalize ou Uri.parse provoque des bugs (sous Windows, Uri.parse confond C: avec un schéma URI et :line avec un port).
  • Extrayez le suffixe :line-col final via expression régulière (RegExp(r'^(.*?):(\d+(?:-\d+)?)$')) avant de passer le chemin de fichier à package:path.
  • Conversions aux frontières URI : Lors de la conversion entre chemins de fichiers et objets Uri, utilisez toujours p.toUri(path) et p.fromUri(uri) plutôt que Uri.parse(path) ou la concaténation manuelle de chaînes.

2. Idiomes recommandés package:path vs. anti-motifs de chaînes

Jointure de chemins

  • Préférez : p.join(dir, file)
  • Évitez : '$dir/$file' ou 'a/$b'
  • Pourquoi : L'interpolation de chaînes injecte / sous Windows et crée des barres doubles (//) quand $dir se termine par un séparateur.

Correspondance de segments

  • Préférez : p.split(path).contains('foo')
  • Évitez : path.contains('foo/')
  • Pourquoi : La correspondance de chaînes échoue sur les antislashs Windows (foo\bar) et produit des faux positifs sur les noms de sous-chaînes partielles (ex. barfoo/).

Racine et préfixes de répertoire

  • Préférez : p.split(path).first == 'foo' ou p.isWithin('foo', path)
  • Évitez : path.startsWith('foo/')
  • Pourquoi : Échoue sur les séparateurs Windows et manque les variantes de préfixes relatifs comme ./foo/.

Extensions de fichiers

  • Préférez : p.extension(path) == '.wasm'
  • Évitez : path.endsWith('.wasm')
  • Pourquoi : La correspondance de suffixe de sous-chaîne correspond faussement aux répertoires (foo.wasm/) ou aux suffixes autres que les extensions.

Découpage d'extensions et extensions composées

  • Préférez : p.withoutExtension(path) et p.extension(path, 2)
  • Évitez : path.lastIndexOf('.') et la découpe manuelle avec substring
  • Pourquoi : L'arithmétique manuelle s'effondre sur les fichiers cachés pointillés (.gitignore) et les extensions composées (.js.map, .tar.gz).

Conversion POSIX et URL

  • Préférez : p.posix.joinAll(p.split(path)) ou p.url.joinAll(p.split(path))
  • Évitez : path.replaceAll(r'\', '/')
  • Pourquoi : Le remplacement de séparateur ad hoc échoue sur les lecteurs racine et mélange le contexte du système avec les cibles POSIX ou URL.

Conversion URI

  • Préférez : p.toUri(path) et p.fromUri(uri)
  • Évitez : Uri.parse(path) et uri.path
  • Pourquoi : L'analyse directe d'URI échoue sur les lettres de lecteur Windows (C:) et fuit l'encodage en pourcentage (ex. %20 pour les espaces).

Aide pour le nom de base de répertoire

  • Préférez : String canonicalDirName(Directory d) => p.basename(p.normalize(d.absolute.path));
  • Évitez : Répéter p.basename(p.normalize(dir.absolute.path)) en ligne dans les fichiers.
  • Pourquoi : Centralise la logique de nommage canonique des répertoires et réduit le code répétitif.

3. Convertir les chemins natifs en contextes POSIX, Git et URL

Évitez d'appeler .replaceAll('\\', '/') ou .replaceAll(r'\', '/') pour convertir les chemins natifs du système en chemins POSIX (pour Git, YAML, manifestes d'archives) ou segments URL.

Règle : Divisez le chemin relatif natif avec p.split(...), inspectez les segments avec la correspondance de motifs de liste Dart 3, et joignez avec p.posix.joinAll(...) ou p.url.joinAll(...). Appelez toujours p.relative(filePath, from: root) en premier pour que les segments de racine ('/' sur POSIX ou r'C:\' sous Windows) n'interfèrent pas avec les motifs de préfixe relatif :

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]),
  };
}

Chemins Git et métadonnées de dépôt

  • Les objets d'arbre du dépôt Git, les règles de motifs .gitignore, .gitattributes et les liens symboliques suivis par git utilisent strictement les barres obliques POSIX (/), même sous Windows.
  • Insérer des antislashs Windows natifs (\) dans .gitignore ou les commandes git amène Git à traiter \ comme un caractère d'échappement plutôt qu'un séparateur de répertoire, cassant silencieusement la correspondance de motifs.
  • Lors de la génération d'entrées .gitignore, de manifestes de dépôt ou de cibles de liens symboliques par programme à partir de chemins de fichiers natifs, convertissez le chemin relatif natif avec p.posix.joinAll(p.split(relativePath)) ou p.posix.join(...).

4. Systèmes de fichiers mockables (package:file vs. p.* global)

Dans les bases de code utilisant package:file (ex. applications CLI ou services testés avec MemoryFileSystem), évitez d'appeler des fonctions top-level p.* sur les chemins File ou Directory.

  • Les fonctions top-level p.* se lient au système d'exploitation hôte exécutant le test.
  • Si un test unitaire crée MemoryFileSystem(style: FileSystemStyle.windows) sur un runner Linux ou macOS, la fonction globale p.split(file.path) divisera sur / au lieu de \, cassant le test.

Règle : Utilisez toujours le Context attaché au FileSystem (file.fileSystem.path) :

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, extensions composées et extraction de radical

Évitez l'arithmétique manuelle .lastIndexOf('.') et .substring() lors de l'extraction d'extensions de fichiers ou de l'insertion de hachages de contenu. p.extension supporte nativement les extensions multi-niveaux via son paramètre optionnel level.

  • Nuance du radical multi-point : Appeler p.extension('main.dart.wasm', 2) retourne '.dart.wasm' parce qu'il capture aveuglément les deux derniers segments séparés par des points. Lors du hachage ou de la suppression d'extensions sur les fichiers qui peuvent avoir des radicaux multi-points (ex. main.dart.wasm vs. main.dart.js.map), vérifiez si p.extension(filename, 2) correspond à une extension composée connue (ou .endsWith('.map')) avant de revenir à l'extension mono-niveau p.extension(filename) :
import 'package:path/path.dart' as p;

String insertContentHash(String filename, String hash) {
  final compoundExt = p.extension(filename, 2);
  // Utilisez uniquement l'extension à 2 niveaux pour les vrais suffixes composés (ex. '.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 et liste de contrôle d'audit

Liste de contrôle de refactorisation de chemins

  • [ ] Remplacez l'interpolation de chaîne ('$dir/$file') par p.join(dir, file).
  • [ ] Remplacez .contains('dir/') et .startsWith('dir/') par des vérifications de segments p.split(path) ou p.isWithin(parent, child).
  • [ ] Remplacez .replaceAll(r'\', '/') par p.posix.joinAll(p.split(path)) (ou p.url.joinAll).
  • [ ] Remplacez .endsWith('.ext') sur les chemins de fichiers par p.extension(path) == '.ext'.
  • [ ] Remplacez la découpe manuelle par indice de point par p.withoutExtension(path) et p.extension(path, [level]).
  • [ ] Vérifiez que le code utilisant package:file accède à fileSystem.path au lieu de p.* global.
  • [ ] Assurez-vous que les chemins Git, les entrées .gitignore et les cibles de liens symboliques utilisent les barres obliques p.posix.

Références et exemples

Skills similaires