Repository: vuejs/eslint-plugin-vue (js)

Issue:
"ignores": [ "VAttribute" ] doesn't have any affect in tab mode
**Tell us about your environment**
* **ESLint version: 6.5.1**	
* **eslint-plugin-vue version: 5.2.3**	
* **Node version: v12.11.0**	

**Please show your full configuration:**
<details>
<summary>Click to expand</summary>

```json
{
	"env": {
		"commonjs": true,
		"es6": true,
		"node": true
	},
	"extends": [
		"eslint:recommended",
		"plugin:vue/recommended"
	],
	"globals": {
		"Atomics": "readonly",
		"SharedArrayBuffer": "readonly"
	},
	"parserOptions": {
		"ecmaVersion": 2018
	},
	"plugins": [
		"vue"
	],
	"overrides": [
		{
			"files": [ "*.vue" ],
			"rules": {
				"indent": [ "off" ]
			}
		}
	],
	"parser": "vue-eslint-parser",
	"rules": {
		"vue/html-indent": [
			"error",
			"tab",
			{
				"ignores": [
					"VAttribute"
				]
			}
		],
		"vue/script-indent": [ "off" ],
		"vue/max-attributes-per-line": [
			"error",
			{
				"singleline": 2,
				"multiline": {
					"max": 1,
					"allowFirstLine": true
				}
			}
		],
		"vue/html-closing-bracket-newline": [
			"error",
			{
				"singleline": "never",
				"multiline": "never"
			}
		],
		"vue/mustache-interpolation-spacing": [ "error", "always" ],
		"no-control-regex": [ "off" ],
		"no-empty": [
			"error",
			{ "allowEmptyCatch": true }
		],
		"no-extra-parens": [
			"error",
			"all",
			{
				"conditionalAssign": false,
				"returnAssign": false,
				"nestedBinaryExpressions": false,
				"enforceForSequenceExpressions": false
			}
		],
		"no-irregular-whitespace": [
			"error",
			{
				"skipRegExps": true,
				"skipTemplates": true
			}
		],
		"no-template-curly-in-string": [ "error" ],
		"curly": [ "off" ],
		"dot-location": [ "error", "property" ],
		"dot-notation": [ "error" ],
		"eqeqeq": [ "error", "smart" ],
		"no-caller": [ "error" ],
		"no-case-declarations": [ "error" ],
		"no-else-return": [ "error" ],
		"no-eval": [ "error" ],
		"no-extend-native": [ "error" ],
		"no-extra-bind": [ "error" ],
		"no-fallthrough": [
			"error",
			{ "commentPattern": "[Bb]reak[\\s\\w]*[Oo]mitted" }
		],
		"no-global-assign": [ "error" ],
		"no-implied-eval": [ "error" ],
		"no-loop-func": [ "error" ],
		"no-octal-escape": [ "error" ],
		"no-octal": [ "error" ],
		"no-self-compare": [ "error" ],
		"no-useless-call": [ "error" ],
		"no-warning-comments": [
			"warn",
			{
				"terms": [ "todo", "fixme", "xxx" ],
				"location": "start"
			}
		],
		"radix": [ "error" ],
		"require-await": [ "error" ],
		"wrap-iife": [ "error", "inside" ],
		"array-bracket-newline": [
			"error",
			{
				"multiline": true,
				"minItems": 2
			}
		],
		"array-bracket-spacing": [ "error", "always" ],
		"array-element-newline": [ "error", "consistent" ],
		"block-spacing": [ "error" ],
		"brace-style": [
			"error",
			"1tbs",
			{ "allowSingleLine": true }
		],
		"capitalized-comments": [ "warn", "always" ],
		"comma-dangle": [ "error", "always" ],
		"comma-spacing": [
			"error",
			{
				"before": false,
				"after": true
			}
		],
		"comma-style": [ "error", "last" ],
		"computed-property-spacing": [ "error", "never" ],
		"func-call-spacing": [ "error", "never" ],
		"function-paren-newline": [ "error", "consistent" ],
		"indent": [
			"error",
			"tab",
			{
				"SwitchCase": 1
			}
		],
		"key-spacing": [
			"error",
			{
				"beforeColon": false,
				"afterColon": true,
				"mode": "strict",
				"align": {
					"beforeColon": false,
					"afterColon": true,
					"on": "colon"
				}
			}
		],
		"keyword-spacing": [
			"error",
			{
				"before": true,
				"after": true
			}
		],
		"new-parens": [ "error" ],
		"newline-per-chained-call": [
			"error",
			{ "ignoreChainWithDepth": 2 }
		],
		"no-mixed-operators": "error",
		"no-mixed-spaces-and-tabs": [ "error", "smart-tabs" ],
		"no-trailing-spaces": [ "error" ],
		"no-unneeded-ternary": [
			"error",
			{ "defaultAssignment": false }
		],
		"object-curly-newline": [
			"error",
			{ "consistent": true }
		],
		"object-curly-spacing": [ "error", "always" ],
		"one-var": [ "error", "consecutive" ],
		"operator-assignment": [ "warn", "always" ],
		"semi": [
			"error",
			"always",
			{ "omitLastInOneLineBlock": true }
		],
		"semi-spacing": [
			"error",
			{
				"before": false,
				"after": true
			}
		],
		"semi-style": [ "error", "last" ],
		"quotes": [
			"error",
			"single",
			{ "avoidEscape": true }
		],
		"space-before-function-paren": [
			"error",
			{
				"anonymous": "always",
				"named": "never",
				"asyncArrow": "always"
			}
		],
		"space-in-parens": [ "error", "never" ],
		"space-infix-ops": [ "error" ],
		"switch-colon-spacing": [ "error" ],
		"wrap-regex": [ "error" ]
	}
}
```
</details>

