Настройка ESLint для чистого кода в проектах на Vue

от автора

В процессе работы над проектами разработчики придерживаются определенного кодстайла. Как правило, за это отвечает ESLint. ESLint — это линтер для языка программирования JavaScript. Он статически анализирует код на наличие проблем, многие из которых можно исправить автоматически.

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

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

Все форматирование кода будет осуществляться с помощью ESLint, поэтому если у вас установлен prettier или Vetur, желательно их отключить. ESLint необходимо установить в extensions вашей IDE, чтобы пользоваться его функционалом.

В конце статьи приведем пример файла .eslintrc.js с настройками.  

Начало работы

Создадим новое приложение. В настройках при установке выберем  ESLint + Airbnb config.

Файл .eslintrc.js должен выглядеть так:

В качестве базовых настроек используем конфигурацию @vue/airbnb, plugin:vue/essential заменим на plugin:vue/strongly-recommended (или plugin:vue/vue3-strongly-recommended, plugin:vue/vue3-essential, plugin:vue/vue3-recommended для vue 3) и дополним кастомными настройками. Если вы еще не знакомы с отличиями настроек essential от strongly-recommended, то рекомендуем изучить их здесь

Далее в объект rules будем добавлять правила, которые хотим использовать.

Правила для секции <template>

#vue/attributes-order Проверка порядка атрибутов:

<template>   <!-- ✓ GOOD -->   <div     is="header"     v-for="item in items"     v-if="!visible"     v-once     id="uniqueID"     ref="header"     v-model="headerData"     my-prop="prop"     @click="functionCall"     v-text="textContent">   </div>     <!-- ✗ BAD -->   <div     ref="header"     my-prop="prop"     v-for="item in items"     v-once     @click="functionCall"     id="uniqueID"     v-model="headerData"     v-if="!visible"     is="header"     v-text="textContent">   </div> </template>

Пример настройки:

"vue/attributes-order": ["error", {  "order": [    "DEFINITION",    "LIST_RENDERING",    "CONDITIONALS",    "RENDER_MODIFIERS",    "GLOBAL",    ["UNIQUE", "SLOT"],    "TWO_WAY_BINDING",    "OTHER_DIRECTIVES",    "OTHER_ATTR",    "EVENTS",    "CONTENT"  ],  "alphabetical": false }],

Это правило упорядочивания атрибутов компонентов. Порядок по умолчанию указан в руководстве по стилю Vue.js. и определен:

  • DEFINITION e.g. ‘is’, ‘v-is’

  • LIST_RENDERING e.g. ‘v-for item in items’

  • CONDITIONALS e.g. ‘v-if’, ‘v-else-if’, ‘v-else’, ‘v-show’, ‘v-cloak’

  • RENDER_MODIFIERS e.g. ‘v-once’, ‘v-pre’

  • GLOBAL e.g. ‘id’

  • UNIQUE e.g. ‘ref’, ‘key’

  • SLOT e.g. ‘v-slot’, ‘slot’

  • TWO_WAY_BINDING e.g. ‘v-model’

  • OTHER_DIRECTIVES e.g. ‘v-custom-directive’

  • OTHER_ATTR e.g. ‘custom-prop=»foo»‘, ‘v-bind:prop=»foo»‘, ‘:prop=»foo»‘

  • EVENTS e.g. ‘@click=»functionCall»‘, ‘v-on=»event»‘

  • CONTENT e.g. ‘v-text’, ‘v-html’

#vue/max-attributes-per-line Проверка на максимальное количество атрибутов в строке:

<template>   <!-- ✓ GOOD -->   <MyComponent lorem="1"/>   <MyComponent     lorem="1"     ipsum="2"   />     <!-- ✗ BAD -->   <MyComponent lorem="1" ipsum="2"/>   <MyComponent     lorem="1" ipsum="2"   />   <MyComponent     lorem="1" ipsum="2"     dolor="3"   /> </template>

Пример настройки:

 "vue/max-attributes-per-line": ["error", {     "singleline": {       "max": 1     },           "multiline": {       "max": 1     }   }]

Значения singleline.max (number) и multiline.max (number) установим в значение 1, чтобы каждый атрибут начинался с новой строчки.

#vue/html-self-closing Проверка на самозакрывающийся тег или компонент:

