// eqs-http:autorise doit être sur la ligne du fetch, ce que Prettier défait #110

Closed
opened 2026-10-01 14:43:49 +00:00 by nicolas-fryder · 1 comment

Ce qui se passe

http.fetch-brut cherche le marqueur sur la ligne même du fetch :

if (!/\bfetch\(/.test(l) || /eqs-http:autorise/.test(l)) return

Or un appel fetch réel tient rarement sur une ligne. Et dès qu'il ne la tient pas, Prettier déplace le commentaire de fin de ligne : placé après l'accolade ouvrante, il le descend à la ligne suivante.

J'ai écrit ceci :

response = await fetch(url, { // eqs-http:autorise
  ...(options.headers ? { headers: options.headers } : {}),
  signal: controller.signal,
})

Prettier a réécrit ceci :

response = await fetch(url, {
  // eqs-http:autorise
  ...(options.headers ? { headers: options.headers } : {}),
  signal: controller.signal,
})

Et la règle ne le voit plus. Le marqueur devient donc impossible à poser de façon stable sur tout appel multi-ligne, dans un dépôt qui met en forme son code — c'est-à-dire tous, puisque format est une tâche de la porte.

J'ai perdu deux passages à le comprendre : on pose le marqueur, le gate reste rouge, on croit s'être trompé de syntaxe. Le seul des quatre appels de nuage qui a fonctionné d'emblée est celui qui tenait déjà sur une ligne.

Le contournement, qui n'en est pas vraiment un

Extraire les options dans une constante pour que l'appel tienne sur une ligne :

const init: RequestInit = {
  ...(options.headers ? { headers: options.headers } : {}),
  signal: controller.signal,
}
response = await fetch(url, init) // eqs-http:autorise

Ça marche, et ce n'est pas affreux. Mais c'est une contrainte de MISE EN FORME imposée par un détecteur, et il faut l'expliquer dans chaque fichier concerné pour que personne ne « simplifie » en réinlinant l'objet — ce qui désarmerait le marqueur en silence et ferait échouer la porte au prochain passage.

Proposition

Accepter le marqueur sur la ligne du fetch ou sur l'une des lignes qui la précèdent immédiatement, comme eslint-disable-next-line le fait — c'est la convention que tout le monde connaît déjà :

const lignes = readFileSync(p, 'utf8').split(/\r?\n/)
lignes.forEach((l, i) => {
  if (LIGNE_DE_COMMENTAIRE.test(l)) return
  if (!/\bfetch\(/.test(l)) return
  // Le marqueur sur la ligne, ou dans le bloc de commentaire qui la précède.
  if (/eqs-http:autorise/.test(l)) return
  if (/eqs-http:autorise/.test(lignes[i - 1] ?? '')) return
  …
})

Une variante plus généreuse remonterait tant que les lignes précédentes sont des commentaires : cela laisserait écrire la raison et le marqueur dans le même bloc JSDoc, ce qui est l'endroit naturel.

Un mot dans le README sur l'emplacement exact aiderait aussi, quelle que soit la décision — la règle est la seule du socle dont la position du marqueur compte, et rien ne le dit.

Trouvé en rétrofittant nuage, @eqs/http@0.14.0, Prettier 3.9.6. Voir aussi #109, où la même règle est aveugle en monorepo.

## Ce qui se passe `http.fetch-brut` cherche le marqueur **sur la ligne même** du `fetch` : ```js if (!/\bfetch\(/.test(l) || /eqs-http:autorise/.test(l)) return ``` Or un appel `fetch` réel tient rarement sur une ligne. Et dès qu'il ne la tient pas, **Prettier déplace le commentaire de fin de ligne** : placé après l'accolade ouvrante, il le descend à la ligne suivante. J'ai écrit ceci : ```ts response = await fetch(url, { // eqs-http:autorise ...(options.headers ? { headers: options.headers } : {}), signal: controller.signal, }) ``` Prettier a réécrit ceci : ```ts response = await fetch(url, { // eqs-http:autorise ...(options.headers ? { headers: options.headers } : {}), signal: controller.signal, }) ``` Et la règle ne le voit plus. Le marqueur devient donc impossible à poser **de façon stable** sur tout appel multi-ligne, dans un dépôt qui met en forme son code — c'est-à-dire tous, puisque `format` est une tâche de la porte. J'ai perdu deux passages à le comprendre : on pose le marqueur, le gate reste rouge, on croit s'être trompé de syntaxe. Le seul des quatre appels de nuage qui a fonctionné d'emblée est celui qui tenait déjà sur une ligne. ## Le contournement, qui n'en est pas vraiment un Extraire les options dans une constante pour que l'appel tienne sur une ligne : ```ts const init: RequestInit = { ...(options.headers ? { headers: options.headers } : {}), signal: controller.signal, } response = await fetch(url, init) // eqs-http:autorise ``` Ça marche, et ce n'est pas affreux. Mais c'est une contrainte de MISE EN FORME imposée par un détecteur, et il faut l'expliquer dans chaque fichier concerné pour que personne ne « simplifie » en réinlinant l'objet — ce qui désarmerait le marqueur en silence et ferait échouer la porte au prochain passage. ## Proposition Accepter le marqueur sur la ligne du `fetch` **ou sur l'une des lignes qui la précèdent immédiatement**, comme `eslint-disable-next-line` le fait — c'est la convention que tout le monde connaît déjà : ```js const lignes = readFileSync(p, 'utf8').split(/\r?\n/) lignes.forEach((l, i) => { if (LIGNE_DE_COMMENTAIRE.test(l)) return if (!/\bfetch\(/.test(l)) return // Le marqueur sur la ligne, ou dans le bloc de commentaire qui la précède. if (/eqs-http:autorise/.test(l)) return if (/eqs-http:autorise/.test(lignes[i - 1] ?? '')) return … }) ``` Une variante plus généreuse remonterait tant que les lignes précédentes sont des commentaires : cela laisserait écrire la raison et le marqueur dans le même bloc JSDoc, ce qui est l'endroit naturel. Un mot dans le README sur l'emplacement exact aiderait aussi, quelle que soit la décision — la règle est la seule du socle dont la position du marqueur compte, et rien ne le dit. Trouvé en rétrofittant nuage, `@eqs/http@0.14.0`, Prettier 3.9.6. Voir aussi #109, où la même règle est aveugle en monorepo.
Author
Owner

Corrigé par 6b198e5, PR #112, changeset marqueur-qui-survit-a-prettier. Chasse 24. Fermeture à la fusion.

Ce que la mesure a ajouté à votre ticket

Vous décriviez un déplacement : le marqueur descend dans l'objet. Mesuré avec Prettier 3.9, il y en a deux :

fetch(url, { // m              →  fetch(url, {
                                    // m

fetch('…long…', init) // m     →  fetch(
                                    '…long…',
                                    init,
                                  ); // m

Le second renvoie le marqueur après la parenthèse fermante, plusieurs lignes plus bas. C'est la forme la plus naturelle pour un console.error.

Ma première version suivait votre proposition à la lettre — la ligne de commentaire au-dessus, plus celle juste en dessous — et ratait cette seconde forme. Elle est sortie en mesurant ce que Prettier fait des autres marqueurs, avant d'écrire leurs tests.

Ce qui est accepté

  • le bloc de commentaires qui précède immédiatement — votre proposition, la position d'eslint-disable-next-line, recommandée parce que Prettier ne la déplace jamais et que la raison tient avec le marqueur. J'ai pris votre variante généreuse : on remonte tant que ce sont des commentaires, pour que la raison et le marqueur vivent dans le même bloc ;
  • n'importe quelle ligne de l'expression qui commence sur la ligne signalée, trouvée en comptant parenthèses, accolades et crochets. Ça couvre la ligne même, le marqueur descendu dans l'objet, et celui renvoyé après la parenthèse.

La remontée s'arrête au premier code : un marqueur posé plus haut autorise autre chose, et le laisser couvrir votre appel ferait d'un seul marqueur une dispense de fichier entier. Un test le fixe, et il a été éprouvé en cassant seulement cette propriété.

La chasse : vous en aviez signalé un, il y en avait trois

marqueur règle
eqs-http:autorise http.fetch-brut — le vôtre
eqs-config:autorise config.lecture-hors-module — même code, non signalé
eqs-telemetry:autorise telemetry.console — même code, non signalé

Les trois portent le même helper, recopié, parce que @eqs/testing dépend déjà de @eqs/config et qu'aucune dépendance commune ne permet de le partager sans cycle. C'est la forme exacte de #100 — porter recopié trois fois avec la même faute. La parade est différente : chaque copie a son propre test, qui rejoue son marqueur à travers le vrai Prettier, et chaque test a été éprouvé en cassant sa copie. Une dérive rougirait chez elle. C'est une parade, pas une suppression, et la chasse 24 le dit.

Les tests rejouent Prettier, ils ne le supposent pas

http.fetch-brut n'avait aucun test. Il en a huit. Les cas centraux écrivent le marqueur comme vous l'avez fait, passent le vrai Prettier dessus, vérifient que le marqueur a bien quitté la ligne de l'appel, puis jouent la règle.

Écrire à la main la forme « après Prettier » aurait supposé ce que Prettier fait — et ma première entrée de test a montré que je le supposais mal : Prettier a gardé le marqueur sur la ligne, l'assertion « il a quitté la ligne » a rougi, j'ai mesuré puis corrigé l'entrée.

Le README

Les trois nomment désormais leur marqueur, où le poser, et pourquoi le bloc au-dessus est la bonne place. Vous aviez raison : c'était la seule règle du socle dont la position du marqueur comptait, et rien ne le disait.

Votre contournement

const init: RequestInit = { … }
response = await fetch(url, init) // eqs-http:autorise

Il continue de marcher et n'a rien à retirer. Mais il n'est plus nécessaire, et le commentaire « ne pas réinliner l'objet » qui le protégeait peut partir : réinliner ne désarme plus le marqueur.

Corrigé par `6b198e5`, PR #112, changeset `marqueur-qui-survit-a-prettier`. Chasse 24. Fermeture à la fusion. ## Ce que la mesure a ajouté à votre ticket Vous décriviez un déplacement : le marqueur descend dans l'objet. Mesuré avec Prettier 3.9, il y en a **deux** : ``` fetch(url, { // m → fetch(url, { // m fetch('…long…', init) // m → fetch( '…long…', init, ); // m ``` Le second renvoie le marqueur **après la parenthèse fermante**, plusieurs lignes plus bas. C'est la forme la plus naturelle pour un `console.error`. Ma première version suivait votre proposition à la lettre — la ligne de commentaire au-dessus, plus celle juste en dessous — et ratait cette seconde forme. Elle est sortie en mesurant ce que Prettier fait des autres marqueurs, **avant** d'écrire leurs tests. ## Ce qui est accepté - **le bloc de commentaires qui précède immédiatement** — votre proposition, la position d'`eslint-disable-next-line`, recommandée parce que Prettier ne la déplace jamais et que la raison tient avec le marqueur. J'ai pris votre variante généreuse : on remonte tant que ce sont des commentaires, pour que la raison et le marqueur vivent dans le même bloc ; - **n'importe quelle ligne de l'expression** qui commence sur la ligne signalée, trouvée en comptant parenthèses, accolades et crochets. Ça couvre la ligne même, le marqueur descendu dans l'objet, et celui renvoyé après la parenthèse. La remontée s'arrête au premier code : un marqueur posé plus haut autorise autre chose, et le laisser couvrir votre appel ferait d'un seul marqueur une dispense de fichier entier. Un test le fixe, et il a été éprouvé en cassant **seulement** cette propriété. ## La chasse : vous en aviez signalé un, il y en avait trois | marqueur | règle | | --- | --- | | `eqs-http:autorise` | `http.fetch-brut` — le vôtre | | `eqs-config:autorise` | `config.lecture-hors-module` — **même code, non signalé** | | `eqs-telemetry:autorise` | `telemetry.console` — **même code, non signalé** | Les trois portent le même helper, **recopié**, parce que `@eqs/testing` dépend déjà de `@eqs/config` et qu'aucune dépendance commune ne permet de le partager sans cycle. C'est la forme exacte de #100 — `porter` recopié trois fois avec la même faute. La parade est différente : chaque copie a son propre test, qui rejoue **son** marqueur à travers le vrai Prettier, et chaque test a été éprouvé en cassant **sa** copie. Une dérive rougirait chez elle. C'est une parade, pas une suppression, et la chasse 24 le dit. ## Les tests rejouent Prettier, ils ne le supposent pas `http.fetch-brut` n'avait **aucun** test. Il en a huit. Les cas centraux écrivent le marqueur comme vous l'avez fait, passent le **vrai** Prettier dessus, vérifient que le marqueur a bien quitté la ligne de l'appel, puis jouent la règle. Écrire à la main la forme « après Prettier » aurait supposé ce que Prettier fait — et ma première entrée de test a montré que je le supposais mal : Prettier a gardé le marqueur sur la ligne, l'assertion « il a quitté la ligne » a rougi, j'ai mesuré puis corrigé l'entrée. ## Le README Les trois nomment désormais leur marqueur, où le poser, et pourquoi le bloc au-dessus est la bonne place. Vous aviez raison : c'était la seule règle du socle dont la position du marqueur comptait, et rien ne le disait. ## Votre contournement ```ts const init: RequestInit = { … } response = await fetch(url, init) // eqs-http:autorise ``` Il continue de marcher et n'a rien à retirer. Mais il n'est plus nécessaire, et le commentaire « ne pas réinliner l'objet » qui le protégeait peut partir : réinliner ne désarme plus le marqueur.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Infrastructure/eqs-platform#110
No description provided.