Repository: vuejs/eslint-plugin-vue
Pull request #471 (2018-05-05 07:55:13)

Add same order support on multiple types of attributes.

tinymins: Thanks,

1. Is it a bug fix or improvement?
    Improvement.

2. What was the motivation?
    In my project we want to sort attributes but we want some different kinds of attributes can have same priority. For example:
    ```html
    <component id="a1" class="c1" :class="c2" style="color: black"></component>
    ```
    In the code above, we want to force `id` before `class` and `style`, but we don't care if `:class` is before or after `class`. 

3. What problem does it solve?
    When lint sort attributes, some kinds of attributes can have same priority.

4. Is it related to any posted issue?
    Maybe not? I don't know if anyone else has the same problem.

5. Can you provide test cases that would fail before this change?
    Just like the example above, that case cannot be solved before this change, thus, we have to turn off this rule, because we do not want to force let `:class`(computed prop) before or after `class`(static prop).
tinymins: What's more, I have an other improvement idea for this rule. If an attribute's type is not listed in `attribute-order`, it will be reported as wrong order if this attribute exist, no matter what position it is at. I think  this is not right, attribute not be listed in `order` should be free at any position.
michalsnik: Alright @tinymins ! Thanks for clearing it out.

I think that your case was covered in #421, as we got rid of `BINDING` group there and treat bindings and other attrs equally. (It just waits for the major release).

If you however still want to ship support for grouping properties, I have to ask you to add tests covering the proposed enhancement - right here: https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/attributes-order.js
Although given the change in #421 I'm not sure it's still that relevant. What do you think?
tinymins: Thanks, @michalsnik
I've read the file changes in #421, and I think my PR will get conflicted with #421. When will #421 got merged into master branch?
michalsnik: Hard to tell, maybe in week or two. Once we're done fixing current bugs and start preparing 5.0-beta

Patch:
diff --git a/README.md b/README.md
index ea0d0c22..1e5ed04d 100644
--- a/README.md
+++ b/README.md
@@ -190,6 +190,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
 |    | Rule ID | Description |
 |:---|:--------|:------------|
 | :wrench: | [vue/attribute-hyphenation](./docs/rules/attribute-hyphenation.md) | enforce attribute naming style on custom components in template |
+| :wrench: | [vue/component-name-in-template-casing](./docs/rules/component-name-in-template-casing.md) | enforce specific casing for the component naming style in template |
 | :wrench: | [vue/html-closing-bracket-newline](./docs/rules/html-closing-bracket-newline.md) | require or disallow a line break before tag's closing brackets |
 | :wrench: | [vue/html-closing-bracket-spacing](./docs/rules/html-closing-bracket-spacing.md) | require or disallow a space before tag's closing brackets |
 | :wrench: | [vue/html-end-tags](./docs/rules/html-end-tags.md) | enforce end tag style |
diff --git a/docs/rules/attributes-order.md b/docs/rules/attributes-order.md
index e7b9a620..a3f2e511 100644
--- a/docs/rules/attributes-order.md
+++ b/docs/rules/attributes-order.md
@@ -90,7 +90,16 @@ Specify custom order of attribute groups
 :+1: Examples of **correct** code with custom order`:
 
 ```html
-<!-- 'vue/attributes-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'TWO_WAY_BINDING', 'OTHER_DIRECTIVES', 'OTHER_ATTR', 'EVENTS', 'CONTENT', 'DEFINITION'] }] -->
+<!-- 'vue/attributes-order': [2, { order: ['DEFINITION', 'LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', ['BINDING', 'OTHER_ATTR'], 'EVENTS', 'CONTENT'] }] -->
+<div
+  is="header"
+  prop-one="prop"
+  :prop-two="prop">
+</div>
+```
+
+```html
+<!-- 'vue/attributes-order': [2, { order: ['LIST_RENDERING', 'CONDITIONALS', 'RENDER_MODIFIERS', 'GLOBAL', 'UNIQUE', 'BINDING', 'OTHER_ATTR', 'EVENTS', 'CONTENT', 'DEFINITION'] }] -->
 <div
   prop-one="prop"
   prop-two="prop"