<template>   <!-- ✓ GOOD -->   <div/>   <img>   <MyComponent/>   <svg><path d=""/></svg>     <!-- ✗ BAD -->   <div></div>   <img/>   <MyComponent></MyComponent>   <svg><path d=""></path></svg> </template>
 "vue/html-self-closing": ["error", {     "html": {       "void": "never",       "normal": "always",       "component": "always"     },     "svg": "always",     "math": "always"   }]
  • html.void («never» по умолчанию) — стиль хорошо известных пустых элементов HTML.

  • html.normal («always» по умолчанию) — стиль известных элементов HTML за исключением пустых элементов.

  • html.component («always» по умолчанию) — стиль пользовательских компонентов Vue.js.

  • svg («always» по умолчанию) — стиль известных элементов SVG.

  • math («always» по умолчанию) — стиль известных элементов MathML.

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

  • «always» — требовать самозакрытия элементов, у которых нет своего содержимого.

  • «never» — запретить самозакрытие.

  • «any» — не применять самозакрывающийся стиль.

#vue/html-indent Проверка последовательного отступа в шаблоне <template>:

<template>   <!-- ✓ GOOD -->   <div class="foo">     Hello.   </div>   <div class="foo">     Hello.   </div>   <div class="foo"        :foo="bar"   >     World.   </div>   <div     id="a"     class="b"     :other-attr="{       aaa: 1,       bbb: 2     }"     @other-attr2="       foo();       bar();     "   >     {{       displayMessage     }}   </div>     <!-- ✗ BAD -->  <div class="foo">    Hello.     </div> </template>

Пример настройки:

'vue/html-indent': [  'error',  4,  {    attribute: 1,    baseIndent: 1,    closeBracket: 0,    alignAttributesVertically: true,    ignores: []  } ],
  • type (number | «tab») — тип отступа. Значение по умолчанию 2. Если это число, то это количество пробелов для одного отступа. Если это «tab», он использует одну вкладку для одного отступа.

  • attribute (integer) — множитель отступа для атрибутов. Значение по умолчанию 1.

  • baseIndent (integer) — множитель отступа для операторов верхнего уровня. Значение по умолчанию 1.

  • closeBracket (integer | object) — множитель отступа для правых скобок. Значение по умолчанию 0.

    Вы можете применить все нижеперечисленное, установив числовое значение.

    • closeBracket.startTag (integer) — множитель отступа для правых скобок открывающих тегов (<div>). Значение по умолчанию 0.

    • closeBracket.endTag (integer) — множитель отступа для правых скобок закрывающих тегов (</div>). Значение по умолчанию 0.

    • closeBracket.selfClosingTag (integer) — множитель отступа для правых скобок открывающих тегов (<div/>). Значение по умолчанию 0.

  • alignAttributesVertically (boolean) — условие того, должны ли атрибуты выравниваться по вертикали с первым атрибутом в многострочном случае или нет. По умолчанию true.

  • ignores (string[]) — селектор для игнорирования узлов. Со спецификацией AST можно ознакомиться здесь.

#vue/component-name-in-template-casing Проверка регистра для стиля именования компонентов в шаблоне:

<template>   <!-- ✓ GOOD -->   <cool-component />     <!-- ✗ BAD -->   <CoolComponent />   <coolComponent />   <Cool-component />     <!-- ignore -->   <unregistered-component />   <UnregisteredComponent /> </template> <script> export default {   components: {     CoolComponent   } } </script>

Пример настройки:

"vue/component-name-in-template-casing": ["error", "kebab-case", {  "registeredComponentsOnly": true, }],
  • «PascalCase» (по умолчанию) — требует написание имен тегов в регистре паскаля. Например, <CoolComponent>. Это соответствует практике JSX.

  • «kebab-case» — требует написание имен тегов в регистре кебаба: например, <cool-component>. Это согласуется с практикой HTML, которая изначально нечувствительна к регистру.

  • registeredComponentsOnly — если true, проверяются только зарегистрированные компоненты (в PascalCase). если false, проверьте все. По умолчанию true.

  • ignores (string[]) — имена элементов, которые следует игнорировать. Устанавливает разрешающее имя элемента. Например, пользовательские элементы или компоненты Vue со специальным именем. Вы можете установить регулярное выражение, написав его как «/^name/».

#vue/no-irregular-whitespace Проверка нерегулярных пробелов:

<template>   <!-- ✓ GOOD -->   <div class="foo bar" />   <!-- ✗ BAD -->   <div class="foo bar" />   <!--           ^ LINE TABULATION (U+000B) --> </template> <script> /* ✓ GOOD */ var foo = bar; /* ✗ BAD */ var foo = bar; //       ^ LINE TABULATION (U+000B) </script>

Пример настройки:

