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

Issue:
vue/attributes-order doesn't order v-bind correct
<!--
  ❗Please don't ignore this template.❗

  If you ignore it, we're just going to respond asking you to fill it out, which wastes everyone's time.
  The more relevant information you can include, the faster we can find the issue and fix it without asking you for more info.
-->

<!--
  Before posting the issue, please confirm that the problem you're getting
  is not related with your code editor configuration.
  To make sure it's not, run: yarn eslint src/your-file.vue
-->

**Checklist**

-  I have tried restarting my IDE and the issue persists.
-  I have read the [FAQ](https://eslint.vuejs.org/user-guide/#faq) and my problem is not listed.
<!-- If you do not read the FAQ and open an issue that is listed in the FAQ, we may silently close the issue. -->

**Tell us about your environment**

- **Vue version":** 3.2.27
- **ESLint version:** 8.7.0
- **eslint-plugin-vue version:**  8.3.0
- **Node version:** 16.13.0
- **Operating System:**  macOS Monterey 12.1

**Please show your full configuration:**
<!-- Paste content of your .eslintrc file -->
```json5
extends: [
        'eslint:recommended',
        'plugin:vue/vue3-essential',
        'plugin:vue/vue3-strongly-recommended',
        'plugin:vue/vue3-recommended',
],
rules: {
        'vue/attributes-order': 2,
}
```

**What did you do?**	
<!-- Please include the actual source code causing the issue. -->
```vue
<base-menu
        v-model:placement="placement"
        v-bind="menuProps"
        :value="focused"
        :class="className"
        @click:outside="hideOptions"
    />
```

**What did you expect to happen?**
no warning on console

**What actually happened?**
<!--
  Please include the actual, raw output from ESLint.
  If you are only looking at the results of your editor extension, also check the CLI results.
-->
```
Module Warning (from ./node_modules/vue-loader/dist/templateLoader.js):
(Emitted value instead of an instance of Error) (deprecation COMPILER_V_BIND_OBJECT_ORDER) v-bind="obj" usage is now order sensitive and behaves like JavaScript object spread: it will now overwrite an existing non-mergeable attribute that appears before v-bind in the case of conflict. To retain 2.x behavior, move v-bind to make it the first attribute. You can also suppress this warning if the usage is intended.
  Details: https://v3.vuejs.org/guide/migration/v-bind.html
2  |      <base-menu
3  |          v-model:placement="placement"
4  |          v-bind="menuProps"
   |          ^^^^^^^^^^^^^^^^^^
5  |          :value="focused"
6  |          :class="className"

```


**What did you do?**	
<!-- Please include the actual source code causing the issue. -->
```vue
<base-menu
        v-bind="menuProps"
        v-model:placement="placement"
        :value="focused"
        :class="className"
        @click:outside="hideOptions"
    />
```

**What did you expect to happen?**
no warning on console

**What actually happened?**
<!--
  Please include the actual, raw output from ESLint.
  If you are only looking at the results of your editor extension, also check the CLI results.
-->
```
ERROR in component.vue:4:9
vue/attributes-order: Attribute "v-model:placement" should go before "v-bind".
    2 |     <base-menu
    3 |         v-bind="menuProps"
    4 |         v-model:placement="placement"

```



**Repository to reproduce this issue**
<!--
  ❗ Please share a repository that can reproduce your issue.
  ❗ If you don't share it, we just label it as requesting a repository that can reproduce your issue.
  ❗ If you don't share it for a few days, we silently close this issue.
-->



Pull request:
Fix false positives for `v-bind="obj"` with `v-model` in `vue/attributes-order` rule
fixes #1767

Patch:
diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js
index 5af387db..0d9ea9ce 100644
--- a/lib/rules/attributes-order.js
+++ b/lib/rules/attributes-order.js
@@ -36,6 +36,14 @@ const ATTRS = {
 function isVBind(node) {
   return Boolean(node && node.directive && node.key.name.name === 'bind')
 }
+/**
+ * Check whether the given attribute is `v-model` directive.
+ * @param {VAttribute | VDirective | undefined | null} node
+ * @returns { node is VDirective }
+ */
+function isVModel(node) {
+  return Boolean(node && node.directive && node.key.name.name === 'model')
+}
 /**
  * Check whether the given attribute is plain attribute.
  * @param {VAttribute | VDirective | undefined | null} node
@@ -45,12 +53,12 @@ function isVAttribute(node) {
   return Boolean(node && !node.directive)
 }
 /**
- * Check whether the given attribute is plain attribute or `v-bind` directive.
+ * Check whether the given attribute is plain attribute, `v-bind` directive or `v-model` directive.
  * @param {VAttribute | VDirective | undefined | null} node
  * @returns { node is VAttribute }
  */
-function isVAttributeOrVBind(node) {
-  return isVAttribute(node) || isVBind(node)
+function isVAttributeOrVBindOrVModel(node) {
+  return isVAttribute(node) || isVBind(node) || isVModel(node)
 }
 
 /**
@@ -235,8 +243,8 @@ function create(context) {
 
         if (isVBindObject(node)) {
           // prev, v-bind:foo, v-bind -> v-bind:foo, v-bind, prev
-          isMoveUp = isVAttributeOrVBind
-        } else if (isVAttributeOrVBind(node)) {
+          isMoveUp = isVAttributeOrVBindOrVModel
+        } else if (isVAttributeOrVBindOrVModel(node)) {
           // prev, v-bind, v-bind:foo -> v-bind, v-bind:foo, prev
           isMoveUp = isVBindObject
         } else {
@@ -298,11 +306,13 @@ function create(context) {
     const attributes = node.attributes.filter((node, index, attributes) => {
       if (
         isVBindObject(node) &&
-        (isVAttributeOrVBind(attributes[index - 1]) ||
-          isVAttributeOrVBind(attributes[index + 1]))
+        (isVAttributeOrVBindOrVModel(attributes[index - 1]) ||
+          isVAttributeOrVBindOrVModel(attributes[index + 1]))
       ) {
-        // In Vue 3, ignore the `v-bind:foo=" ... "` and `v-bind ="object"` syntax
-        // as they behave differently if you change the order.
+        // In Vue 3, ignore `v-bind="object"`, which is
+        // a pair of `v-bind:foo="..."` and `v-bind="object"` and
+        // a pair of `v-model="..."` and `v-bind="object"`,
+        // because changing the order behaves differently.
         return false
       }
       return true
@@ -330,14 +340,14 @@ function create(context) {
       if (isVBindObject(node)) {
         // node is `v-bind ="object"` syntax
 
-        // In Vue 3, if change the order of `v-bind:foo=" ... "` and `v-bind ="object"`,
+        // In Vue 3, if change the order of `v-bind:foo="..."`, `v-model="..."` and `v-bind="object"`,
         // the behavior will be different, so adjust so that there is no change in behavior.
 
         const len = attributes.length
         for (let nextIndex = index + 1; nextIndex < len; nextIndex++) {
           const next = attributes[nextIndex]
 
-          if (isVAttributeOrVBind(next) && !isVBindObject(next)) {
+          if (isVAttributeOrVBindOrVModel(next) && !isVBindObject(next)) {
             // It is considered to be in the same order as the next bind prop node.
             return getPositionFromAttrIndex(nextIndex)
           }
diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js
index 6897ea4e..020b6373 100644
--- a/lib/utils/indent-common.js
+++ b/lib/utils/indent-common.js
@@ -1573,10 +1573,10 @@ module.exports.defineVisitor = function create(
           )
         )
         processNodeList(namedSpecifiers, leftBrace, rightBrace, 1)
-        for (const token of tokenStore.getTokensBetween(
-          leftBrace,
+        for (const token of [
+          ...tokenStore.getTokensBetween(leftBrace, rightBrace),
           rightBrace
-        )) {
+        ]) {
           const i = beforeTokens.indexOf(token)
           if (i >= 0) {
             beforeTokens.splice(i, 1)


Test patch:
diff --git a/tests/fixtures/script-indent/import-declaration-12.vue b/tests/fixtures/script-indent/import-declaration-12.vue
new file mode 100644
index 00000000..fa622a69
--- /dev/null
+++ b/tests/fixtures/script-indent/import-declaration-12.vue
@@ -0,0 +1,11 @@
+<!--{}-->
+<script>
+import Def, {
+  foo
+} from 'mod';
+import
+  Def2, {
+    bar
+  } from 'mod';
+</script>
+<!-- https://github.com/vuejs/eslint-plugin-vue/issues/1766 -->
diff --git a/tests/lib/rules/attributes-order.js b/tests/lib/rules/attributes-order.js
index b08ab161..3797b918 100644
--- a/tests/lib/rules/attributes-order.js
+++ b/tests/lib/rules/attributes-order.js
@@ -442,6 +442,42 @@ tester.run('attributes-order', rule, {
       </template>`,
       options: [{ alphabetical: true }]
     },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div
+          v-if="x"
+          v-bind="b"
+          v-model="c"
+          v-bind:value="a">
+        </div>
+      </template>`
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div
+          v-if="x"
+          v-model="c"
+          v-bind="b"
+          v-bind:value="a">
+        </div>
+      </template>`
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div
+          v-if="x"
+          v-bind="b"
+          v-bind:id="a"
+          v-model="c">
+        </div>
+      </template>`
+    },
 
     // omit order
     {
@@ -1246,6 +1282,50 @@ tester.run('attributes-order', rule, {
         'Attribute "v-if" should go before "v-on:click".'
       ]
     },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div
+          v-custom-directive="x"
+          v-bind="b"
+          v-model="c"
+          v-bind:value="a">
+        </div>
+      </template>`,
+      output: `
+      <template>
+        <div
+          v-bind="b"
+          v-model="c"
+          v-custom-directive="x"
+          v-bind:value="a">
+        </div>
+      </template>`,
+      errors: ['Attribute "v-model" should go before "v-custom-directive".']
+    },
+    {
+      filename: 'test.vue',
+      code: `
+      <template>
+        <div
+          v-if="x"
+          v-model="c"
+          v-bind="b"
+          v-bind:id="a">
+        </div>
+      </template>`,
+      output: `
+      <template>
+        <div
+          v-if="x"
+          v-bind="b"
+          v-bind:id="a"
+          v-model="c">
+        </div>
+      </template>`,
+      errors: ['Attribute "v-bind:id" should go before "v-model".']
+    },
 
     // omit order
     {