Repository: vuejs/eslint-plugin-vue
Pull request #1465 (2021-03-25 04:31:05)

feat(max-attributes-per-line): singleline.allowFirstLine option
**What rule do you want to change?**
`max-attributes-per-line`

**Does this change cause the rule to produce more or fewer warnings?**
More

**How will the change be implemented? (New option, new default behavior, etc.)?**
Lower the minimum value on `singleline`/`singleline.max` to 0 on the schema.

Update:
Add a `singleline.allowFirstLine` option that defaults to `true`, but prevents single attributes from being on the same line if set to `false`.

**Please provide some example code that this change will affect:**
When setting `singleline.max = 0`, the following should be marked incorrect:

Update:
When setting `singleline.allowFirstLine = false`, the following should be marked incorrect:

```vue
<template>
	<component name="John Doe" />
</template>
```

**What does the rule currently do for this code?**
Currently, can't set `singleline` to 0 because of the schema validation. 

**What will the rule do after it's changed?**
Correct it to:
```vue
<template>
	<component
		name="John Doe"
	/>
</template>
```

**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

- This was working in prior versions of ESLint that did not enforce the JSON schema on options. But since upgrading ESLint, this appeared to be a regression. Since it's not a regression in this plugin, I labeled this as a feature request.

- This is style used by a large codebase I'm contributing to.


ota-meshi: Thank you for this PR!
I think it easier for users to add a `singleline.allowFirstLine` option similar to `multiline.allowFirstLine` option.
What do you think?
privatenumber: Yeah that definitely makes more sense when trying to grasp the options.

Will update to `allowFirstLine`. Thanks!
privatenumber: Now that I'm working on updating the docs, I'm second-guessing whether it makes sense.


It's a little strange that a "singleline" rule enforces the multiline behavior:

```js
['error', {singleline: { allowFirstLine: false }}]
```

```html
<template>
	<component
		name="John Doe"
	/>
</template>
```

I still think it's an improvement over `max: 0` though.





Patch:
diff --git a/docs/rules/max-attributes-per-line.md b/docs/rules/max-attributes-per-line.md
index 69bd7794..dac9d2d7 100644
--- a/docs/rules/max-attributes-per-line.md
+++ b/docs/rules/max-attributes-per-line.md
@@ -57,7 +57,10 @@ There is a configurable number of attributes that are acceptable in one-line cas
 ```json
 {
   "vue/max-attributes-per-line": ["error", {
-    "singleline": 1,
+    "singleline": {
+      "max": 1,
+      "allowFirstLine": true
+    },      
     "multiline": {
       "max": 1,
       "allowFirstLine": false
@@ -66,7 +69,8 @@ There is a configurable number of attributes that are acceptable in one-line cas
 }
 ```
 
-- `singleline` (`number`) ... The number of maximum attributes per line when the opening tag is in a single line. Default is `1`.
+- `singleline.max` (`number`) ... The number of maximum attributes per line when the opening tag is in a single line. Default is `1`.
+- `singleline.allowFirstLine` (`boolean`) ... If `true`, it allows attributes on the same line as that tag name. Default is `true`.
 - `multiline.max` (`number`) ... The max number of attributes per line when the opening tag is in multiple lines. Default is `1`. This can be `{ multiline: 1 }` instead of `{ multiline: { max: 1 }}` if you don't configure `allowFirstLine` property.
 - `multiline.allowFirstLine` (`boolean`) ... If `true`, it allows attributes on the same line as that tag name. Default is `false`.
 
@@ -86,6 +90,24 @@ There is a configurable number of attributes that are acceptable in one-line cas
 
 </eslint-code-block>
 
+### `"singleline": 1, "allowFirstLine": false`
+
+<eslint-code-block fix :rules="{'vue/max-attributes-per-line': ['error', {singleline: { allowFirstLine: false }}]}">
+
+```vue
+<template>
+  <!-- ✓ GOOD -->
+  <MyComponent
+    lorem="1"
+  />
+
+  <!-- ✗ BAD -->
+  <MyComponent lorem="1" />
+</template>
+```
+
+</eslint-code-block>
+
 ### `"multiline": 2`
 
 <eslint-code-block fix :rules="{'vue/max-attributes-per-line': ['error', {multiline: 2}]}">
