Spec-Zone.ru › Prettier

Обоснование

Prettier — это форматировщик кода с определёнными предпочтениями. Этот документ объясняет некоторые из его решений.

Что беспокоит Prettier

Корректность

Первое требование к Prettier — это вывод корректного кода, который имеет точно такое же поведение, как и до форматирования. Пожалуйста, сообщите о любом коде, где Prettier не соблюдает эти правила корректности — это ошибка, которую нужно исправить!

Строки

Двойные или одинарные кавычки? Prettier выбирает те, которые приводят к наименьшему количеству экранирований. "It's gettin' better!", а не 'It\'s gettin\' better!'. В случае ничьей или если строка не содержит кавычек, Prettier использует двойные кавычки (но это можно изменить с помощью опции singleQuote).

JSX имеет собственную опцию для кавычек: jsxSingleQuote. JSX унаследовал корни от HTML, где для атрибутов чаще используются двойные кавычки. Инструменты разработчика браузера также следуют этой конвенции, всегда отображая HTML с двойными кавычками, даже если исходный код использует одинарные кавычки. Отдельная опция позволяет использовать одинарные кавычки для JS и двойные кавычки для "HTML" (JSX).

Prettier сохраняет способ экранирования вашей строки. Например, "🙂" не будет отформатирован в "\uD83D\uDE42" и наоборот.

Пустые строки

Оказалось, что пустые строки очень сложно генерировать автоматически. Подход Prettier заключается в сохранении пустых строк так, как они были в исходном коде. Есть два дополнительных правила:

  • Prettier сводит несколько пустых строк к одной пустой строке.
  • Пустые строки в начале и конце блоков (и целых файлов) удаляются. (Файлы всегда заканчиваются одной пустой строкой.)

Многострочные объекты

