Скрипты

TypeScript, JavaScript

0. Введение

0.1. Термины и определения

0.2. Использованные источники

При подготовке документа были использованы следующие материалы:

a. Google JavaScript Style Guide

b. Макконнелл С. - Совершенный код. Мастер-класс, «Русская редакция», 2010. — 896 стр. : ил.

c. Ревью в Gitlab в рамках проекта workspace-web.

d. Airbnb JavaScript Style Guide

e. Принципы написания консистентного, идиоматического кода на JavaScript

f. C++ Guideline

g. Intellij IDEA. File | Settings | Editor | Code Style | TypeScript

h. Мартин Роберт К. - Чистый код. Создание, анализ и рефакторинг: Издательство «Питер», 2010.

1. Файлы с исходным кодом

1.1. [Не автоматизировано] Правила именования

a. Допустимые группы в имени файла: 1. Название (основная часть имени файла), которое описывает суть содержимого файла. Должно совпадать с названием класса, который содержится в файле, приведенным в соответствие с правилами именования файлов. В некоторых случаях часть имени класса должна рассматривать как содержимое группы Тип. 2. Тип, который определяет категорию, к которой относится класс. Допустимые типы: 1. enum - перечисление/псевдо-перечисление (map с константами); 2. constants - константы.

    // Плохо
    tag-type-enum.ts

    // Хорошо
    tag-type.enum.ts

b. Файлы с тестами дополняются ещё одним суффиксом spec.

    tag.service.spec.ts

1.2. [Не автоматизировано] Файлы с перечислениями

Использовать один файл перечисления под группу перечислений, объединенных общей идеей. Например, использовать один файл перечислений под перечисления, связанные с компонентом. При этом файл перечисления называть: <grouping>.enums.ts

    // Плохо
    /button
        /enums
            button-mode.enum.ts
            button-type.enum.ts
            buttom-state.enum.ts

    // Хорошо
    /button
        button.enums // Содержит все перечисления: mode, type, state

1.3. [Не автоматизировано] JSDoc в начале файла

Каждый файл после секции import'ов, если таковая имеется, должен содержать JSDoc с информацией:

a. Для чего предназначен файл/класс.

b. Автор

c. Дата создания

2. Форматирование

2.1. Скобки

2.1.1. [Автоматизировано: curly] Скобки для управляющих конструкций

Скобки необходимо писать для всех управляющих конструкций (таких как if, else, for, do, while, а также других), даже если тело конструкции содержит всего один оператор.

    // Плохо
    if (condition)
        action();
    // Плохо
    for (let i = 0; i < arr.length; i++) action(arr[i]);

    // Хорошо
    if (condition) {
        action();
    }
    // Хорошо
    for (let i = 0; i < arr.length; i++) {
        action(arr[i]);
    }

2.1.2. [Автоматизировано: brace-style] Непустые блоки

Скобки для непустых блоков должны проставляться следующим образом:

  • Открывающая фигурная скобка должна быть на той же строке, что и управляющая конструкция.

  • Перед открывающей скобкой должен быть пробел.

  • Закрывающая фигурная скобка должна быть на отдельной строке после тела управляющей

    конструкции.

  • Для составных управляющих конструкций следующая часть конструкции располагается

    на той же строке, что и закрывающая скобка.

    // Плохо
    if (condition)
    {
        // ...
    }
    else if (condition)
    {
        // ...
    }
    else
    {
        // ...
    }

    // Плохо
    if (condition){
        // ...
    }
    else {
        // ...
    }

    // Хорошо
    if (condition) {
        // ...
    } else if (condition) {
        // ...
    } else {
        // ...
    }

2.2. Отступы

2.2.1. [Автоматизировано: indent] Величина отступа

Величина отступа должна составлять 4 пробела. Не допускается использовать табуляцию.

2.2.2. [Автоматизировано: indent] Блоки и блокоподобные конструкции

Для каждого нового блока или тела блока, содержимого объектов или массивов, которые записаны в несколько строк подобно блокам, должен применяться дополнительный отступ. По завершению блока отступ должен возвращаться на предыдущий уровень.

Отступ должен применяться как для кода, так и для комментариев к коду.

    // Плохо
    if (condition) {
    action();
    }

    // Плохо
    const obj = {
      prop1: 'value' // Отступ менее 4 пробелов
    }

    // Хорошо
    if (condition) {
        action();
    }

    // Хорошо
    const obj = {
        prop1: 'value'
    }

2.2.3. [Автоматизировано: newline-per-chained-call] Цепочки вызовов

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

    // Плохо
    obj.firstMethod().secondMethod().thirdMethod();

    // Хорошо
    obj.firstMethod()
        .secondMethod()
        .thirdMethod();