diff --git a/docs/rules/component-name-in-template-casing.md b/docs/rules/component-name-in-template-casing.md
new file mode 100644
index 00000000..68cfa791
--- /dev/null
+++ b/docs/rules/component-name-in-template-casing.md
@@ -0,0 +1,74 @@
+# enforce specific casing for the component naming style in template (vue/component-name-in-template-casing)
+
+- :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
+- :wrench: The `--fix` option on the [command line](http://eslint.org/docs/user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.
+
+Define a style for the component name in template casing for consistency purposes.
+
+## :book: Rule Details
+
+:+1: Examples of **correct** code for `PascalCase`:
+
+```html
+<template>
+  <TheComponent />
+</template>
+```
+
+:-1: Examples of **incorrect** code for `PascalCase`:
+
+```html
+<template>
+  <the-component />
+  <theComponent />
+  <The-component />
+</template>
+```
+
+:+1: Examples of **correct** code for `kebab-case`:
+
+```html
+<template>
+  <the-component />
+</template>
+```
+
+:-1: Examples of **incorrect** code for `kebab-case`:
+
+```html
+<template>
+  <TheComponent />
+  <theComponent />
+  <Thecomponent />
+  <The-component />
+</template>
+```
+
+## :wrench: Options
+
+Default casing is set to `PascalCase`.
+
+```json
+  "vue/component-name-in-template-casing": ["error",
+    "PascalCase|kebab-case",
+    {
+      "ignores": []
+    }
+  ]
+```
+
+- `ignores` (`string[]`) ... The element name to ignore. Sets the element name to allow. For example, a custom element or a non-Vue component.
+
+
+:+1: Examples of **correct** code for `{ignores: ["custom-element"]}`:
+
+```html
+<template>
+  <custom-element></custom-element>
+  <TheComponent/>
+</template>
+```
+
+## Related links
+
+- [Style guide - Component name casing in templates](https://vuejs.org/v2/style-guide/#Component-name-casing-in-templates-strongly-recommended)
diff --git a/docs/rules/html-closing-bracket-newline.md b/docs/rules/html-closing-bracket-newline.md
index 1fad0fea..e5134bec 100644
--- a/docs/rules/html-closing-bracket-newline.md
+++ b/docs/rules/html-closing-bracket-newline.md
@@ -25,7 +25,7 @@ This rule enforces a line break (or no line break) before tag's closing brackets
 {
     "vue/html-closing-bracket-newline": ["error", {
         "singleline": "never",
-        "multiline": "never"
+        "multiline": "always"
     }]
 }
 ```
@@ -34,63 +34,53 @@ This rule enforces a line break (or no line break) before tag's closing brackets
     - `"never"` ... disallow line breaks before the closing bracket. This is the default.
     - `"always"` ... require one line break before the closing bracket.
 - `multiline` ... the configuration for multiline elements. It's a multiline element if the last attribute is not on the same line of the opening bracket.
-    - `"never"` ... disallow line breaks before the closing bracket. This is the default.
-    - `"always"` ... require one line break before the closing bracket.
+    - `"never"` ... disallow line breaks before the closing bracket.
+    - `"always"` ... require one line break before the closing bracket. This is the default.
 
 Plus, you can use [`vue/html-indent`](./html-indent.md) rule to enforce indent-level of the closing brackets.
 
 :-1: Examples of **incorrect** code for this rule:
 
 ```html
-/*eslint html-closing-bracket-newline: "error"*/
+<!-- eslint html-closing-bracket-newline: "error" -->
 
 <div id="foo" class="bar"
 >
+
 <div
     id="foo"
-    class="bar"
->
-<div
-    id="foo"
-    class="bar"
-    >
+    class="bar">
 ```
 
 :+1: Examples of **correct** code for this rule:
 
 ```html
-/*eslint html-closing-bracket-newline: "error"*/
+<!-- eslint html-closing-bracket-newline: "error" -->
 
 <div id="foo" class="bar">
 <div
     id="foo"
-    class="bar">
+    class="bar"
+>
 ```
 
-:-1: Examples of **incorrect** code for `{ "multiline": "always" }`:
+:-1: Examples of **incorrect** code for `{ "multiline": "never" }`:
 
 ```html
-/*eslint html-closing-bracket-newline: ["error", { multiline: always }]*/
+<!-- eslint html-closing-bracket-newline: ["error", { multiline: never }] -->
 
-<div id="foo" class="bar"
->
 <div
     id="foo"
-    class="bar">
+    class="bar"
+>
 ```
 
-:+1: Examples of **correct** code for `{ "multiline": "always" }`:
+:+1: Examples of **correct** code for `{ "multiline": "never" }`:
 
 ```html
-/*eslint html-closing-bracket-newline: ["error", { multiline: always }]*/
+<!-- html-closing-bracket-newline: ["error", { multiline: never }] -->
 
-<div id="foo" class="bar">
 <div
     id="foo"
-    class="bar"
->
-<div
-    id="foo"
-    class="bar"
-    >
+    class="bar">
 ```
diff --git a/docs/rules/require-default-prop.md b/docs/rules/require-default-prop.md
index 93fc6b5a..3fb467b7 100644
--- a/docs/rules/require-default-prop.md
+++ b/docs/rules/require-default-prop.md
@@ -2,7 +2,7 @@
 
 - :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`.
 
-This rule requires default value to be set for each props that are not marked as `required`.
+This rule requires default value to be set for each props that are not marked as `required` (except `Boolean` props).
 
 ## Rule Details
 
@@ -12,10 +12,11 @@ Examples of **incorrect** code for this rule:
 props: {
   a: Number,
   b: [Number, String],
-  c: {
+  c: [Boolean, Number],
+  d: {
     type: Number
   },
-  d: {
+  e: {
     type: Number,
     required: false
   }
@@ -38,6 +39,9 @@ props: {
     type: Number,
     default: 0,
     required: false
+  },
+  d: {
+    type: Boolean, // Boolean is the only type that doesn't require default
   }
 }
 ```
diff --git a/lib/configs/strongly-recommended.js b/lib/configs/strongly-recommended.js
index 53acc859..57ae9c8e 100644
--- a/lib/configs/strongly-recommended.js
+++ b/lib/configs/strongly-recommended.js
@@ -7,6 +7,7 @@ module.exports = {
   extends: require.resolve('./essential'),
   rules: {
     'vue/attribute-hyphenation': 'error',
+    'vue/component-name-in-template-casing': 'error',
     'vue/html-closing-bracket-newline': 'error',
     'vue/html-closing-bracket-spacing': 'error',
     'vue/html-end-tags': 'error',
diff --git a/lib/index.js b/lib/index.js
index 79769f2a..8e5709a1 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -10,6 +10,7 @@ module.exports = {
     'attribute-hyphenation': require('./rules/attribute-hyphenation'),
     'attributes-order': require('./rules/attributes-order'),
     'comment-directive': require('./rules/comment-directive'),
+    'component-name-in-template-casing': require('./rules/component-name-in-template-casing'),
     'html-closing-bracket-newline': require('./rules/html-closing-bracket-newline'),
     'html-closing-bracket-spacing': require('./rules/html-closing-bracket-spacing'),
     'html-end-tags': require('./rules/html-end-tags'),
diff --git a/lib/rules/attributes-order.js b/lib/rules/attributes-order.js
index 3beabf8f..c3b3e665 100644
--- a/lib/rules/attributes-order.js
+++ b/lib/rules/attributes-order.js
@@ -38,14 +38,11 @@ function getAttributeType (name, isDirective) {
     }
   }
 }
-function getPosition (attribute, attributeOrder) {
-  let attributeType
-  if (attribute.directive && attribute.key.name === 'bind') {
-    attributeType = getAttributeType(attribute.key.argument, false)
-  } else {
-    attributeType = getAttributeType(attribute.key.name, attribute.directive)
-  }
-  return attributeOrder.indexOf(attributeType)
+function getPosition (attribute, attributePosition) {
+  const attributeType = attribute.directive && attribute.key.name === 'bind'
+    ? getAttributeType(attribute.key.argument, false)
+    : getAttributeType(attribute.key.name, attribute.directive)
+  return attributePosition.hasOwnProperty(attributeType) ? attributePosition[attributeType] : -1
 }
 
 function create (context) {
@@ -54,6 +51,14 @@ function create (context) {
   if (context.options[0] && context.options[0].order) {
     attributeOrder = context.options[0].order
   }
+  const attributePosition = {}
+  attributeOrder.forEach((item, i) => {
+    if (item instanceof Array) {
+      item.forEach((attr) => {
+        attributePosition[attr] = i
+      })
+    } else attributePosition[item] = i
+  })
   let currentPosition
   let previousNode
 
@@ -101,8 +106,8 @@ function create (context) {
       previousNode = null
     },
     'VAttribute' (node) {
-      if ((currentPosition === -1) || (currentPosition <= getPosition(node, attributeOrder))) {
-        currentPosition = getPosition(node, attributeOrder)
+      if ((currentPosition === -1) || (currentPosition <= getPosition(node, attributePosition))) {
+        currentPosition = getPosition(node, attributePosition)
         previousNode = node
       } else {
         reportIssue(node, previousNode)
diff --git a/lib/rules/component-name-in-template-casing.js b/lib/rules/component-name-in-template-casing.js
new file mode 100644
index 00000000..689398a7
--- /dev/null
+++ b/lib/rules/component-name-in-template-casing.js
@@ -0,0 +1,108 @@
+/**
+ * @author Yosuke Ota
+ * issue https://github.com/vuejs/eslint-plugin-vue/issues/250
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+
+const allowedCaseOptions = ['PascalCase', 'kebab-case']
+const defaultCase = 'PascalCase'
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: 'enforce specific casing for the component naming style in template',
+      category: undefined, // strongly-recommended
+      url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.1/docs/rules/component-name-in-template-casing.md'
+    },
+    fixable: 'code',
+    schema: [
+      {
+        enum: allowedCaseOptions
+      },
+      {
+        type: 'object',
+        properties: {
+          ignores: {
+            type: 'array',
+            items: { type: 'string' },
+            uniqueItems: true,
+            additionalItems: false
+          }
+        },
+        additionalProperties: false
+      }
+    ]
+  },
+
+  create (context) {
+    const caseOption = context.options[0]
+    const options = context.options[1] || {}
+    const caseType = allowedCaseOptions.indexOf(caseOption) !== -1 ? caseOption : defaultCase
+    const ignores = options.ignores || []
+    const tokens = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore()
+    const sourceCode = context.getSourceCode()
+
+    let hasInvalidEOF = false
+
+    return utils.defineTemplateBodyVisitor(context, {
+      'VElement' (node) {
+        if (hasInvalidEOF) {
+          return
+        }
+
+        if (!utils.isCustomComponent(node)) {
+          return
+        }
+
+        const name = node.rawName
+        if (ignores.indexOf(name) >= 0) {
+          return
+        }
+        const casingName = casing.getConverter(caseType)(name)
+        if (casingName !== name) {
+          const startTag = node.startTag
+          const open = tokens.getFirstToken(startTag)
+
+          context.report({
+            node: open,
+            loc: open.loc,
+            message: 'Component name "{{name}}" is not {{caseType}}.',
+            data: {
+              name,
+              caseType
+            },
+            fix: fixer => {
+              const endTag = node.endTag
+              if (!endTag) {
+                return fixer.replaceText(open, `<${casingName}`)
+              }
+              const endTagOpen = tokens.getFirstToken(endTag)
+              // If we can upgrade requirements to `eslint@>4.1.0`, this code can be replaced by:
+              // return [
+              //   fixer.replaceText(open, `<${casingName}`),
+              //   fixer.replaceText(endTagOpen, `</${casingName}`)
+              // ]
+              const code = `<${casingName}${sourceCode.text.slice(open.range[1], endTagOpen.range[0])}</${casingName}`
+              return fixer.replaceTextRange([open.range[0], endTagOpen.range[1]], code)
+            }
+          })
+        }
+      }
+    }, {
+      Program (node) {
+        hasInvalidEOF = utils.hasInvalidEOF(node)
+      }
+    })
+  }
+}
diff --git a/lib/rules/html-closing-bracket-newline.js b/lib/rules/html-closing-bracket-newline.js
index 93becc9d..2e7d4787 100644
--- a/lib/rules/html-closing-bracket-newline.js
+++ b/lib/rules/html-closing-bracket-newline.js
@@ -46,7 +46,10 @@ module.exports = {
   },
 
   create (context) {
-    const options = context.options[0] || {}
+    const options = Object.assign({}, {
+      singleline: 'never',
+      multiline: 'always'
+    }, context.options[0] || {})
     const template = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore()
 
     return utils.defineTemplateBodyVisitor(context, {
diff --git a/lib/rules/require-default-prop.js b/lib/rules/require-default-prop.js
index f7f2e3da..ac4cddb3 100644
--- a/lib/rules/require-default-prop.js
+++ b/lib/rules/require-default-prop.js
@@ -61,7 +61,7 @@ module.exports = {
     /**
      * Finds all props that don't have a default value set
      * @param {Property} propsNode - Vue component's "props" node
-     * @return {boolean}
+     * @return {Array} Array of props without "default" value
      */
     function findPropsWithoutDefaultValue (propsNode) {
       return propsNode.value.properties
@@ -75,6 +75,50 @@ module.exports = {
         })
     }
 
+    /**
+     * Detects whether given value node is a Boolean type
+     * @param {Node} value
+     * @return {Boolean}
+     */
+    function isValueNodeOfBooleanType (value) {
+      return (
+        value.type === 'Identifier' &&
+        value.name === 'Boolean'
+      ) || (
+        value.type === 'ArrayExpression' &&
+        value.elements.length === 1 &&
+        value.elements[0].type === 'Identifier' &&
+        value.elements[0].name === 'Boolean'
+      )
+    }
+
+    /**
+     * Detects whether given prop node is a Boolean
+     * @param {Node} prop
+     * @return {Boolean}
+     */
+    function isBooleanProp (prop) {
+      const value = prop.value
+
+      return isValueNodeOfBooleanType(value) || (
+        value.type === 'ObjectExpression' &&
+        value.properties.find(p =>
+          p.key.type === 'Identifier' &&
+          p.key.name === 'type' &&
+          isValueNodeOfBooleanType(p.value)
+        )
+      )
+    }
+
+    /**
+     * Excludes purely Boolean props from the Array
+     * @param {Array} props - Array with props
+     * @return {Array}
+     */
+    function excludeBooleanProps (props) {
+      return props.filter(prop => !isBooleanProp(prop))
+    }
+
     // ----------------------------------------------------------------------
     // Public
     // ----------------------------------------------------------------------
@@ -91,8 +135,9 @@ module.exports = {
       if (!propsNode) return
 
       const propsWithoutDefault = findPropsWithoutDefaultValue(propsNode)
+      const propsToReport = excludeBooleanProps(propsWithoutDefault)
 
-      propsWithoutDefault.forEach(prop => {
+      propsToReport.forEach(prop => {
         context.report({
           node: prop,
           message: `Prop '{{propName}}' requires default value to be set.`,


Test patch:
diff --git a/tests/lib/rules/attributes-order.js b/tests/lib/rules/attributes-order.js
index ece0b262..d3629441 100644
--- a/tests/lib/rules/attributes-order.js
+++ b/tests/lib/rules/attributes-order.js
@@ -249,6 +249,33 @@ tester.run('attributes-order', rule, {
             'OTHER_DIRECTIVES'
           ]
         }]
+    },
+    {
+      filename: 'test.vue',
+      code:
+        `<template>
+          <div
+            v-if="!visible"
+            class="content"
+            :class="className"
+            v-text="textContent"
+            >
+          </div>
+        </template>`,
+      options: [
+        { order:
+          [
+            'CONDITIONALS',
+            'LIST_RENDERING',
+            'RENDER_MODIFIERS',
+            'DEFINITION',
+            'EVENTS',
+            'UNIQUE',
+            ['BINDING', 'OTHER_ATTR'],
+            'CONTENT',
+            'GLOBAL'
+          ]
+        }]
     }
   ],
 
@@ -515,6 +542,48 @@ tester.run('attributes-order', rule, {
           nodeType: 'VIdentifier'
         }
       ]
+    },
+    {
+      code:
+        `<template>
+          <div
+            class="content"
+            v-if="!visible"
+            :class="className"
+            v-text="textContent"
+            >
+          </div>
+        </template>`,
+      options: [
+        { order:
+          [
+            'CONDITIONALS',
+            'LIST_RENDERING',
+            'RENDER_MODIFIERS',
+            'DEFINITION',
+            'EVENTS',
+            'UNIQUE',
+            ['BINDING', 'OTHER_ATTR'],
+            'CONTENT',
+            'GLOBAL'
+          ]
+        }],
+      output:
+        `<template>
+          <div
+            v-if="!visible"
+            class="content"
+            :class="className"
+            v-text="textContent"
+            >
+          </div>
+        </template>`,
+      errors: [
+        {
+          message: 'Attribute "v-if" should go before "class".',
+          nodeType: 'VIdentifier'
+        }
+      ]
     }
   ]
 })
diff --git a/tests/lib/rules/component-name-in-template-casing.js b/tests/lib/rules/component-name-in-template-casing.js
new file mode 100644
index 00000000..b2fc9871
--- /dev/null
+++ b/tests/lib/rules/component-name-in-template-casing.js
@@ -0,0 +1,320 @@
+/**
+ * @author Yosuke Ota
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/component-name-in-template-casing')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const tester = new RuleTester({
+  parser: 'vue-eslint-parser'
+})
+
+tester.run('component-name-in-template-casing', rule, {
+  valid: [
+    // default
+    '<template><div/></template>',
+    '<template><img></template>',
+    '<template><TheComponent/></template>',
+    '<template><svg><path/></svg></template>',
+    '<template><math><mspace/></math></template>',
+    '<template><div><slot></slot></div></template>',
+
+    // kebab-case
+    {
+      code: '<template><the-component></the-component></template>',
+      options: ['kebab-case']
+    },
+    {
+      code: '<template><div/></template>',
+      options: ['kebab-case']
+    },
+    {
+      code: '<template><img></template>',
+      options: ['kebab-case']
+    },
+    {
+      code: '<template><svg><path/></svg></template>',
+      options: ['kebab-case']
+    },
+    {
+      code: '<template><math><mspace/></math></template>',
+      options: ['kebab-case']
+    },
+    // ignores
+    {
+      code: '<template><custom-element></custom-element></template>',
+      options: ['PascalCase', { ignores: ['custom-element'] }]
+    },
+    {
+      code: '<template><custom-element><TheComponent/></custom-element></template>',
+      options: ['PascalCase', { ignores: ['custom-element'] }]
+    },
+    // Invalid EOF
+    '<template><the-component a=">test</the-component></template>',
+    '<template><the-component><!--test</the-component></template>'
+  ],
+  invalid: [
+    {
+      code: `
+      <template>
+        <the-component id="id">
+          <!-- comment -->
+        </the-component>
+      </template>
+      `,
+      output: `
+      <template>
+        <TheComponent id="id">
+          <!-- comment -->
+        </TheComponent>
+      </template>
+      `,
+      errors: ['Component name "the-component" is not PascalCase.']
+    },
+    {
+      code: `
+      <template>
+        <the-component id="id"/>
+      </template>
+      `,
+      output: `
+      <template>
+        <TheComponent id="id"/>
+      </template>
+      `,
+      errors: ['Component name "the-component" is not PascalCase.']
+    },
+    {
+      code: `
+      <template>
+        <TheComponent id="id">
+          <!-- comment -->
+        </TheComponent>
+      </template>
+      `,
+      options: ['kebab-case'],
+      output: `
+      <template>
+        <the-component id="id">
+          <!-- comment -->
+        </the-component>
+      </template>
+      `,
+      errors: ['Component name "TheComponent" is not kebab-case.']
+    },
+    {
+      code: `
+      <template>
+        <TheComponent id="id"/>
+      </template>
+      `,
+      options: ['kebab-case'],
+      output: `
+      <template>
+        <the-component id="id"/>
+      </template>
+      `,
+      errors: ['Component name "TheComponent" is not kebab-case.']
+    },
+    {
+      code: `
+      <template>
+        <the-component
+          id="id"/>
+      </template>
+      `,
+      output: `
+      <template>
+        <TheComponent
+          id="id"/>
+      </template>
+      `,
+      errors: ['Component name "the-component" is not PascalCase.']
+    },
+    {
+      code: `
+      <template>
+        <the-component/>
+      </template>
+      `,
+      output: `
+      <template>
+        <TheComponent/>
+      </template>
+      `,
+      errors: ['Component name "the-component" is not PascalCase.']
+    },
+    {
+      code: `
+      <template>
+        <the-component></the-component>
+      </template>
+      `,
+      output: `
+      <template>
+        <TheComponent></TheComponent>
+      </template>
+      `,
+      errors: ['Component name "the-component" is not PascalCase.']
+    },
+    {
+      code: `
+      <template>
+        <theComponent/>
+      </template>
+      `,
+      output: `
+      <template>
+        <TheComponent/>
+      </template>
+      `,
+      errors: ['Component name "theComponent" is not PascalCase.']
+    },
+    {
+      code: `
+      <template>
+        <theComponent/>
+      </template>
+      `,
+      options: ['kebab-case'],
+      output: `
+      <template>
+        <the-component/>
+      </template>
+      `,
+      errors: ['Component name "theComponent" is not kebab-case.']
+    },
+    {
+      code: `
+      <template>
+        <The-component/>
+      </template>
+      `,
+      output: `
+      <template>
+        <TheComponent/>
+      </template>
+      `,
+      errors: ['Component name "The-component" is not PascalCase.']
+    },
+    {
+      code: `
+      <template>
+        <The-component/>
+      </template>
+      `,
+      options: ['kebab-case'],
+      output: `
+      <template>
+        <the-component/>
+      </template>
+      `,
+      errors: ['Component name "The-component" is not kebab-case.']
+    },
+    {
+      code: `
+      <template>
+        <Thecomponent/>
+      </template>
+      `,
+      options: ['kebab-case'],
+      output: `
+      <template>
+        <thecomponent/>
+      </template>
+      `,
+      errors: ['Component name "Thecomponent" is not kebab-case.']
+    },
+    {
+      code: `
+      <template>
+        <the-component></the-component  >
+      </template>
+      `,
+      output: `
+      <template>
+        <TheComponent></TheComponent  >
+      </template>
+      `,
+      errors: ['Component name "the-component" is not PascalCase.']
+    },
+    {
+      code: `
+      <template>
+        <the-component></the-component
+        >
+      </template>
+      `,
+      output: `
+      <template>
+        <TheComponent></TheComponent
+        >
+      </template>
+      `,
+      errors: ['Component name "the-component" is not PascalCase.']
+    },
+    {
+      code: `
+      <template>
+        <the-component></the-component end-tag-attr="attr" >
+      </template>
+      `,
+      output: `
+      <template>
+        <TheComponent></TheComponent end-tag-attr="attr" >
+      </template>
+      `,
+      errors: ['Component name "the-component" is not PascalCase.']
+    },
+
+    // ignores
+    {
+      code: `
+      <template>
+        <custom-element>
+          <the-component />
+        </custom-element>
+      </template>`,
+      output: `
+      <template>
+        <custom-element>
+          <TheComponent />
+        </custom-element>
+      </template>`,
+      options: ['PascalCase', { ignores: ['custom-element'] }],
+      errors: ['Component name "the-component" is not PascalCase.']
+    },
+    {
+      code: `
+      <template>
+        <custom-element1>
+          <the-component />
+        </custom-element1>
+        <custom-element2 />
+        <the-component />
+      </template>`,
+      output: `
+      <template>
+        <custom-element1>
+          <TheComponent />
+        </custom-element1>
+        <custom-element2 />
+        <TheComponent />
+      </template>`,
+      options: ['PascalCase', { ignores: ['custom-element1', 'custom-element2'] }],
+      errors: [
+        'Component name "the-component" is not PascalCase.',
+        'Component name "the-component" is not PascalCase.'
+      ]
+    }
+  ]
+})
diff --git a/tests/lib/rules/html-closing-bracket-newline.js b/tests/lib/rules/html-closing-bracket-newline.js
index 46e15418..fbb74b69 100644
--- a/tests/lib/rules/html-closing-bracket-newline.js
+++ b/tests/lib/rules/html-closing-bracket-newline.js
@@ -29,7 +29,8 @@ tester.run('html-closing-bracket-newline', rule, {
     `
       <template>
         <div
-          id="">
+          id=""
+        >
         </div>
       </template>
     `,
@@ -144,20 +145,20 @@ tester.run('html-closing-bracket-newline', rule, {
       code: `
         <template>
           <div
-            id=""
-            >
+            id="">
           </div>
         </template>
       `,
       output: `
         <template>
           <div
-            id="">
+            id=""
+>
           </div>
         </template>
       `,
       errors: [
-        'Expected no line breaks before closing bracket, but 1 line break found.'
+        'Expected 1 line break before closing bracket, but no line breaks found.'
       ]
     },
     {
diff --git a/tests/lib/rules/require-default-prop.js b/tests/lib/rules/require-default-prop.js
index efc71441..e5f2f346 100644
--- a/tests/lib/rules/require-default-prop.js
+++ b/tests/lib/rules/require-default-prop.js
@@ -19,7 +19,7 @@ const parserOptions = {
 // Tests
 // ------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester()
+const ruleTester = new RuleTester({ parserOptions })
 ruleTester.run('require-default-prop', rule, {
 
   valid: [
@@ -46,12 +46,27 @@ ruleTester.run('require-default-prop', rule, {
               required: false,
               'default': 'lorem'
             },
+            e: {
+              type: Boolean
+            },
+            f: {
+              type: Boolean,
+              required: false
+            },
+            g: {
+              type: Boolean,
+              default: true
+            },
+            h: {
+              type: [Boolean]
+            },
+            i: Boolean,
+            j: [Boolean],
             // eslint-disable-next-line require-default-prop
-            e: Number
+            k: Number
           }
         }
-      `,
-      parserOptions
+      `
     },
     {
       filename: 'test.vue',
@@ -70,8 +85,7 @@ ruleTester.run('require-default-prop', rule, {
             }
           }
         }
-      `,
-      parserOptions
+      `
     },
     {
       filename: 'test.vue',
@@ -98,8 +112,7 @@ ruleTester.run('require-default-prop', rule, {
             }
           }
         }
-      `,
-      parserOptions
+      `
     }
   ],
 
@@ -117,11 +130,14 @@ ruleTester.run('require-default-prop', rule, {
             d: {
               type: Number,
               required: false
+            },
+            e: [Boolean, String],
+            f: {
+              type: [Boolean, String],
             }
           }
         }
       `,
-      parserOptions,
       errors: [{
         message: `Prop 'a' requires default value to be set.`,
         line: 4
@@ -134,6 +150,12 @@ ruleTester.run('require-default-prop', rule, {
       }, {
         message: `Prop 'd' requires default value to be set.`,
         line: 9
+      }, {
+        message: `Prop 'e' requires default value to be set.`,
+        line: 13
+      }, {
+        message: `Prop 'f' requires default value to be set.`,
+        line: 14
       }]
     }
   ]