**What did you do?**	
<!-- Please include the actual source code causing the issue. -->
```vue
<template>
	<div class="page">
		<div class="page__header">
			<nav class="nav">
				<ul class="nav__container">
					<li class="nav__item">
						<router-link to="/"
									 class="nav__link">
							Home
						</router-link>
					</li>
					<li class="nav__item">
						<router-link to="/about"
									 class="nav__link">
							About
						</router-link>
					</li>
					<li class="nav__item">
						<router-link to="/contact"
									 class="nav__link">
							Contact
						</router-link>
					</li>
					<li class="nav__item">
						<router-link to="/blog"
									 class="nav__link">
							Blog
						</router-link>
					</li>
				</ul>
			</nav>
		</div>
		<div class="page__content-container">
			<router-view class="page__content" />
		</div>
		<div class="page__footer">
			<!-- Todo -->
		</div>
	</div>
</template>
```

**What did you expect to happen?**
Code should pass the check, because I using `"ignores": [ "VAttribute" ]`.

**What actually happened?**
```
   8:10  error  Expected "\t" character, but found " " character  vue/html-indent
  14:10  error  Expected "\t" character, but found " " character  vue/html-indent
  20:10  error  Expected "\t" character, but found " " character  vue/html-indent
  26:10  error  Expected "\t" character, but found " " character  vue/html-indent
```

PS: GitHub has 8-space tab, actual code looks like:
<details>
<summary>Screenshot</summary>