2.2.4. [Автоматизировано: indent, IDEA code format] Конструкция switch-case

Секции case (default) должны иметь такой же отступ, что и блоки. Тело секции case (default) должно иметь дополнительный отступ равный стандартному для блоков.

    // Плохо
    switch (state) {
    // Нет отступа для case
    case StateType.NORMAL:
        action1();
        break;
      // Уменьшенные отступы для case и тела case
      case StateType.WARNING:
        action2();
        break;
      default:
        action3();    
    }

    // Хорошо
    switch (state) {
        case StateType.NORMAL:
            action1();
            break;
        case StateType.WARNING:
            action2();
            break;
        default:
            action3();
    }

2.3. Выражения

2.3.1. [Автоматизировано: max-statements-per-line, IDEA code format] В одной строке должно быть не более одного выражения

  1. Каждое выражение должно располагаться на своей строке. Не должно быть несколько

    выражений на одной и той же строке. Выражением называется совокупность переменных,

    констант, знаков операций, имен функций, скобок, которая может быть вычислена

    в соответствии с синтаксисом языка программирования. Результатом вычисления

    выражения является величина определенного типа. Признаком завершения выражения

    является ;.

    // Плохо
    resultX = orderPoint.x + shiftPoint.x; resultY = orderPoint.y + shiftPoint.y;
    calculate(min(a, b), max(a, b)); buildDataForGraph();

    // Хорошо
    resultX = orderPoint.x + shiftPoint.x; 
    resultY = orderPoint.y + shiftPoint.y;
    calculate(min(a, b), max(a, b)); 
    buildDataForGraph();
  1. Это же правило относится и к объявлению переменных.

    // Плохо
    let name, width, height;

    // Хорошо
    let name;
    let width;
    let height;

2.3.2. [Автоматизировано: @typescript-eslint/semi] Выражение обязательно завершается точкой с запятой (";")

Каждое выражение должно завершаться знаком точка с запятой ";". Не следует полагаться на автоматическую простановку этого знака или умолчания для некоторых случаев типа последней строки.

2.4. Перенос выражений

Под переносом выражений следует понимать правила переноса выражения на несколько строк.

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

Ограничение на длину даёт преимущества, например, при ревью кода или при параллельной работе с двумя файлами. Два файла в 120 символов как раз хорошо умещаются на мониторе с разрешением 1920 px без горизонтальной прокрутки, а вот строки длиннее приводят к её появлению. При работе с одним файлом длинные строки также могут доставлять неудобства, если помимо файла открыты дополнительные панели в IDE.

2.4.1. [Не автоматизировано] В каком месте разрывать выражение

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

    // Исходная строка
    let obj = { name: 'IDS_DASHBOARD_CONTROL_SCRIPT_OPTIMIZE_TRANSPORTS', planning_settings: Object.assign({}, DEFAULT_PLANNING_SETTINGS, { planning_settings: { planning_settings: { control_script: CONTROL_SCRIPTS_NAMES.OPTIMIZE_TRANSPORTS } } }) };

    // Плохо
    let obj = {
        name: 'IDS_DASHBOARD_CONTROL_SCRIPT_OPTIMIZE_TRANSPORTS',
        planning_settings: Object.assign({}, DEFAULT_PLANNING_SETTINGS, { planning_settings: {
            planning_settings: { control_script: CONTROL_SCRIPTS_NAMES.OPTIMIZE_TRANSPORTS } } })
    };

    // Хорошо
    let obj = {
        name: 'IDS_DASHBOARD_CONTROL_SCRIPT_OPTIMIZE_TRANSPORTS',
        planning_settings: Object.assign(
            {}, 
            DEFAULT_PLANNING_SETTINGS, 
            { 
                planning_settings: {
                    planning_settings: { 
                        control_script: CONTROL_SCRIPTS_NAMES.OPTIMIZE_TRANSPORTS 
                    } 
                } 
            }
        )
    }; 

    // Плохо
    calculateDraft(orders, shifts, 
        trips);

    // Хорошо
    calculateDraft(
        orders,
        shifts,
        trips
    );

2.4.2. [Автоматизировано: indent, IDEA code format] Отступ при переносе части выражения

Части выражения, которые перенесены на новые строки, т.е. каждая следующая строка после исходной (первой), должны иметь дополнительный отступ относительно неё. При этом должно учитываться правило переноса блоков, что может добавлять дополнительные отступы.

   // Плохо
   let result = builder.append('first')
   .append('second')
   .append('third');

   // Плохо
   let result = builder.append('first')
     .append('second')
     .append('third');

   // Хорошо
   let result = builder.append('first')
       .append('second')
       .append('third');

2.4.3. [Не автоматизировано] Параметры функции

