Repository: vuejs/eslint-plugin-vue
Pull request #1527 (2021-07-01 04:57:39)

Change the indent rule so that the union type has the same indent as the binary expression.


Patch:
diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js
index 8ec6bbe4..5d80b83b 100644
--- a/lib/utils/indent-common.js
+++ b/lib/utils/indent-common.js
@@ -1824,6 +1824,8 @@ module.exports.defineVisitor = function create(
       let firstToken = tokenStore.getFirstToken(node)
 
       while (
+        leftToken &&
+        rightToken &&
         isOpeningParenToken(leftToken) &&
         isClosingParenToken(rightToken)
       ) {
diff --git a/lib/utils/indent-ts.js b/lib/utils/indent-ts.js
index 4801ab21..9afcdb34 100644
--- a/lib/utils/indent-ts.js
+++ b/lib/utils/indent-ts.js
@@ -19,6 +19,7 @@ const {
  * @typedef { { type: string } & HasLocation } MaybeNode
  */
 /**
+ * @typedef {import('@typescript-eslint/types').TSESTree.Node} TSESTreeNode
  * @typedef {import('@typescript-eslint/types').TSESTree.ClassExpression} ClassExpression
  * @typedef {import('@typescript-eslint/types').TSESTree.ClassDeclaration} ClassDeclaration
  * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeAliasDeclaration} TSTypeAliasDeclaration
@@ -125,6 +126,48 @@ function defineVisitor({
   processSemicolons,
   getFirstAndLastTokens
 }) {
+  /**
+   * Check whether a given token is the first token of:
+   *
+   * - A parameter of TSTypeParameterInstantiation
+   * - An element of TSTupleType
+   *
+   * @param {Token} token The token to check.
+   * @param {TSUnionType | TSIntersectionType} belongingNode The node that the token is belonging to.
+   * @returns {boolean} `true` if the token is the first token of an element.
+   */
+  function isBeginningOfElement(token, belongingNode) {
+    /** @type {TSESTreeNode | null} */
+    let node = belongingNode
+
+    while (node != null && node.parent != null) {
+      /** @type {TSESTreeNode} */
+      const parent = node.parent
+      if (parent.type === 'TSTypeParameterInstantiation') {
+        return (
+          parent.params.length >= 2 &&
+          parent.params.some(
+            (param) =>
+              getFirstAndLastTokens(param).firstToken.range[0] ===
+              token.range[0]
+          )
+        )
+      }
+      if (parent.type === 'TSTupleType') {
+        return parent.elementTypes.some(
+          (element) =>
+            element != null &&
+            getFirstAndLastTokens(element).firstToken.range[0] ===
+              token.range[0]
+        )
+      }
+
+      node = parent
+    }
+
+    return false
+  }
+
   return {
     // Support TypeScript
     /** @param {ClassDeclaration | ClassExpression} node */
@@ -463,16 +506,34 @@ function defineVisitor({
       // A | B
       // A & B
       const firstToken = tokenStore.getFirstToken(node)
-      const types = [...node.types]
-      if (getFirstAndLastTokens(types[0]).firstToken === firstToken) {
-        types.shift()
+
+      const prevToken = tokenStore.getTokenBefore(firstToken)
+      const shouldIndent =
+        prevToken == null ||
+        prevToken.loc.end.line === firstToken.loc.start.line ||
+        isBeginningOfElement(firstToken, node)
+      const offset = shouldIndent ? 1 : 0
+
+      const typeTokensList = node.types.map(getFirstAndLastTokens)
+      const typeTokens = typeTokensList.shift()
+      if (!typeTokens) {
+        return
+      }
+      let lastToken
+      if (typeTokens.firstToken === firstToken) {
+        lastToken = typeTokens.lastToken
+      } else {
+        typeTokensList.unshift(typeTokens)
+        lastToken = firstToken
+      }
+      for (const typeTokens of typeTokensList) {
+        setOffset(
+          tokenStore.getTokensBetween(lastToken, typeTokens.firstToken),
+          offset,
+          firstToken
+        )
+        setOffset(typeTokens.firstToken, offset, firstToken)
       }
-      processNodeList(
-        types,
-        firstToken,
-        null,
-        isBeginningOfLine(firstToken) ? 0 : 1
-      )
     },
     TSParenthesizedType(node) {
       // (T)
@@ -1253,12 +1314,4 @@ function defineVisitor({
     // ----------------------------------------------------------------------
     TSLiteralType() {}
   }
-  /**
-   * Check whether the given node or token is the beginning of a line.
-   * @param {MaybeNode} node
-   */
-  function isBeginningOfLine(node) {
-    const prevToken = tokenStore.getTokenBefore(node, { includeComments: true })
-    return !prevToken || prevToken.loc.end.line < node.loc.start.line
-  }
 }


Test patch:
diff --git a/tests/fixtures/script-indent/ts-union-intersection-02.vue b/tests/fixtures/script-indent/ts-union-intersection-02.vue
new file mode 100644
index 00000000..04b72221
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-02.vue
@@ -0,0 +1,21 @@
+<!--{"parserOptions": {"parser":"@typescript-eslint/parser"}}-->
+<script lang="ts">
+type T =
+  a
+  | b
+type U =
+  | a
+  | b
+type V =
+  a
+  & b
+type W =
+  & a
+  & b
+type X =
+  &
+  (
+    a
+    | b
+  )
+</script>
diff --git a/tests/fixtures/script-indent/ts-union-intersection-03.vue b/tests/fixtures/script-indent/ts-union-intersection-03.vue
new file mode 100644
index 00000000..78928f2e
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-03.vue
@@ -0,0 +1,19 @@
+<!--{"parserOptions": {"parser":"@typescript-eslint/parser"}}-->
+<script lang="ts">
+type T = a
+  | b
+  | c
+type U = | a
+  | b
+  | c
+type V = a
+  |
+  b
+  |
+  c
+const a = a
+  |
+  b
+  |
+  c
+</script>
diff --git a/tests/fixtures/script-indent/ts-union-intersection-04.vue b/tests/fixtures/script-indent/ts-union-intersection-04.vue
new file mode 100644
index 00000000..7396edc4
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-04.vue
@@ -0,0 +1,17 @@
+<!--{"parserOptions": {"parser":"@typescript-eslint/parser"}}-->
+<script lang="ts">
+type T = U< a
+  | b
+  | c
+>
+type T = U<
+  | a
+  | b
+  | c
+>
+type T = U<
+  & a
+  & b
+  & c
+>
+</script>
diff --git a/tests/fixtures/script-indent/ts-union-intersection-05.vue b/tests/fixtures/script-indent/ts-union-intersection-05.vue
new file mode 100644
index 00000000..d6013535
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-05.vue
@@ -0,0 +1,19 @@
+<!--{"parserOptions": {"parser":"@typescript-eslint/parser"}}-->
+<script lang="ts">
+type T = U<
+  a
+    | b
+    | c,
+  a
+    | b
+    | c
+>
+type T = U<
+  a
+    & b
+    & c,
+  a
+    & b
+    & c
+>
+</script>
diff --git a/tests/fixtures/script-indent/ts-union-intersection-06.vue b/tests/fixtures/script-indent/ts-union-intersection-06.vue
new file mode 100644
index 00000000..a363a284
--- /dev/null
+++ b/tests/fixtures/script-indent/ts-union-intersection-06.vue
@@ -0,0 +1,27 @@
+<!--{"parserOptions": {"parser":"@typescript-eslint/parser"}}-->
+<script lang="ts">
+type T = [
+  a
+    | b
+    | c
+]
+type U = [
+  | a
+    | b
+    | c
+]
+type T2 = [ a
+              | b
+              | c,
+            a
+              | b
+              | c
+]
+type U2 = [ | a
+              | b
+              | c,
+            | a
+              | b
+              | c
+]
+</script>