How to resolve eslint(vue/html-closing-bracket-newline) conflict

Viewed 6736

On save, VSCode is fixing eslint is fixing all the rules. How to fix the below conflict?

Expected Indentation

Expected Indentation

Unexpected Indentation

Unexpected Indentation

VScode Plugins in use:

 [
    "formulahendry.auto-close-tag",
    "msjsdiag.debugger-for-chrome",
    "hookyqr.beautify",
    "mikestead.dotenv",
    "dbaeumer.vscode-eslint",
    "donjayamanne.githistory",
    "eamodio.gitlens",
    "sidthesloth.html5-boilerplate",
    "ecmel.vscode-html-css",
    "abusaidm.html-snippets",
    "wix.vscode-import-cost",
    "lonefy.vscode-js-css-html-formatter",
    "eg2.vscode-npm-script",
    "christian-kohler.npm-intellisense",
    "sibiraj-s.vscode-scss-formatter",
    "octref.vetur",
    "blanu.vscode-styled-jsx",
    "jcbuisson.vue",
    "hollowtree.vue-snippets",
    "wscats.vue",
    "sdras.vue-vscode-snippets",
    "dariofuzinato.vue-peek",
]

Error

Error

Here is the config in use:

 'vue/html-closing-bracket-newline': [
  'error',
  {
    singleline: 'never',
    multiline: 'never'
  }
],
'indent': ['error', 2],
'vue/html-indent': ['error', 2],
'vue/script-indent': ['error', 2],
'vue/multiline-html-element-content-newline': 0

VSCode settings

 {
    "editor.formatOnSave": true,
    "[javascript]": {
        "editor.formatOnSave": true
    },
    "eslint.alwaysShowStatus": true,
    "files.autoSave": "onFocusChange",
    "emmet.includeLanguages": {
        "javascript": "javascriptreact",
        "vue-html": "html",
        "plaintext": "jade",
        "edge": "html"
    },
    "emmet.syntaxProfiles": {
        "javascript": "jsx"
    },
    "emmet.triggerExpansionOnTab": true,
    "emmet.showSuggestionsAsSnippets": true,
    "files.associations": {
        "*.js": "javascriptreact"
    },
    "editor.fontSize": 14,
    "git.enableSmartCommit": true,
    "git.confirmSync": false,
    "search.exclude": {
        "**/.git": true,
        "**/node_modules": true,
        "**/bower_components": true,
        "**/tmp": true,
        "**/.bin": true,
        "**/.next": true,
        "**/__snapshots__/**": true,
        "**/coverage/**": true,
        "**/report/**": true
    },
    "javascript.updateImportsOnFileMove.enabled": "always",
    "explorer.confirmDragAndDrop": false,
    "explorer.confirmDelete": false,
    "diffEditor.ignoreTrimWhitespace": false,
    "workbench.editor.enablePreviewFromQuickOpen": false,
    "files.exclude": {
        ".next": true,
        "*.log": true,
        "**/__pycache__": true,
        "**/node_modules": true,
        "**/o": true,
        "dist": true,
        "geckodriver.log": true,
        "package-lock.json": true,
        "yarn.lock": true
    },
    "window.zoomLevel": 1,
    "editor.find.globalFindClipboard": true,
    "editor.fontLigatures": true,
    "editor.formatOnType": true,
    "team.showWelcomeMessage": false,
    "git.autofetch": true,
    "workbench.startupEditor": "newUntitledFile",
    "editor.codeActionsOnSave": {
        // For ESLint
        "source.fixAll.eslint": true,
        // For TSLint
        "source.fixAll.tslint": true,
        // For Stylelint
        "source.fixAll.stylelint": true
    },
    "launch": {},
    "workbench.colorCustomizations": {},
    "javascript.validate.enable": true,
    "javascript.suggestionActions.enabled": false,
    "editor.insertSpaces": false,
    "editor.detectIndentation": false,
    "prettier.disableLanguages": [],
    "vetur.format.defaultFormatter.js": "vscode-typescript",
    "vetur.format.defaultFormatter.html": "js-beautify-html",
    "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
    "eslint.validate": [
        "vue",
        "html",
        "javascript",
        "typescript",
        "javascriptreact",
        "typescriptreact"
    ]
}
  • Multiline formatting should be retained, but the closing bracket should be in the same line itself as expected from the image.
  • There should be no conflict between eslint and vscode settings
  • Require necessary vscode settings. and eslint settings for vue setup Also, the sass files should not get affected with alignments every time on pre-commit checks.
  • It will be good if tab alignments are considered instead of 2 spaces for vue, js and sass files