a. Предпочтительно размещать параметры функции на той же строке, что и имя функции.

    function someFunction(param1, param2, param3) {
        // ...
    }

b. Если при способе записи п.1 будет превышен лимит на длину строки, то необходимо перенести все параметры по одному на каждую строку, сделав для них один горизонтальный отступ.

    function someFunction(
        param1,
        param2,
        // ...,
        paramK,
        ...
        paramN
    ) {
        // ...
    }

2.4.4. [Не автоматизировано] Тернарный оператор

a. Допустимо использовать тернарный оператор только в выражениях, в ином случае использовать if.

    // Плохо
    entity.transportType === TransportType.PLANE ? this.onActionPerformed(action) : this.finalizeTrip(action);


    // Хорошо
    if (entity.transportType === TransportType.PLANE) {
        this.onActionPerformed(action);
        return;
    }

    this.finalizeTrip(action);

    // Хорошо
    return entity.transportType === TransportType.PLANE ? this.onActionPerformed(action) : this.finalizeTrip(action);

b. Недопустимо переносить тернарный оператор на несколько строк. Если возникает такая необходимость, то нужно переписать тернарный оператор на оператор if с учётом правил 4.9.5.

    // Плохо
    count = action.hasIndex
        ? action.index : action.type.icon;
    // Плохо    
    action.timeTo.prepared.time = action.timeTo.raw
        ? formatTime(action.time_to.raw, 'time') : null;


    // Хорошо
    count = action.hasIndex ? action.index : action.type.icon;
    // Хорошо
    action.timeTo.prepared.time = null;
    if (action.timeTo.raw) {
        action.timeTo.prepared.time = formatTime(action.time_to.raw, 'time');
    }

2.4.5. [Не автоматизировано] Перенос длинных математических выражений

Если имеем дело с длинным математическим выражением, то необходимо разбить его на несколько, выделив логические части. Математические выражения не должны переноситься на несколько строк.

    // Плохо
    const entropy = forceFactor 
        * jobFactor + diffFactor / (directionMultiplier * chaosPortion);

    // Хорошо
    const aggregatedFactor = forceFactor * jobFactor;
    const diffusion = diffFactor / (directionMultiplier * chaosPortion); 
    const entropy = aggregatedFactor + diffusion;

2.4.6. [Не автоматизировано, но есть в IDEA code format] Перенос массивов

a. Не нужно переносить строку с массивом, если она не превышает лимит. Исключение составляет массив, в котором элементы представлены объектными литералами, количество которых превышает единицу.

    // Плохо
    scenarios = [
        'a',
        'b',
        'c'
    ];
    arrWithObjects = [{ id: 1, field: '' }, { id: 2, field: '' }];

    // Плохо
    arrWithObject = [
        { id: 1, field: '' }
    ];

    // Хорошо
    scenarios = ['a', 'b', 'c'];
    arrWithObjects = [
        { id: 1, field: '' },
        { id: 2, field: '' }
    ];

    // Хорошо
    arrWithObject = [{ id: 1, field: '' }];

b. При необходимости переноса массива объектов каждый объект начинается и заканчивается на своей строке, т.е. не имеет соседства с массивом или другим объектом.

    // Плохо
    const objectInArray = [{
        id: 1,
        field: ''
    }, {
        id: 2,
        field: ''
    }];
    const objectInArray = [
        { id: 1, field: ''}, { id: 2, field: '' }
    ];

    // Хорошо
    const objectInArray = [
        { id: 1, field: '' },
        { id: 2, field: '' }
    ];

2.4.7.[Не автоматизировано] Перенос полей объектных литералов

Поля объектного литерала переносятся, если:

  1. Их количество более 3.

  2. Поля в качестве значения имеют выражение, функцию (если её длина больше 20 символов и она всего 1 в строке),

    объект (если он не пустой), массив (с больше, чем 1 объектов).

  3. Длина содержимого объекта (все поля + их значения) превышает 70 символов.

  4. Длина неперенесённого объекта превышает установленный лимит строки.

    // Плохо
    actualScenarioRun = {
        startTime: 0
    };

    // Хорошо
    actualScenarioRun = { startTime: 0 };

    // Плохо
    store = { fruits: ['apple', 'orange'], price: 3000 };

    // Хорошо
    store = { prices: [2000] };

    // Плохо
    store = { fruits: { some: 'value' }, another: 3000 };

    // Хорошо
    store = { prices: {} };

    // Плохо
    store = { fruits: resolve(), vegetables: jest.fn(), another: 3000 };

    // Плохо
    store = { fruits: some.big.function.for.resolve(), another: 3000 };

    // Хорошо
    store = { prices: jest.fn(), another: 3000 };

    // Плохо
    actualScenarioRun = { startTime: 0, endTime: 1, param1: 1,  param1: 2 };

    // Хорошо
    actualScenarioRun = { 
        startTime: 0,
        endTime: 1, 
        param1: 1,
        param1: 2
    };

    // Плохо
    return { entityId: data.entityId, fieldsToUpdate: { [data.relatedProperty]: value } };

    // Хорошо (предпочтительный вариант)    
    const fieldsToUpdate = { [data.relatedProperty]: value };
    return { entityId: data.entityId, fieldsToUpdate };

    // Хорошо (допустимый вариант)
    return { 
        entityId: data.entityId, 
        fieldsToUpdate: { [data.relatedProperty]: value } 
    };