"vue/no-irregular-whitespace": ["error", {  "skipStrings": true,  "skipComments": false,  "skipRegExps": false,  "skipTemplates": false,  "skipHTMLAttributeValues": false,  "skipHTMLTextContents": false }],
  • skipStrings: true — разрешает любые пробельные символы в строковых литералах. По умолчанию true.

  • skipComments: true — разрешает любые пробельные символы в комментариях. По умолчанию false.

  • skipRegExps: true — разрешает любые пробельные символы в литералах регулярных выражений. По умолчанию false.

  • skipTemplates: true — разрешает любые пробельные символы в литералах шаблона. По умолчанию false.

  • skipHTMLAttributeValues: true — разрешает любые пробельные символы в значениях атрибутов HTML. По умолчанию false.

  • skipHTMLTextContents: true — разрешает любые пробельные символы в текстовом содержимом HTML. По умолчанию false.

Правила для секции <script>

#vue/component-definition-name-casing Проверка на определенный регистр для имени компонента:

<script> export default {   /* ✓ GOOD */   name: 'MyComponent'   /* ✗ BAD */   name: 'my-component' } </script>

Пример настройки:

"vue/component-definition-name-casing": ["error", "PascalCase"],
  • «PascalCase» (по умолчанию) — требует преобразования имен компонентов к регистру паскаля.

  • «kebab-case» — требует преобразования имен компонентов к регистру kebab.

#vue/match-component-file-name Проверка имени компонента — оно должно соответствовать имени файла, в котором он находится:

// file name: src/MyComponent.vue export default {   /* ✓ GOOD */   name: 'MyComponent',   render() {     return <h1>Hello world</h1>   } }
// file name: src/MyComponent.vue export default {   /* ✓ GOOD */   name: 'my-component',   render() { return <div /> } }
// file name: src/MyComponent.vue export default {   /* ✗ BAD */   name: 'MComponent', // пропущена буква y   render() {     return <h1>Hello world</h1>   } }

Пример настройки:

"vue/match-component-file-name": ["error", {  "extensions": ["vue"],  "shouldMatchCase": false }],
  • «extensions»: [] — массив расширений файлов для проверки. По умолчанию установлено значение [«jsx»].

  • «shouldMatchCase»: false — логическое значение, указывающее, должно ли имя компонента также соответствовать регистру имени файла. По умолчанию установлено значение false.

#vue/no-dupe-keys Запретить дублирование имен полей:

<script> /* ✗ BAD */ export default {   props: {     foo: String    },   computed: {     foo: {  // дубликат свойства       get () {}      }   },   data: {     foo: null // дубликат свойства   },   methods: {     foo () {} // дубликат свойства   } } </script>

Пример настройки:

 "vue/no-dupe-keys": ["error", {     "groups": []   }]
  • «groups» (string[]) — массив дополнительных групп для поиска дубликатов. По умолчанию пусто.

#vue/order-in-components Порядок свойств в компонентах:

<script> /* ✗ BAD */ export default {   name: 'app',   data () {     return {       msg: 'Welcome to Your Vue.js App'     }   },   props: {// неправильный порядок свойств props перед data      propA: Number      },   methods: { // неправильный порядок свойств после computed    add() {}   },   computed: { // неправильный порядок свойств перед methods     foo () {}   } } </script>
'vue/order-in-components': ['error', {  order: [    'el',    'name',    'key',    'parent',    'functional',    ['delimiters', 'comments'],    ['components', 'directives', 'filters'],    'extends',    'mixins',    ['provide', 'inject'],    'ROUTER_GUARDS',    'layout',    'middleware',    'validate',    'scrollToTop',    'transition',    'loading',    'inheritAttrs',    'model',    ['props', 'propsData'],    'emits',    'setup',    'asyncData',    'data',    'fetch',    'head',    'computed',    'watch',    'watchQuery',    'LIFECYCLE_HOOKS',    'methods',    ['template', 'render'],    'renderError'  ] }],

#comma-dangle Проверка  запятых:

  • arrays для литералов массива и шаблонов деструктуризации массива.
    например, let [a,] = [1,];

  • objects для объектных литералов и объектных шаблонов деструктуризации.
    например, let {a,} = {a: 1};

  • imports предназначен для деклараций импорта модулей ES.
    например, import {a,} from «foo»;

  • exports для экспортных деклараций модулей ES.
    например, export {a,};

  • functions предназначен для объявлений функций и вызовов функций.
    например, (function(a,){ })(b,);

  • functions следует включать только при анализе ECMAScript 2017 или более поздней версии.

/* ✗ BAD */ var foo = {     bar: "baz",     qux: "quux", };  var arr = [1,2,];  foo({   bar: "baz",   qux: "quux", });
 /* ✓ GOOD */ var foo = {     bar: "baz",     qux: "quux" };  var arr = [1,2];  foo({   bar: "baz",   qux: "quux" });

Пример настройки:

   "comma-dangle": ["error", {         "arrays": "never",         "objects": "never",         "imports": "never",         "exports": "never",         "functions": "never"     }]
  • «never» (по умолчанию) запрещает запятые в конце.

  • «always» требует наличие запятых в конце.

  • «always-multiline» требует замыкающих запятых, когда последний элемент или свойство находятся в другой строке, а закрывающий “]” или  “}” на следующей и запрещает замыкающие запятые, когда последний элемент или свойство находится в той же строке, что и закрывающий “]” или “}”.

  • «only-multiline» разрешает (но не требует) замыкающие запятые, когда последний элемент или свойство находятся в иной строке, чем закрывающий “]” или  “}”, и запрещает замыкающие запятые, когда последний элемент или свойство находятся на той же строке, что и закрывающий “]” или “}”.