4 Answers

4 space/tab is a pain and its a challenge to fix. Hence sticking to 2 space along with few changes in settings to match my requirement.

.vscode/settings.json

{
  "editor.formatOnSave": true,
  "[javascript]": {
    "editor.tabSize": 4,
    "editor.insertSpaces": true
  },
  "[vue]": {
    "editor.tabSize": 4,
    "editor.insertSpaces": true
  },
  "eslint.alwaysShowStatus": true,
  "files.autoSave": "onFocusChange",
  "emmet.includeLanguages": {
    "javascript": "javascriptreact",
    "vue-html": "html",
    "plaintext": "jade",
    "edge": "html"
  },
  "emmet.syntaxProfiles": {
    "javascript": "jsx"
  },
  "emmet.triggerExpansionOnTab": true,
  "emmet.showSuggestionsAsSnippets": true,
  "files.associations": {
    "*.js": "javascriptreact"
  },
  "editor.fontSize": 14,
  "git.enableSmartCommit": true,
  "git.confirmSync": false,
  "search.exclude": {
    "**/.git": true,
    "**/node_modules": true,
    "**/bower_components": true,
    "**/tmp": true,
    "**/.bin": true,
    "**/.next": true,
    "**/__snapshots__/**": true,
    "**/coverage/**": true,
    "**/report/**": true
  },
  "javascript.updateImportsOnFileMove.enabled": "always",
  "explorer.confirmDragAndDrop": false,
  "explorer.confirmDelete": false,
  "diffEditor.ignoreTrimWhitespace": false,
  "workbench.editor.enablePreviewFromQuickOpen": false,
  "files.exclude": {
    ".next": true,
    "*.log": true,
    "**/__pycache__": true,
    "**/node_modules": true,
    "**/o": true,
    "dist": true,
    "geckodriver.log": true,
    "package-lock.json": true,
    "yarn.lock": true
  },
  "window.zoomLevel": 1,
  "editor.find.globalFindClipboard": true,
  "editor.fontLigatures": true,
  "editor.formatOnType": true,
  "team.showWelcomeMessage": false,
  "git.autofetch": true,
  "workbench.startupEditor": "newUntitledFile",
  "editor.codeActionsOnSave": {
    // For ESLint
    "source.fixAll.eslint": true,
    // For TSLint
    "source.fixAll.tslint": true,
    // For Stylelint
    "source.fixAll.stylelint": true
  },
  "launch": {},
  "workbench.colorCustomizations": {},
  "javascript.validate.enable": true,
  "javascript.suggestionActions.enabled": false,
  "editor.insertSpaces": false,
  "editor.detectIndentation": false,
  "prettier.disableLanguages": [],
  "vetur.format.defaultFormatter.html": "prettyhtml",
  "vetur.format.defaultFormatter.css": "prettier",
  "vetur.format.defaultFormatter.postcss": "prettier",
  "vetur.format.defaultFormatter.scss": "prettier",
  "vetur.format.defaultFormatter.less": "prettier",
  "vetur.format.defaultFormatter.stylus": "stylus-supremacy",
  "vetur.format.defaultFormatter.js": "prettier",
  "vetur.format.defaultFormatter.ts": "prettier",
  "vetur.format.defaultFormatter.sass": "sass-formatter",
  "vetur.validation.template": true,
  "vetur.format.defaultFormatterOptions": {
    "prettyhtml": {
      "printWidth": 100, // No line exceeds 100 characters
      "singleQuote": false // Prefer double quotes over single quotes
    }
  },
  "javascript.format.insertSpaceBeforeFunctionParenthesis": true,
  "eslint.validate": ["javascript", "javascriptreact", "html", "vue"],
  "eslint.options": {
    "extensions": [".js", ".vue"]
  },
  "eslint.probe": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "html",
    "vue"
  ]
}

package.json devDependencies