diff --git a/lib/rules/max-attributes-per-line.js b/lib/rules/max-attributes-per-line.js
index e379df47..d50a78d6 100644
--- a/lib/rules/max-attributes-per-line.js
+++ b/lib/rules/max-attributes-per-line.js
@@ -34,6 +34,9 @@ module.exports = {
                   max: {
                     type: 'number',
                     minimum: 1
+                  },
+                  allowFirstLine: {
+                    type: 'boolean'
                   }
                 },
                 additionalProperties: false
@@ -72,7 +75,8 @@ module.exports = {
     const configuration = parseOptions(context.options[0])
     const multilineMaximum = configuration.multiline
     const singlelinemMaximum = configuration.singleline
-    const canHaveFirstLine = configuration.allowFirstLine
+    const canHaveSinglelineFirstLine = configuration.singlelineAllowFirstLine
+    const canHaveMultilineFirstLine = configuration.multilineAllowFirstLine
     const template =
       context.parserServices.getTemplateBodyTokenStore &&
       context.parserServices.getTemplateBodyTokenStore()
@@ -83,16 +87,22 @@ module.exports = {
 
         if (!numberOfAttributes) return
 
-        if (
-          utils.isSingleLine(node) &&
-          numberOfAttributes > singlelinemMaximum
-        ) {
-          showErrors(node.attributes.slice(singlelinemMaximum))
+        if (utils.isSingleLine(node)) {
+          if (
+            !canHaveSinglelineFirstLine &&
+            node.attributes[0].loc.start.line === node.loc.start.line
+          ) {
+            showErrors([node.attributes[0]])
+          }
+
+          if (numberOfAttributes > singlelinemMaximum) {
+            showErrors(node.attributes.slice(singlelinemMaximum))
+          }
         }
 
         if (!utils.isSingleLine(node)) {
           if (
-            !canHaveFirstLine &&
+            !canHaveMultilineFirstLine &&
             node.attributes[0].loc.start.line === node.loc.start.line
           ) {
             showErrors([node.attributes[0]])
@@ -114,27 +124,36 @@ module.exports = {
     function parseOptions(options) {
       const defaults = {
         singleline: 1,
+        singlelineAllowFirstLine: true,
         multiline: 1,
-        allowFirstLine: false
+        multilineAllowFirstLine: false
       }
 
       if (options) {
         if (typeof options.singleline === 'number') {
           defaults.singleline = options.singleline
-        } else if (options.singleline && options.singleline.max) {
-          defaults.singleline = options.singleline.max
+        } else if (typeof options.singleline === 'object') {
+          if (typeof options.singleline.max === 'number') {
+            defaults.singleline = options.singleline.max
+          }
+
+          if (typeof options.singleline.allowFirstLine === 'boolean') {
+            defaults.singlelineAllowFirstLine =
+              options.singleline.allowFirstLine
+          }
         }
 
         if (options.multiline) {
           if (typeof options.multiline === 'number') {
             defaults.multiline = options.multiline
           } else if (typeof options.multiline === 'object') {
-            if (options.multiline.max) {
+            if (typeof options.multiline.max === 'number') {
               defaults.multiline = options.multiline.max
             }
 
-            if (options.multiline.allowFirstLine) {
-              defaults.allowFirstLine = options.multiline.allowFirstLine
+            if (typeof options.multiline.allowFirstLine === 'boolean') {
+              defaults.multilineAllowFirstLine =
+                options.multiline.allowFirstLine
             }
           }
         }


Test patch:
diff --git a/tests/lib/rules/max-attributes-per-line.js b/tests/lib/rules/max-attributes-per-line.js
index fa721002..aedc6d6d 100644
--- a/tests/lib/rules/max-attributes-per-line.js
+++ b/tests/lib/rules/max-attributes-per-line.js
@@ -299,6 +299,19 @@ petname="Snoopy" extra="foo">
           line: 3
         }
       ]
+    },
+    {
+      code: `<template><component name="John Doe"></component></template>`,
+      options: [{ singleline: { allowFirstLine: false } }],
+      output: `<template><component
+name="John Doe"></component></template>`,
+      errors: [
+        {
+          message: "'name' should be on a new line.",
+          type: 'VAttribute',
+          line: 1
+        }
+      ]
     }
   ]
 })