![image](https://user-images.githubusercontent.com/8970959/67202423-445bd080-f411-11e9-8120-70595a07778e.png)
</details>

Pull request:
Fixed: "ignores" option of `html-indent` does not work
fixed #975 

Patch:
diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js
index 227e85ad..2f2ec574 100644
--- a/lib/utils/indent-common.js
+++ b/lib/utils/indent-common.js
@@ -278,7 +278,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
   const options = parseOptions(context.options[0], context.options[1] || {}, defaultOptions)
   const sourceCode = context.getSourceCode()
   const offsets = new Map()
-  const preformattedTokens = new Set()
+  const ignoreTokens = new Set()
 
   /**
    * Set offset to the given tokens.
@@ -341,9 +341,9 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       )
     }
     for (const token of tokenStore.getTokensBetween(node.startTag, endToken, option)) {
-      preformattedTokens.add(token)
+      ignoreTokens.add(token)
     }
-    preformattedTokens.add(endToken)
+    ignoreTokens.add(endToken)
   }
 
   /**
@@ -509,6 +509,11 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
   function getChainHeadToken (node) {
     const type = node.type
     while (node.parent.type === type) {
+      const prevToken = tokenStore.getTokenBefore(node)
+      if (isLeftParen(prevToken)) {
+        // The chaining is broken by parentheses.
+        break
+      }
       node = node.parent
     }
     return tokenStore.getFirstToken(node)
@@ -537,7 +542,15 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
         return parent.range[0] === token.range[0]
       }
       if (t === 'VExpressionContainer') {
-        return node.range[0] === token.range[0]
+        if (node.range[0] !== token.range[0]) {
+          return false
+        }
+        const prevToken = tokenStore.getTokenBefore(belongingNode)
+        if (isLeftParen(prevToken)) {
+          // It is not the first token because it is enclosed in parentheses.
+          return false
+        }
+        return true
       }
       if (t === 'CallExpression' || t === 'NewExpression') {
         const openParen = tokenStore.getTokenAfter(parent.callee, isNotRightParen)
@@ -587,6 +600,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
   function ignore (node) {
     for (const token of tokenStore.getTokens(node)) {
       offsets.delete(token)
+      ignoreTokens.add(token)
     }
   }
 
@@ -848,8 +862,8 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
       }
     }
 
-    // It does not validate preformatted tokens.
-    if (preformattedTokens.has(firstToken)) {
+    // It does not validate ignore tokens.
+    if (ignoreTokens.has(firstToken)) {
       return
     }
 
diff --git a/package.json b/package.json
index 1474b777..0affa217 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
     "start": "npm run test:base -- --watch --growl",
     "test:base": "mocha \"tests/lib/**/*.js\" --reporter dot",
     "test": "nyc npm run test:base -- \"tests/integrations/*.js\" --timeout 60000",
+    "debug": "mocha --inspect-brk \"tests/lib/**/*.js\" --reporter dot --timeout 60000",
     "lint": "eslint . --rulesdir eslint-internal-rules",
     "pretest": "npm run lint",
     "preversion": "npm test && npm run update && git add .",


Test patch:
diff --git a/tests/fixtures/html-indent/logical-expression-01.vue b/tests/fixtures/html-indent/logical-expression-01.vue
new file mode 100644
index 00000000..a1f575d4
--- /dev/null
+++ b/tests/fixtures/html-indent/logical-expression-01.vue
@@ -0,0 +1,64 @@
+<!--{}-->
+<template>
+  <div>
+    <span
+      v-if="
+        a &&
+          b"
+    >
+    </span>
+    <span
+      v-if="
+        a
+          && b"
+    >
+    </span>
+    <span
+      v-if="
+        a
+          &&
+          b"
+    >
+    </span>
+    <!-- Enclosed in parentheses -->
+    <span
+      v-if="(
+        a &&
+        b
+      )"
+    >
+    </span>
+    <span
+      v-if="(
+        a
+        && b
+      )"
+    >
+    </span>
+    <span
+      v-if="(
+        a
+        &&
+        b
+      )"
+    >
+    </span>
+    <!-- The first line is the same line -->
+    <span
+      v-if="a &&
+        b"
+    >
+    </span>
+    <span
+      v-if="a
+        && b"
+    >
+    </span>
+    <span
+      v-if="a
+        &&
+        b"
+    >
+    </span>
+  </div>
+</template>
diff --git a/tests/fixtures/html-indent/opts-ignores-vattribute-01.vue b/tests/fixtures/html-indent/opts-ignores-vattribute-01.vue
new file mode 100644
index 00000000..d503a204
--- /dev/null
+++ b/tests/fixtures/html-indent/opts-ignores-vattribute-01.vue
@@ -0,0 +1,19 @@
+<!--{"options":[2,{"ignores": ["VAttribute"]}]}-->
+<template>
+  <div>
+    <div
+attr1="a">
+    </div>
+    <div
+attr2>
+    </div>
+    <div
+:attr3="a">
+    </div>
+    <div
+:attr4="
+a
+">
+    </div>
+  </div>
+</template>
diff --git a/tests/fixtures/script-indent/logical-expression-01.vue b/tests/fixtures/script-indent/logical-expression-01.vue
new file mode 100644
index 00000000..087e0075
--- /dev/null
+++ b/tests/fixtures/script-indent/logical-expression-01.vue
@@ -0,0 +1,16 @@
+<!--{}-->
+<script>
+// https://github.com/vuejs/eslint-plugin-vue/issues/1007
+function fn () {
+  return (
+    (
+      a ||
+      b
+    ) &&
+    (
+      c ||
+      d
+    )
+  );
+}
+</script>
diff --git a/tests/fixtures/script-indent/logical-expression-02.vue b/tests/fixtures/script-indent/logical-expression-02.vue
new file mode 100644
index 00000000..ccba0b7c
--- /dev/null
+++ b/tests/fixtures/script-indent/logical-expression-02.vue
@@ -0,0 +1,12 @@
+<!--{}-->
+<script>
+var bb =
+  (
+    a ||
+    b
+  ) &&
+  (
+    c ||
+    d
+  )
+</script>
diff --git a/tests/fixtures/script-indent/logical-expression-03.vue b/tests/fixtures/script-indent/logical-expression-03.vue
new file mode 100644
index 00000000..9b5ccb8c
--- /dev/null
+++ b/tests/fixtures/script-indent/logical-expression-03.vue
@@ -0,0 +1,9 @@
+<!--{}-->
+<script>
+var bb =
+  a &&
+  b
+  ||
+  c &&
+  d
+</script>
diff --git a/tests/lib/rules/html-indent.js b/tests/lib/rules/html-indent.js
index 2fa78893..81a77340 100644
--- a/tests/lib/rules/html-indent.js
+++ b/tests/lib/rules/html-indent.js
@@ -756,6 +756,25 @@ tester.run('html-indent', rule, loadPatterns(
         { message: 'Expected indentation of 4 spaces but found 0 spaces.', line: 7 },
         { message: 'Expected indentation of 2 spaces but found 0 spaces.', line: 8 }
       ]
+    },
+    {
+      filename: 'test.vue',
+      code: unIndent`
+        <template>
+        \t <div attr1
+        \t\t attr2/>
+        </template>
+      `,
+      output: unIndent`
+        <template>
+        \t<div attr1
+        \t\t attr2/>
+        </template>
+      `,
+      options: ['tab', { 'ignores': ['VAttribute'] }],
+      errors: [
+        { message: 'Expected "\\t" character, but found " " character.', line: 2 }
+      ]
     }
   ]
 ))