2.4.8. [Не автоматизировано] Перенос функций, переданных в качестве параметров

a. Если стрелочная функция является единственным параметром функции, но целиком не помещается в строку, то стрелочную функцию нужно записать с использованием блочной конструкции. При этом закрывающая фигурная скобка } будет на той же строке, что и закрывающая круглая скобка ) функции, в которую передаются параметры.

    // Плохо
    orders.forEach(order => {
        order.getMapEntities()
            .forEach(
                eventWindow => map.add(new GeoJsonEventFeature(eventWindow))
            );
    });

    // Хорошо, если помещается в строку
    orders.forEach(order => {
        order.getMapEntities()
            .forEach(eventWindow => map.add(new GeoJsonEventFeature(eventWindow)));
    });

    // Хорошо, если не помещается в строку
    orders.forEach(order => {
        order.getMapEntities()
            .forEach(eventWindow => {
                map.add(new GeoJsonEventFeature(eventWindow))
            });
    });

b. Если параметры функции переносятся, то должны переноситься все параметры, включая стрелочную функцию. При этом закрывающая круглая скобка ) функции, в которую передаются параметры, должна быть на отдельной строке.

    // Плохо
    calculateDraft(orders, shifts, 
        trips, () => {
            // какой-то обработчик
        });

    // Хорошо
    calculateDraft(
        orders,
        shifts,
        trips,
        () => {
            // какой-то обработчик
        }
    );

    // Плохо
    calculateDraft(orders, shifts, trips, () => {
        // какой-то обработчик
    }, actions);

    // Хорошо
    calculateDraft(
        orders,
        shifts,
        trips,
        () => {
            // какой-то обработчик
        },
        actions
    );

c. Если после стрелочной функции есть ещё какие-то параметры, то необходимо переносить все параметры.

    // Плохо
    calculateDraft(orders, shifts, trips, () => /*...,*/ actions);

    // Хорошо
    calculateDraft(
        orders,
        shifts,
        trips,
        () => /*...,*/
        actions
    );

2.4.9. [Не автоматизировано] Перенос цепочек методов

a. [Автоматизировано: newline-per-chained-call] Выражение должно переноситься по точке.

   // Плохо
   let result = builder.append('first').
       append('second').
       append('third');

   // Хорошо
   let result = builder.append('first')
       .append('second')
       .append('third');

b. [Не автоматизировано] Если в цепочке вызовов есть метод, параметры которого переносятся на несколько строк, то следует эту цепочку разбивать на несколько переменных.

    // Плохо
    this.routeSubscription = this.router.events.pipe(
        filter(/*...*/),
        map(/*...*/)
    )
        .subscribe(/*...*/);

    // Хорошо
    const routerEvents$ = this.router.events.pipe(
        filter(/*...*/),
        map(/*...*/)
    );

    this.routeSubscription = routerEvents$.subscribe(/*...*/);

2.4.10. [Автоматизировано: comma-style] Перенос запятой при переносе выражений

Если выражение переносится по запятой, то запятую на новую строку переносить не нужно.

    // Плохо
    let arr = [
        'one'
        , 'second'
        , 'thrid'
    ];

    // Хорошо
    let arr = [
       'one',
       'second',
       'thrid'
    ];

2.4.12. Что если нет правила под ситуацию, с которой столкнулись?

В этом случае избежать длинной строки и соответственно переноса строк следует путём выделения части выражения в переменную.

Если ситуация кажется довольно общей, то следует предложить правило, которым можно дополнить настоящее руководство по стилю кода.

2.5. Пробельные места

2.5.1. [Не автоматизировано] Пробельные места в вертикальном направлении

Не должно быть множественных пустых строк подряд. Допускается использование только одной для логического разделения частей кода.

Одиночная пустая строка должна быть использована в следующих случаях:

a. [Не автоматизировано] Перед конструктором и любым методом класса.

    class SomeClass {

        constructor(data) {
            // ...
        }

        method1() {
            // ...
        }

        method2() {
            // ...
        }
    }

