Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vue/require-default-export rule #2494

Merged
merged 12 commits into from
Jul 13, 2024
1 change: 1 addition & 0 deletions docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,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 @@ -208,6 +208,7 @@
'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 Expand Up @@ -277,7 +278,7 @@
vue: require('./processor')
},
environments: {
// TODO Remove in the next major version

Check warning on line 281 in lib/index.js

View workflow job for this annotation

GitHub Actions / Lint

Unexpected 'todo' comment: 'TODO Remove in the next major version'
/** @deprecated */
'setup-compiler-macros': {
globals: {
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
FloEdelmann marked this conversation as resolved.
Show resolved Hide resolved
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
}
]
}
]
})
Loading