"devDependencies": {
    "@vue/cli-plugin-babel": "4.2.3",
    "@vue/cli-plugin-eslint": "^4.3.1",
    "@vue/cli-plugin-router": "4.2.3",
    "@vue/cli-service": "4.2.3",
    "@vue/eslint-config-prettier": "6.0.0",
    "@vue/eslint-config-standard": "^5.1.2",
    "babel-eslint": "^10.1.0",
    "eslint": "6.8.0",
    "eslint-config-airbnb": "^18.1.0",
    "eslint-config-import": "0.13.0",
    "eslint-config-prettier": "^6.11.0",
    "eslint-plugin-es-beautifier": "^1.0.1",
    "eslint-plugin-import": "^2.20.2",
    "eslint-plugin-modules": "^1.1.1",
    "eslint-plugin-node": "^11.1.0",
    "eslint-plugin-prettier": "^3.1.3",
    "eslint-plugin-prettier-vue": "^2.1.0",
    "eslint-plugin-promise": "^4.2.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^6.2.2",
    "express": "^4.17.1",
    "fibers": "4.0.2",
    "gulp": "4.0.2",
    "gulp-append-prepend": "1.0.8",
    "husky": "^4.2.5",
    "jest": "^25.4.0",
    "jest-sonar-reporter": "^2.0.0",
    "json-server": "^0.16.1",
    "node-sass": "4.13.1",
    "nodemon": "^2.0.3",
    "pretty-quick": "^2.0.1",
    "sass": "1.26.3",
    "sass-loader": "^8.0.2",
    "vue-jest": "^3.0.5",
    "vue-template-compiler": "2.6.11"
  },

.eslintrc.js

module.exports = {
  root: true,

  env: {
    node: true,
    jest: true
  },

  extends: ['plugin:vue/essential', '@vue/standard'],

  rules: {
    quotes: [
      2,
      'single',
      {
        avoidEscape: true
      }
    ],
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-unused-vars': [
      'error',
      {
        vars: 'all',
        varsIgnorePattern: 'css',
        args: 'all'
      }
    ],
    'no-use-before-define': [
      'error',
      {
        functions: false,
        classes: true
      }
    ],
    'no-var': 'error',
    'prefer-const': 2,
    eqeqeq: [
      'error',
      'always',
      {
        null: 'ignore'
      }
    ],
    'no-array-constructor': 'error',
    'no-new-object': 'error',
    'no-bitwise': 'error',
    'no-redeclare': 2,
    'no-proto': 2,
    'no-invalid-regexp': 2,
    'vue/script-indent': ['error', 2],
    'vue/html-closing-bracket-newline': [
      'error',
      {
        singleline: 'never',
        multiline: 'always'
      }
    ],
    'space-before-function-paren': [2, 'never'],
    'no-new': 0,
    'no-implied-eval': 0,
    'handle-callback-err': 0,
    'no-extend-native': 0,
    indent: ['error', 2, { SwitchCase: 1 }],
    semi: [2, 'always'],
    'vue/html-indent': [
      'error',
      2,
      {
        attribute: 1,
        baseIndent: 1,
        closeBracket: 0,
        alignAttributesVertically: true,
        ignores: ['pre', 'textarea', 'span']
      }
    ],
    'no-tabs': 0,
    'vue/singleline-html-element-content-newline': [
      'error',
      {
        ignoreWhenNoAttributes: true,
        ignoreWhenEmpty: true,
        ignores: ['pre', 'textarea', 'span']
      }
    ]
  },

  parserOptions: {
    parser: 'babel-eslint'
  },

  globals: {
    console: true,
    alert: true,
    document: true,
    __dirname: true,
    require: true,
    window: true,
    process: true,
    module: true,
    define: true,
    _: true,
    Promise: true,
    setTimeout: true,
    clearTimeout: true,
    mount: true,
    clearInterval: true,
    setInterval: true,
    $: false,
    MutationObserver: false,
    Map: false,
    localStorage: true
  }
};

.prettierrc.js

module.exports = {
  singleQuote: true,
  tabWidth: 2
};

.jshintrc

{
    "esversion": 9
}

.editorconfig

[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

That's it, and everything working as per expectations. Hope this helps someone.

you can config .prettierrc.js

bracketSameLine: true

You can change your config to this, hopefully it should allow what you are trying to do:

{
  "vue/html-closing-bracket-newline": ["error", {
    "singleline": "never",
    "multiline": "always"
  }]
}

If you want to read more about it, please refer this.

if I have understood correctly, what you need is to use in your eslint extends:

  extends: [
    'plugin:vue/recommended',
    '@vue/standard',
  ]

which have pretty much everything you need for a sane formatting.

Keep in mind you have to install them

My vs-code settings just have the default formatter

"vetur.format.defaultFormatter.html": "js-beautify-html",
...
"editor.formatOnSave": true,
...
"editor.codeActionsOnSave": {
  "source.fixAll.eslint": true
},

And nothing else related to this matter - I hope this helps.

Related