Skip to content

Commit

Permalink
Add vue/require-default-export rule (#2494)
Browse files Browse the repository at this point in the history
Co-authored-by: Flo Edelmann <[email protected]>
  • Loading branch information
ItMaga and FloEdelmann committed Jul 13, 2024
1 parent 15a9c1b commit 9252468
Show file tree
Hide file tree
Showing 6 changed files with 321 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ For example:
| [vue/prefer-prop-type-boolean-first](./prefer-prop-type-boolean-first.md) | enforce `Boolean` comes first in component prop types | :bulb: | :warning: |
| [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: | :hammer: |
| [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: | :hammer: |
| [vue/require-default-export](./require-default-export.md) | require components to be the default export | | :warning: |
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | | :hammer: |
| [vue/require-emit-validator](./require-emit-validator.md) | require type definitions in emits | :bulb: | :hammer: |
| [vue/require-explicit-slots](./require-explicit-slots.md) | require slots to be explicitly defined | | :warning: |
Expand Down
4 changes: 4 additions & 0 deletions docs/rules/one-component-per-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export default {

Nothing.

## :couple: Related Rules

- [vue/require-default-export](./require-default-export.md)

## :books: Further Reading

- [Style guide - Component files](https://vuejs.org/style-guide/rules-strongly-recommended.html#component-files)
Expand Down
57 changes: 57 additions & 0 deletions docs/rules/require-default-export.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/require-default-export
description: require components to be the default export
---

# vue/require-default-export

> require components to be the default export
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>

## :book: Rule Details

This rule reports when a Vue component does not have a default export, if the component is not defined as `<script setup>`.

<eslint-code-block :rules="{'vue/require-default-export': ['error']}">

```vue
<!-- ✗ BAD -->
<script>
const foo = 'foo';
</script>
```

</eslint-code-block>

<eslint-code-block :rules="{'vue/require-default-export': ['error']}">

```vue
<!-- ✓ GOOD -->
<script>
export default {
data() {
return {
foo: 'foo'
};
}
};
</script>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :couple: Related Rules

- [vue/one-component-per-file](./one-component-per-file.md)

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-default-export.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-default-export.js)
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ const plugin = {
'prop-name-casing': require('./rules/prop-name-casing'),
'quote-props': require('./rules/quote-props'),
'require-component-is': require('./rules/require-component-is'),
'require-default-export': require('./rules/require-default-export'),
'require-default-prop': require('./rules/require-default-prop'),
'require-direct-export': require('./rules/require-direct-export'),
'require-emit-validator': require('./rules/require-emit-validator'),
Expand Down
68 changes: 68 additions & 0 deletions lib/rules/require-default-export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @author ItMaga
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'require components to be the default export',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/require-default-export.html'
},
fixable: null,
schema: [],
messages: {
missing: 'Missing default export.',
mustBeDefaultExport: 'Component must be the default export.'
}
},
/** @param {RuleContext} context */
create(context) {
const sourceCode = context.getSourceCode()
const documentFragment = sourceCode.parserServices.getDocumentFragment?.()

const hasScript =
documentFragment &&
documentFragment.children.some(
(e) => utils.isVElement(e) && e.name === 'script'
)

if (utils.isScriptSetup(context) || !hasScript) {
return {}
}

let hasDefaultExport = false
let hasDefinedComponent = false

return utils.compositingVisitors(
utils.defineVueVisitor(context, {
onVueObjectExit() {
hasDefinedComponent = true
}
}),

{
'Program > ExportDefaultDeclaration'() {
hasDefaultExport = true
},

/**
* @param {Program} node
*/
'Program:exit'(node) {
if (!hasDefaultExport && node.body.length > 0) {
context.report({
loc: node.tokens[node.tokens.length - 1].loc,
messageId: hasDefinedComponent ? 'mustBeDefaultExport' : 'missing'
})
}
}
}
)
}
}
190 changes: 190 additions & 0 deletions tests/lib/rules/require-default-export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/**
* @author ItMaga
* See LICENSE file in root directory for full license.
*/
'use strict'

const RuleTester = require('../../eslint-compat').RuleTester
const rule = require('../../../lib/rules/require-default-export')

const tester = new RuleTester({
languageOptions: {
parser: require('vue-eslint-parser'),
ecmaVersion: 2020,
sourceType: 'module'
}
})

tester.run('require-default-export', rule, {
valid: [
{
filename: 'test.vue',
code: `
<template>Without script</template>
`
},
{
filename: 'test.vue',
code: `
<script>
import { ref } from 'vue';
export default {}
</script>
`
},
{
filename: 'test.vue',
code: `
<script setup>
const foo = 'foo';
</script>
`
},
{
filename: 'test.vue',
code: `
<script>
const component = {};
export default component;
</script>
`
},
{
filename: 'test.vue',
code: `
<script>
import {defineComponent} from 'vue';
export default defineComponent({});
</script>
`
},
{
filename: 'test.js',
code: `
const foo = 'foo';
export const bar = 'bar';
`
},
{
filename: 'test.js',
code: `
import {defineComponent} from 'vue';
defineComponent({});
`
}
],
invalid: [
{
filename: 'test.vue',
code: `
<script>
const foo = 'foo';
</script>
`,
errors: [
{
messageId: 'missing',
line: 4,
endLine: 4,
column: 7,
endColumn: 16
}
]
},
{
filename: 'test.vue',
code: `
<script>
export const foo = 'foo';
</script>
`,
errors: [
{
messageId: 'missing',
line: 4,
endLine: 4,
column: 7,
endColumn: 16
}
]
},
{
filename: 'test.vue',
code: `
<script>
const foo = 'foo';
export { foo };
</script>
`,
errors: [
{
messageId: 'missing',
line: 6,
endLine: 6,
column: 7,
endColumn: 16
}
]
},
{
filename: 'test.vue',
code: `
<script>
export const foo = 'foo';
export const bar = 'bar';
</script>
`,
errors: [
{
messageId: 'missing',
line: 5,
endLine: 5,
column: 7,
endColumn: 16
}
]
},
{
filename: 'test.vue',
code: `
<script>
import { defineComponent } from 'vue';
export const component = defineComponent({});
</script>
`,
errors: [
{
messageId: 'mustBeDefaultExport',
line: 6,
endLine: 6,
column: 7,
endColumn: 16
}
]
},
{
filename: 'test.vue',
code: `
<script>
import Vue from 'vue';
const component = Vue.component('foo', {});
</script>
`,
errors: [
{
messageId: 'mustBeDefaultExport',
line: 6,
endLine: 6,
column: 7,
endColumn: 16
}
]
}
]
})

0 comments on commit 9252468

Please sign in to comment.