b. Внутри тела метода для логической группировки выражений.

    // Хорошо
    function drawPicture() {
        subjectPoints = generatePointsForSubject();
        draw(subjectPoints);

        manPoints = generatePointsForMan();
        draw(manPoints);
    }

c. [Не автоматизировано] После закрывающей фигурной скобки }, если за ней не следует строка, на которой только закрывающая фигурная скобка }, или эта строка не является последней.

    // Плохо
    if (condition()) {
    }
    if (condition()) {
    }

    // Плохо
    class SomeClass {

        method() {
            // ...
            if (condition()) {
                // ...
            }

        }

    }

    // Хорошо
    if (condition()) {
    }

    if (condition()) {
    }

    // Хорошо
    class SomeClass {

        method() {
            // ...
            if (condition()) {
                // ...
            }            
        }        
    }

Пустой строки не должно быть в следующих случаях:

a. [Не автоматизировано] Перед строкой с закрывающей скобкой, завершающей функцию, метод, конструктор, объект, массив, управляющую конструкцию (if-else, for, while и т.д.).

b. [Не автоматизировано] В начале функции (метода). Исключение составляет только добавление пустой строки для вложенной функции, которые встречаются при написании тестов.

c. [Не автоматизировано] Между последовательно идущими объявлениями полей класса.

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

    // Плохо
    function printResults() {
        // ...
        // Отделено логирование, которое непосредственно 
        // связано с предыдущей строкой кода
        sum = calculate(param1, param2);

        logger.log('sum: ', sum);
    }

    function prepareReportData(rawData) {
        // Логирование не отделено, т.к. следующий 
        // за логированием код не связан с ним
        logger.log('printResults, start');
        logger.log('printResults, raw data:', rawData);
        sum = calculate(param1, param2);
        // ...
    }

    // Хорошо
    function printResults() {
        // ...
        sum = calculate(param1, param2);
        logger.log('sum: ', sum);
    }

    function prepareReportData(rawData) {
        logger.log('printResults, start');
        logger.log('printResults, raw data:', rawData);

        sum = calculate(param1, param2);
        // ...
    }

2.5.2. [Автоматизировано] Пробельные места в горизонтальном направлении

Горизонтальные пробельные места подразделяются на следующие виды:

  • в начале строки;

  • в середине строки;

  • в конце строки.

Пробельные места в начале строки определяются правилами для отступов.

Пробельных мест в конце строки быть не должно. Строка не должна иметь пробелов в конце.

Внутри строки допускается использование только одиночных пробелов. Исключение составляют только строковые литералы.

Одиночный пробел должен использоваться в следующих случаях:

a. [Автоматизировано: keyword-spacing] Отделение ключевого слова (типа if, for, catch) от открывающей скобки ( (, {), которая после него на этой же строке.

b. [Автоматизировано: keyword-spacing] Отделение ключевого слова (типа else, catch) от закрывающей скобки (}), которая предшествует ему на этой же строке.

c. [Автоматизировано: space-before-blocks] Перед открывающей фигурной скобкой ({), за исключением 1. Первого аргумента при вызове функции и первого элемента в массиве: func({ a: [{ c: d }] }). 2. В шаблонных строках: Some param: ${param}.

d. [Автоматизировано: space-infix-ops] По обеим сторонам любого оператора с двумя аргументами a + b и тернарного оператора condition ? statement1 : statement2

e. [Автоматизировано: comma-spacing] После запятой ,, если она не является последним символов строки.

f. [Автоматизировано: key-spacing] После двоеточия : и внутри фигурных скобок в объектных литералах: { a: 10 }.

g. [Автоматизировано: spaced-comment] После знака одиночного комментария: // Комментарий

Пробел не допускается использовать:

a. До запятой , или точки с запятой ;.

b. Внутри литерала массива после [ и перед ]: [1, 20, 4].

2.5.3. [Автоматизировано: key-spacing] Горизонтальное выравнивание

Горизонтальное выравнивание - это добавление дополнительного количества пробелов, чтобы элементы соседних строк были выравнены в логические колонки.

Горизонтальное выравнивание не должно использоваться, т.к. его поддержка требует дополнительных усилий.

Например,

   // Плохо
   object = {
       short:        10,
       longProperty: 175
   }

   // Хорошо
   object = {
       short: 10,
       longProperty: 175
   }

2.6. [Автоматизировано: no-extra-parens] Использование скобок для группировки

Имеет смысл добавлять скобки для группировки частей выражения в том случае, если порядок выполнения операторов неочевиден и для правильного прочтения выражения нужно обратиться к таблице приоритета операторов.

Не нужно использовать скобки:

a. Для выделения условия в тернарном операторе: condition ? statement1 : statement2.

b. Вокруг всего выражения при использовании delete, typeof, instanceof, void, return, throw, case, in, of, yield.

    // Плохо
    result = (checkSomeCondition(params) || (checkAnotherCondition(anotherParams)));

    // Хорошо
    result = checkSomeCondition(params) || checkAnotherCondition(anotherParams)

2.7. Комментарии

Раздел не включает правила для комментариев JSDoc.

2.7.1. [Не автоматизировано] Блочные комментарии

Не нужно использовать блочные комментарии для написания комментариев к коду. Используются только для JSDoc.

   // Плохо
   /*
    * Multiline comment that
    * describes tricky moments of this code
    */
   width = Math.round(data.width);

   // Хорошо
   // Multiline comment that
   // describes tricky moments of this code
   width = Math.round(data.width);

2.7.2. [Не автоматизировано] Отступы

a. [Не автоматизировано] Комментарий должен иметь тот же отступ, что и код, к которому он относится.

    // Плохо

// Get maximum price among orders
    for (let order of orders) {
    // Additional comment
        const a = 1;
    }

    // Хорошо

    // Get maximum price among orders
    for (let order of orders) {
        // Additional comment
        const a = 1;
    }

b. [Автоматизировано: spaced-comment] Текст комментария должен быть отделен от знака комментария одиночным пробелом.

    //Плохо

    // Хорошо

2.7.3. [Автоматизировано: line-comment-position] Не использовать комментарии в конце строки

Комментарий должен быть на отдельной строке над тем участком кода, что он поясняет. Не допускается использовать комментарий на той же строке, что и строка кода.

2.7.4. [Автоматизировано: capitalized-comments] Используемый регистр

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

2.7.5. [Не автоматизировано] Используемый язык

При написании комментариев необходимо использовать английский язык.

2.7.6 [Не автоматизировано] Сокращения

Недопустимо использовать сленговые сокращения в комментариях, например bcz, btw, а использовать полные формы because, by the way.

2.8. Классы

2.8.1. [Автоматизировано: @typescript-eslint/member-ordering] Порядок полей, методов

Поля и методы должны располагаться в следующем порядке:

a. Статические поля (static fields)

b. Поля экземпляра (instance fields)

c. Конструкторы (constructor)

d. Аксессоры (getter/setter)

e. Статические методы (static methods)

f. Методы экземпляра (instance methods)

Внутри каждой из групп порядок определяется уровнем доступа метода/свойства:

a. Публичные (public)

b. Защищенные (protected)

c. Приватные (private)

Группы полей отделять друг от друга пустой строкой.

    class SomeClass {
        static field;
        protected static field;
        private static field;

        field;
        protected field;
        private field;

        constructor(){
        }

        get() {}
        set() {}
        protected get() {}
        protected set() {}
        private get() {}
        private set() {}

        static method() {}
        protected static method() {}
        private static method() {}

        method() {}
        protected method() {}
        private method() {}
    }

2.8.2. [Не автоматизировано] Порядок методов одного типа и уровня доступа

a. Код должен читаться сверху вниз. Вызываемый метод должен находиться под вызывающим.

    class SomeClass {

        private run(): void {
            // ...
            this.buy();
            // ...
        }

        private buy(): void {
            // ...
            const clientInfo = this.getClientInfo();
            // ...
        }

        private getClientInfo(): ClientInfo {
            // ...
            return clientInfo;
        }
    }

b. Если несколько методов расположены друг за другом, то сначала идёт цепочка от первого метода, затем от второго и т.д.

    class SomeClass {

        private run(): void {
            // ...
            this.buy();
            this.runPostActions();
            // ...
        }

        private buy(): void {
            // ...
            const clientInfo = this.getClientInfo();
            // ...
        }

        private getClientInfo(): ClientInfo {
            // ...
            return clientInfo;
        }

        private runPostActions(): void {
            // ...
            this.removeOrders();
            this.sendNotification();
            // ...
        }

        private removeOrders(): void {
            // ...
        }

        private sendNotification(): void {
            // ...
        }
    }

2.8.3. [Не автоматизировано] Группировка аксессоров (get/set)

Объявления getter/setter'ов должны идти друг за другом, если имеется и getter, и setter для одного поля/назначения. При этом getter должен предшествовать setter'у.

    class User {
        // ...

        get id() {
            // ...
        }        

        get name() {
            // ...
        }

        set name(value) {
            // ...
        }

        get surname() {
            // ...
        }

        set surname(value) {
            // ...
        }
    }

2.9. Спецификации

2.9.1. [Не автоматизировано] Вертикальные отступы

Спецификация по смыслу разбивается на 3 секции:

  • given - (необязательная) предусловия, подготовка для выполнения проверяемого сценария;

  • when - вызов проверяемого метода;

  • then (expect) - проверка результата выполнения метода.

Необходимо секции отделять друг от друга одним вертикальным отступом. Внутри каждой из секций дополнительные вертикальные отступы запрещены.

Исключение составляет спецификация, в которой не более 3 строк. В этом случае считается, что каждая из строк представляет собой отдельную секцию. При этом эти секции не разделяются вертикальным отступом.

Допустимо совмещать секции when и then, если для проверки метода достаточно одной проверки expect.

    // Плохо

    // Лишний отступ
    it('should get access to login page if user is not logged in', async () => {
        mockValidate(false);

        expect(await service.canActivate(mockActivatedRouteSnapshot, mockRouterStateSnapshot)).toBeTruthy();
    });

    // Лишний отступ
    it('should redirect to first accessible page if user is logged in', async () => {
        const config = {
            data: { hidden: true },
            reports: {}
        } as Config;

        mockConfig(config);

        await service.canActivate(mockActivatedRouteSnapshot, mockRouterStateSnapshot);

        expect(mockRouter.navigate).toBeCalledWith(['reports']);
    });

    // Хорошо

    it('should get access to login page if user is not logged in', async () => {
        mockValidate(false);
        expect(await service.canActivate(mockActivatedRouteSnapshot, mockRouterStateSnapshot)).toBeTruthy();
    });

    it('should redirect to first accessible page if user is logged in', async () => {
        const config = {
            data: { hidden: true },
            reports: {}
        } as Config;
        mockConfig(config);

        await service.canActivate(mockActivatedRouteSnapshot, mockRouterStateSnapshot);

        expect(mockRouter.navigate).toBeCalledWith(['reports']);
    });

3. Использование возможностей языка

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

3.1. Переменные

3.1.1. [Автоматизировано: no-var] Использовать const и let

a. Для объявления переменных не должно использоваться ключевое слово var.

b. Константы объявлять ключевым словом const.

c. По умолчанию для переменных также использовать ключевое слово const, которое указывает что переменная далее не меняется. Использовать let только в тех случаях, когда переменная должна изменять своё значение.

d. Если переменной присваивается объект или массив и при этом свойства объекта или элементы массива изменяются, но сама переменная более не переприсваивается, то следует использовать для объявления const. Нужно понимать, что при этом сама переменная не меняется, т.к. в ней ссылка на объект, а значения свойств объекта меняются по этой ссылке.

3.1.2. [Не автоматизировано, но есть в IDEA code format] Объявлять по одной переменной за один раз

Должно быть не более одного объявления переменной на одной строке, не нужно объявлять несколько переменных через запятую.

   // Плохо
   let width, height;

   // Хорошо
   let width;
   let height;

3.1.3. [Не автоматизировано] Объявлять ближе к месту использования

Локальные переменные не должны объявляться в самом начале блока, которым они ограничены (например, в начале метода). Они должны объявляться максимально близко к месту их первого использования. Это упрощает чтение кода и сужает область использования переменной, зачастую включая и область видимости.

    // Плохо
    function method() {
        let counter;

        // ... код, который не использует переменную counter

        counter = getCurrentCount(); 
    }

    // Хорошо
    function method() {
        // ... код, который не использует переменную counter

        let counter = getCurrentCount(); 
    }

3.1.4. [Не автоматизировано] Не следует объявлять лишние переменные

Не нужно объявлять переменные в следующих случаях:

a. Если имя переменной и свойство используемого объекта один в один определяют имя переменной. В этом случае нужно просто использовать свойство объекта.

    // Плохо
    const currentDraftId = currentDraft.id;
    console.log(currentDraftId);

    // Хорошо
    console.log(currentDraft.id);

b. Строковый литерал используется только в одном месте и сам по себе является говорящим:

    // Плохо
    const partStateSolution = 'solution';
    const partStateReports = 'reports';
    const partStateMonitoring = 'monitoring';

    if (state.includes(partStateSolution)) {
        range = getSolutionRange();
    } else if (state.includes(partStateReports)) {
        range = getReportRange();
    } else if (state.includes(partStateMonitoring)) {
        range = getMonitoringRange();
    }

    // Хорошо
    if (state.includes('solution')) {
        range = getSolutionRange();
    } else if (state.includes('reports')) {
        range = getReportRange();
    } else if (state.includes('monitoring')) {
        range = getMonitoringRange();
    }

3.1.5. [Не автоматизировано] Одна обязанность у переменной

Если значение переменной перезаписывается, она не должна изменять свой смысл, т.е. одна переменная должна иметь одну обязанность.

    // Плохо
    let width = calculateWidth();
    doActionWithWidth(width);

    width = calculateHeight();
    doActionWithHeigh(width);

    // Хорошо
    const width = calculateWidth();
    doActionWithWidth(width);

    const height = calculateHeight();
    doActionWithHeigh(height);

3.1.6. [Не автоматизировано] Объявление переменной с присваиванием ей значения по умолчанию

a. Если нам необходимо объявить переменную, например массива какого-то типа или объекта какого-то интерфейса, то в этом случае тип переменной прописывается как в случае с обычным объявлением переменной сразу после неё, а не через утверждение типа, использую as:

    // Плохо
    const allowedEntityTypes = [] as string[];
    const someObject = {} as SomeInterface;

    // Хорошо
    const allowedEntityTypes: string[] = [];
    const someObject: SomeInterface = {};

b. Если задаём значение переменной, которое однозначно определяет тип переменной, то явно тип указывать не нужно:

    // Плохо
    const stringVariable: string = 'some string value';
    const numericVariable: number = 17;
    const map: Map<EntityId, Entity> = new Map<EntityId, Entity>();
    const map: Map<EntityId, Entity> = new Map();

    // Хорошо
    const stringVariable = 'some string value';
    const numericVariable = 17;
    const map = new Map<EntityId, Entity>();

3.2. Массивы

3.2.1. [Автоматизировано: comma-dangle] Использование запятой для последнего элемента

За последним элементом не должно быть запятой.

   // Плохо
   const values = [
      'first',
      'second',
   ];

   // Хорошо
   const values = [
      'first',
      'second'
   ];

3.2.2. [Не автоматизировано. Не работает правило no-array-constructor] Избегать использования конструктора Array

Вместо конструктора Array следует использовать литерал [], если нужно создать массив с конкретными элементами. Это вызвано тем, что, во-первых, короче запись, во-вторых, меньше риск ошибиться, т.к. при передаче одного числового аргумента в Array он создаст массив с указанным количеством элементов, которые будут иметь значение undefined.

   // Плохо

   const arr1 = new Array(x1, x2, x3);
   // Если x1 типа number, то будет создан массив с количеством элементов x1
   const arr2 = new Array(x1);
   const arr3 = new Array();

   // Хорошо

   const arr1 = [x1, x2, x3];
   const arr2 = [x1];
   const arr3 = [];

3.2.3. [Не автоматизировано] Оператор "spread"

a. Использовать spread-оператор вместо конструкций с Array.prototype. Например, это актуально при работе с коллекцией аргументов функции arguments, которая не является массивом и ей недоступны методы массива.

    // Плохо
    Array.prototype.splice.call(arguments, 0, 0, basePrefix + prefix);

    // Хорошо
    modifiedArgs = [basePrefix + prefix, ...arguments];

b. При копировании массива предпочтение отдавать spread-оператору [...arr], нежели методу arr.slice().

c. При объединении массивов предпочтение отдавать методу arr.concat(arrAnother), нежели spread-оператору [...arr, ...arrAnother].

d. После spread-оператора не должно быть пробела.

Примечание. По производительности spead-оператор примерно в 2 раза уступает методам Array.

3.3. Объекты

3.3.1. [Автоматизировано: comma-dangle] Использование запятой после последнего свойства

Не должно быть запятой , после последнего свойства, т.е. между свойством и закрывающей фигурной скобкой }.

    // Плохо
    const obj = {
       prop1: 'val',
       prop2: 'val',
    }

    // Хорошо
    const obj = {
        prop1: 'val',
        prop2: 'val'
    }

3.3.2. [Не автоматизировано. Не работает правило no-new-object] Использование конструктора Object

Не нужно использовать конструктор Object для создания объекта, следует использовать литерал {}.

    // Плохо
    const obj = new Object({ 
        a: 1, 
        b: 2 
    });

    // Хорошо
    const obj = { 
        a: 1, 
        b: 2 
    };

3.3.3. [Не автоматизировано] Кавычки для свойств объекта

Не нужно использовать кавычки при задании свойств объекта.

    // Плохо
    const obj = { 
        'a': 1, 
        'b': 2 
    };

    // Хорошо
    const obj = { 
        a: 1, 
        b: 2 
    };

Называть свойства объекта следует так, чтобы не требовалось использовать кавычки для обращения к свойству объекта.

3.3.4. [Не автоматизировано] Вычисляемые названия свойств объекта

Избегать создания вычисляемых полей у объекта: { ['key' + calculateKey()]: 'value' }.

3.3.5. [Не автоматизировано] Объявление методов

В объектных литералах не должны объявляться методы. Вместо этого должен быть написан класс, на основе которого уже могут быть созданы конкретные экземпляры с желаемыми методами.

    // Плохо
    getObject() {
        return {
            prop: 'value',
            method() {
                return this.prop;
            }
        }
    }

    // Хорошо
    class SomeClass {

        constructor() {