Вы также можете использовать параметр объекта, чтобы настроить это правило для каждого типа синтаксиса. Для каждого из следующих параметров можно установить значения «never», «always», «always-multiline», «only-multiline» или «ignore». Значение по умолчанию для каждого параметра равно «never», если не указано иное.

Общие настройки ESLint

'linebreak-style': ["error", "unix"], //стиль разрыва строки linebreak-style: ["error", "unix || windows"] 'no-console': 'error', // без console.log 'no-debugger': 'error',// без debugger 'arrow-parens': ['error', 'as-needed'], // скобки в стрелочной функции 'no-plusplus': 'off', //запрещает унарные операторы ++и -- 'constructor-super': 'off', // конструкторы производных классов должны вызывать super(). Конструкторы не производных классов не должны вызывать super(). "no-mixed-operators": [ //Заключение сложных выражений в круглые скобки проясняет замысел разработчика  "error",  {    "groups": [      ["+", "-", "*", "/", "%", "**"],      ["&", "|", "^", "~", "<<", ">>", ">>>"],      ["==", "!=", "===", "!==", ">", ">=", "<", "<="],      ["&&", "||"],      ["in", "instanceof"]    ],    "allowSamePrecedence": true  } ], 'import/extensions': 'off', // обеспечить согласованное использование расширения файла в пути импорта 'import/prefer-default-export': 'off', // ESLint предпочитает экспорт по умолчанию импорт/предпочитает экспорт по умолчанию 'no-unused-expressions': 'error', //нет неиспользуемых выражений 'no-param-reassign': 'off', //без переназначения параметров 'prefer-destructuring': ["error", { // требуется деструктуризация массивов и/или объектов.      "array": true,      "object": true    }, {      "enforceForRenamedProperties": false    }  ], 'no-bitwise': ['error', { allow: ['~'] }], // запрещает побитовые операторы. 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], // запрещает неиспользуемые переменные. 'max-len': ['error', { code: 120 }], // обеспечивает максимальную длину строки. 'object-curly-newline': ['error', {  ObjectExpression: { multiline: true, consistent: true },  ObjectPattern: { multiline: true, consistent: true } }], // применяет согласованные разрывы строк после открытия и перед закрытием фигурных скобок. 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }] // требует или запрещает пустую строку между членами класса.
module.exports = {  root: true,  env: {    node: true,//Указание среды Глобальные переменные Node.js и область видимости Node.js. browser- глобальные переменные браузера.  },  globals: {     var1: "writable",    var2: "readonly",    Promise: "off"  }, // настройка глобальных переменных  extends: [    'plugin:vue/strongly-recommended', //Использование общей конфигурации    '@vue/airbnb',  ],  parserOptions: {    parser: 'babel-eslint',  },  rules: {} //Настройка правил }
Окончательно файл .eslintrc.js выглядит так:
module.exports = {  root: true,  env: {    node: true,  },  extends: [    'plugin:vue/strongly-recommended',    '@vue/airbnb',  ],  parserOptions: {    parser: 'babel-eslint',  },  rules: {    "vue/html-self-closing": ["error", {      "html": {        "void": "never",        "normal": "always",        "component": "always"      },      "svg": "always",      "math": "always"    }],    'vue/html-indent': [      'error',      4,      {        attribute: 1,        baseIndent: 1,        closeBracket: 0,        alignAttributesVertically: true,        ignores: []      }    ],    "vue/max-attributes-per-line": ["error", {      "singleline": {        "max": 1      },      "multiline": {        "max": 1      }    }],    'vue/order-in-components': ['error', {      order: [        'el',        'name',        'key',        'parent',        'functional',        ['delimiters', 'comments'],        ['components', 'directives', 'filters'],        'extends',        'mixins',        ['provide', 'inject'],        'ROUTER_GUARDS',        'layout',        'middleware',        'validate',        'scrollToTop',        'transition',        'loading',        'inheritAttrs',        'model',        ['props', 'propsData'],        'emits',        'setup',        'asyncData',        'data',        'fetch',        'head',        'computed',        'watch',        'watchQuery',        'LIFECYCLE_HOOKS',        'methods',        ['template', 'render'],        'renderError'      ]    }],    "vue/no-irregular-whitespace": ["error", {      "skipStrings": true,      "skipComments": false,      "skipRegExps": false,      "skipTemplates": false,      "skipHTMLAttributeValues": false,      "skipHTMLTextContents": false    }],    "vue/component-definition-name-casing": ["error", "PascalCase"],    "vue/match-component-file-name": ["error", {      "extensions": ["vue"],      "shouldMatchCase": false    }],    "vue/no-dupe-keys": ["error", {      "groups": []    }],    "vue/component-name-in-template-casing": ["error", "kebab-case", {      "registeredComponentsOnly": true,    }],    'comma-dangle': ['error', {      arrays: 'never',      objects: 'never',      imports: 'never',      exports: 'never',      functions: 'never'    }],    'linebreak-style': ["error", "windows"],     'no-console': 'error',    'no-debugger': 'error',    'arrow-parens': ['error', 'as-needed'],    'no-plusplus': 'off',    'constructor-super': 'off',    "no-mixed-operators": [       "error",      {        "groups": [          ["+", "-", "*", "/", "%", "**"],          ["&", "|", "^", "~", "<<", ">>", ">>>"],          ["==", "!=", "===", "!==", ">", ">=", "<", "<="],          ["&&", "||"],          ["in", "instanceof"]        ],        "allowSamePrecedence": true      }    ],    'import/extensions': 'off',    'import/prefer-default-export': 'off',    'no-unused-expressions': 'error',    'no-param-reassign': 'off',    'prefer-destructuring': ["error", {           "array": true,          "object": true        }, {          "enforceForRenamedProperties": false        }      ],    'no-bitwise': ['error', { allow: ['~'] }],    'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],    'max-len': ['error', { code: 120 }],    'object-curly-newline': ['error', {      ObjectExpression: { multiline: true, consistent: true },      ObjectPattern: { multiline: true, consistent: true }    }],    'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }]  }, };

