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 оставляет объекты многострочными, если между { и первым ключом в исходном коде есть перевод строки. В результате длинные однострочные объекты автоматически разворачиваются, а короткие многострочные объекты никогда не сворачиваются.

Это условное поведение можно отключить с помощью параметра objectWrap.

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

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;
}

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

Шаблонные строки могут содержать интерполяции. Решение о том, уместно ли вставлять перевод строки внутри интерполяции, к сожалению, зависит от смыслового содержимого шаблона. Например, обычно нежелательно вставлять перевод строки посреди предложения на естественном языке. Поскольку 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.
  });
});

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

JSX

При работе с JSX Prettier форматирует код немного иначе, чем другой JS:

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/rationale

Spec-Zone.ru

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