По умолчанию алгоритм вывода Prettier выводит выражения на одной строке, если они помещаются. Однако объекты используются для многих целей в JavaScript, и иногда для улучшения читаемости лучше, чтобы они оставались многострочными. Например, см. списки объектов, вложенные конфигурации, таблицы стилей и ключевые методы. Мы не смогли найти хорошее правило для всех этих случаев, поэтому Prettier вместо этого сохраняет объекты многострочными, если между { и первым ключом в исходном коде есть перевод строки. Следствием этого является то, что длинные однострочные объекты автоматически расширяются, но короткие многострочные объекты никогда не сводятся.

Совет: Если у вас есть многострочный объект, который вы хотели бы объединить в одну строку:

const user = {
  name: "John Doe",
  age: 30,
};

…все, что вам нужно сделать, это удалить перевод строки после {:

const user = {  name: "John Doe",
  age: 30
};

…а затем запустить Prettier:

const user = { name: "John Doe", age: 30 };

И если вы хотите снова сделать его многострочным, добавьте перевод строки после {:

const user = {
 name: "John Doe", age: 30 };

…и запустите Prettier:

const user = {
  name: "John Doe",
  age: 30,
};

♻️ Примечание о возможности обратного преобразования форматирования

Полуавтоматическое форматирование литералов объектов на самом деле является обходным путём, а не функцией. Оно было реализовано только потому, что в то время не удалось найти хорошее эвристическое правило, и была необходима срочная исправление. Однако, в качестве общей стратегии, Prettier избегает необратимого форматирования, такого как это, поэтому команда до сих пор ищет эвристику, которая позволит либо полностью удалить это поведение, либо по крайней мере сократить количество ситуаций, где оно применяется.

Что означает обратимость? После того, как литерал объекта становится многострочным, Prettier не будет сводить его обратно. Если в отформатированном коде Prettier мы добавим свойство в литерал объекта, запустим Prettier, а затем изменим решение, удалим добавленное свойство и снова запустим Prettier, мы можем получить форматирование, не идентичное начальному. Эта бесполезная модификация может даже быть включена в коммит, что является именно той ситуацией, для предотвращения которой и был создан Prettier.

Декораторы

Так же, как и с объектами, декораторы используются для многих разных целей. Иногда имеет смысл писать декораторы над строкой, которую они украшают, а иногда удобнее, если они находятся на одной строке. Мы не смогли найти для этого хорошее правило, поэтому Prettier сохраняет позицию вашего декоратора, как вы их написали (если они помещаются на строке). Это не идеально, но прагматическое решение сложной проблемы.

@Component({
  selector: "hero-button",
  template: `<button>{{ label }}</button>`,
})
class HeroButtonComponent {
  // These decorators were written inline and fit on the line so they stay
  // inline.
  @Output() change = new EventEmitter();
  @Input() label: string;

  // These were written multiline, so they stay multiline.
  @readonly
  @nonenumerable
  NODE_TYPE: 2;
}

Есть одно исключение: классы. Мы не считаем, что когда-либо имеет смысл встраивать декораторы для них, поэтому они всегда перемещаются на отдельную строку.

// Before running Prettier:
@observer class OrderLine {
  @observable price: number = 0;
}
// After running Prettier:
@observer
class OrderLine {
  @observable price: number = 0;
}

Примечание: Prettier 1.14.x и более ранние версии пытались автоматически перемещать ваши декораторы, поэтому, если вы запускали более старую версию Prettier на своём коде, вам может потребоваться вручную объединить некоторые декораторы, чтобы избежать несоответствий:

@observer
class OrderLine {
  @observable price: number = 0;
  @observable
  amount: number = 0;
}

Ещё один момент: TC39 ещё не решила, должны ли декораторы предшествовать или следовать за export. Тем временем Prettier поддерживает оба варианта:

@decorator export class Foo {}

export @decorator class Foo {}

Шаблонные литералы

Шаблонные литералы могут содержать интерполяции. Решение о том, целесообразно ли вставлять перевод строки внутри интерполяции, к сожалению, зависит от семантического содержания шаблона — например, введение перевода строки в середине предложения на естественном языке обычно нежелательно. Поскольку Prettier не обладает достаточной информацией, чтобы принять это решение самостоятельно, он использует эвристику, аналогичную той, что используется для объектов: он будет разделять выражение интерполяции на несколько строк только в том случае, если в этой интерполяции уже был перевод строки.

Это означает, что такой литерал, как следующий, не будет разделен на несколько строк, даже если он превышает ширину печати:

`this is a long message which contains an interpolation: ${format(data)} <- like this`;

Если вы хотите, чтобы Prettier разбил интерполяцию на несколько строк, вам нужно убедиться, что где-то внутри ${...} есть перевод строки. В противном случае всё будет находиться на одной строке, независимо от её длины.

Команда хотела бы не зависеть от исходного форматирования таким образом, но это лучшая эвристика, которая у нас есть на данный момент.

Точки с запятой

Это касается использования опции noSemi.

Рассмотрим этот фрагмент кода:

if (shouldAddLines) {
  [-1, 1].forEach(delta => addLine(delta * 20))
}

Хотя вышеуказанный код работает без точек с запятой, Prettier фактически преобразует его в:

if (shouldAddLines) {
  ;[-1, 1].forEach(delta => addLine(delta * 20))
}

Это делается для того, чтобы помочь вам избежать ошибок. Представьте себе, что Prettier не вставляет эту точку с запятой и добавляет эту строку:

 if (shouldAddLines) {
+  console.log('Do we even get here??')
   [-1, 1].forEach(delta => addLine(delta * 20))
 }

Ошибочка! На самом деле вышеуказанное означает:

if (shouldAddLines) {
  console.log('Do we even get here??')[-1, 1].forEach(delta => addLine(delta * 20))
}

С точкой с запятой перед этим [, такие проблемы никогда не возникают. Это делает строку независимой от других строк, поэтому вы можете перемещать и добавлять строки, не задумываясь о правилах ASI.

Эта практика также распространена в standard, который использует стиль без точек с запятой.

Обратите внимание, что если в вашей программе есть ошибка, связанная с точкой с запятой, Prettier не автоматически исправит её за вас. Помните, что Prettier только форматирует код, а не изменяет поведение кода. В качестве примера рассмотрите этот фрагмент кода с ошибкой, где разработчик забыл поставить точку с запятой перед (:

console.log('Running a background task')
(async () => {
  await doBackgroundWork()
})()

Если вы передадите это в Prettier, он не изменит поведение этого кода, вместо этого он отформатирует его так, чтобы показать, как этот код будет работать при выполнении.

console.log("Running a background task")(async () => {
  await doBackgroundWork();
})();

Ширина печати

Опция printWidth — это скорее руководство для Prettier, чем жёсткое правило. Это не максимальная длина строки. Это способ сказать Prettier, примерно, какой длины вы хотели бы строки. Prettier будет делать и более короткие, и более длинные строки, но в целом стремиться к указанной ширине печати.

Существуют некоторые граничные случаи, такие как очень длинные строковые литералы, регулярные выражения, комментарии и имена переменных, которые нельзя разбить на строки (без использования преобразований кода, чего Prettier не делает). Или, если вы вкладываете свой код на 50 уровней вглубь, ваши строки, конечно, в основном будут состоять из отступов :)

Помимо этого, есть несколько случаев, когда Prettier намеренно превышает ширину печати.

Импорты

Prettier может разбивать длинные import утверждения на несколько строк:

import {
  CollectionDashboard,
  DashboardPlaceholder,
} from "../components/collections/collection-dashboard/main";

В следующем примере строка не помещается в ширину печати, но Prettier всё равно печатает её на одной строке:

import { CollectionDashboard } from "../components/collections/collection-dashboard/main";

Это может быть неожиданно для некоторых, но мы делаем это так, поскольку было распространено желание оставлять import с единственными элементами на одной строке. То же самое относится к вызовам require.

Функции тестирования

Ещё одной распространённой просьбой было сохранение длинных описаний тестов в одной строке, даже если она становится слишком длинной. В таких случаях обертывание аргументов в новые строки не очень помогает.

describe("NodeRegistry", () => {
  it("makes no request if there are no nodes to prefetch, even if the cache is stale", async () => {
    // The above line exceeds the print width but stayed on one line anyway.
  });
});

Prettier имеет особые случаи для общих функций фреймворков тестирования, таких как describe, it и test.

JSX

Prettier печатает вещи немного иначе по сравнению с другим JS, когда задействован JSX:

function greet(user) {
  return user
    ? `Welcome back, ${user.name}!`
    : "Greetings, traveler! Sign up today!";
}

function Greet({ user }) {
  return (
    <div>{user ? (
        <p>Welcome back, {user.name}!</p>
      ) : (
        <p>Greetings, traveler! Sign up today!</p>
      )}</div>
  );
}

Есть две причины.

Во-первых, многие люди уже оборачивают свой JSX в скобки, особенно в return утверждениях. Prettier следует этой распространённой стилистике.

Во-вторых, альтернативное форматирование упрощает редактирование JSX. Легко оставить точку с запятой. В отличие от обычного JS, оставшаяся точка с запятой в JSX может отображаться как простой текст на вашей странице.

<div><p>Greetings, traveler! Sign up today!</p>; {/* <-- Oops! */}</div>

Комментарии

Что касается содержания комментариев, Prettier не может сделать с ними многого. Комментарии могут содержать всё, от прозы до кода, закомментированного с помощью #, и ASCII-диаграмм. Поскольку они могут содержать всё что угодно, Prettier не может знать, как их отформатировать или разбить на строки. Поэтому они остаются как есть. Исключением из этого правила являются комментарии в стиле JSDoc (блочные комментарии, где каждая строка начинается с *), форматирование отступов которых Prettier может исправить.

Затем возникает вопрос о том, где разместить комментарии. Оказывается, это очень сложная проблема. Prettier делает всё возможное, чтобы сохранить ваши комментарии примерно там, где они были, но это непростая задача, поскольку комментарии могут размещаться практически где угодно.

В целом, лучшие результаты получаются при размещении комментариев на отдельных строках, а не в конце строк. Предпочитайте // eslint-disable-next-line вместо // eslint-disable-line.

Обратите внимание, что «магические комментарии», такие как eslint-disable-next-line и $FlowFixMe иногда могут потребовать ручного перемещения из-за того, что Prettier разбивает выражение на несколько строк.

Представьте этот фрагмент кода:

// eslint-disable-next-line no-eval
const result = safeToEval ? eval(input) : fallback(input);

Затем вам нужно добавить ещё одно условие:

// eslint-disable-next-line no-eval
const result = safeToEval && settings.allowNativeEval ? eval(input) : fallback(input);

Prettier преобразует вышеуказанное в:

// eslint-disable-next-line no-eval
const result =
  safeToEval && settings.allowNativeEval ? eval(input) : fallback(input);

Что означает, что комментарий eslint-disable-next-line больше не работает эффективно. В этом случае вам нужно переместить комментарий:

const result =
  // eslint-disable-next-line no-eval
  safeToEval && settings.allowNativeEval ? eval(input) : fallback(input);

Если возможно, отдавайте предпочтение комментариям, которые охватывают диапазоны строк (например, eslint-disable и eslint-enable) или уровень оператора (например, /* istanbul ignore next */). Они даже безопаснее. Можно запретить использование комментариев eslint-disable-line и eslint-disable-next-line с помощью eslint-plugin-eslint-comments.

Предупреждение о нестандартном синтаксисе

Prettier часто может распознать и отформатировать нестандартный синтаксис, такой как предложения ECMAScript на ранней стадии и расширения синтаксиса Markdown, не определённые ни одной спецификацией. Поддержка такого синтаксиса рассматривается как максимально возможная и экспериментальная. Несовместимости могут быть внесены в любой версии и не должны рассматриваться как критические изменения.

Предупреждение о файлах, сгенерированных машиной

Некоторые файлы, такие как package.json или composer.lock, генерируются машиной и регулярно обновляются менеджером пакетов. Если бы Prettier использовал те же правила форматирования JSON, что и для других файлов, это бы регулярно вступало в конфликт с другими инструментами. Чтобы избежать этого неудобства, Prettier будет использовать форматировщик на основе JSON.stringify для таких файлов вместо этого. Вы можете заметить эти различия, такие как удаление вертикального пробела, но это запланированное поведение.

Что Prettier не учитывает

Prettier только выводит код. Он не преобразует его. Это делается для ограничения области применения Prettier. Давайте сосредоточимся на выводе и сделаем это действительно хорошо!

Вот несколько примеров того, что выходит за рамки Prettier:

  • Преобразование одиночных или двойных кавычек в шаблоны или наоборот.
  • Использование + для разбиения длинных строковых литералов на части, которые соответствуют ширине вывода.
  • Добавление/удаление {} и return, где они необязательны.
  • Преобразование ?: в операторы if-else.
  • Сортировка/перемещение импортов, ключей объектов, членов класса, ключей JSX, свойств CSS или чего-либо еще. Помимо того, что это преобразование, а не просто вывод (как упоминалось выше), сортировка потенциально небезопасна из-за побочных эффектов (например, для импортов) и затрудняет проверку самого важного критерия – корректности.

© James Long and contributors
https://prettier.io/docs/en/rationale

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API