Рассмотрим на примере одного компонента, что у нас получилось. Для этого возьмем следующий компонент:

<template>    <div>        <HelloWorld v-model="headerData" is="header" v-once id="uniqueID" @click="functionCall"                    v-text="textContent" ref="header" my-prop="prop" v-for="item in items"        />    </div> </template>  <script> import HelloWorld from './HelloWorld.vue'; export default {  name: 'MyHeadergа',  components: {    HelloWorld  },  data() {    return {      array: [],      firstName: "Alex",      lastName: "Ivanov"    };  },  props: {    foo: String  },  methods: {    foo() {}  },  computed: {    fullName() {      return `${this.firstName} ${this.lastName}`;    },    reversedArray() {      return this.array.reverse(); // <- side effect - orginal array is being mutated    }  },  }; </script>

После выполнения команды eslint —fix получаем следующее:

Автоматически отформатировалось название компонента template в стиле kebab-case, атрибуты в компонентах выстроены по порядку и каждый с новой строки. Название myProps заменено на my-props в соответствии с кодстайлом vue. Появилась пустая строка 19 между секцией template и script. На 21 строке подсвечивается название компонента которое не совпадает с названием файла. Двойные кавычки при определении строковых переменных заменены на одинарные. Свойства в секции script выстроены по порядку name, components, props, data, computed, method. На строке 40 подсвечивается ошибка, так как мы создаем сайд-эффекты в вычисляемом свойстве.

Заключение

Итак, мы разобрали большое количество правил настройки ESLint, которые можно без труда скорректировать по собственному желанию. Результат в большей степени соответствует рекомендациям по стилю написания кода Vue style guide, а если настроить Auto Fix On Save, то код будет автоматически формироваться согласно этим правилам. Все это в целом приведет к написанию более качественного кода, упрощению и ускорению разработки и код ревью. 

Полезные ссылки:

Спасибо за внимание! Полезные материалы для разработчиков мы также публикуем в наших соцсетях – ВК и Telegram.


ссылка на оригинал статьи https://habr.com/ru/company/simbirsoft/blog/674036/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *