Skip to content

Commit 9252468

Browse files
ItMagaFloEdelmann
andauthored
Add vue/require-default-export rule (#2494)
Co-authored-by: Flo Edelmann <[email protected]>
1 parent 15a9c1b commit 9252468

File tree

6 files changed

+321
-0
lines changed

6 files changed

+321
-0
lines changed

docs/rules/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ For example:
268268
| [vue/prefer-prop-type-boolean-first](./prefer-prop-type-boolean-first.md) | enforce `Boolean` comes first in component prop types | :bulb: | :warning: |
269269
| [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: |
270270
| [vue/prefer-true-attribute-shorthand](./prefer-true-attribute-shorthand.md) | require shorthand form attribute when `v-bind` value is `true` | :bulb: | :hammer: |
271+
| [vue/require-default-export](./require-default-export.md) | require components to be the default export | | :warning: |
271272
| [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | | :hammer: |
272273
| [vue/require-emit-validator](./require-emit-validator.md) | require type definitions in emits | :bulb: | :hammer: |
273274
| [vue/require-explicit-slots](./require-explicit-slots.md) | require slots to be explicitly defined | | :warning: |

docs/rules/one-component-per-file.md

+4
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ export default {
4949

5050
Nothing.
5151

52+
## :couple: Related Rules
53+
54+
- [vue/require-default-export](./require-default-export.md)
55+
5256
## :books: Further Reading
5357

5458
- [Style guide - Component files](https://vuejs.org/style-guide/rules-strongly-recommended.html#component-files)

docs/rules/require-default-export.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/require-default-export
5+
description: require components to be the default export
6+
---
7+
8+
# vue/require-default-export
9+
10+
> require components to be the default export
11+
12+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> _**This rule has not been released yet.**_ </badge>
13+
14+
## :book: Rule Details
15+
16+
This rule reports when a Vue component does not have a default export, if the component is not defined as `<script setup>`.
17+
18+
<eslint-code-block :rules="{'vue/require-default-export': ['error']}">
19+
20+
```vue
21+
<!-- ✗ BAD -->
22+
<script>
23+
const foo = 'foo';
24+
</script>
25+
```
26+
27+
</eslint-code-block>
28+
29+
<eslint-code-block :rules="{'vue/require-default-export': ['error']}">
30+
31+
```vue
32+
<!-- ✓ GOOD -->
33+
<script>
34+
export default {
35+
data() {
36+
return {
37+
foo: 'foo'
38+
};
39+
}
40+
};
41+
</script>
42+
```
43+
44+
</eslint-code-block>
45+
46+
## :wrench: Options
47+
48+
Nothing.
49+
50+
## :couple: Related Rules
51+
52+
- [vue/one-component-per-file](./one-component-per-file.md)
53+
54+
## :mag: Implementation
55+
56+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/require-default-export.js)
57+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/require-default-export.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ const plugin = {
209209
'prop-name-casing': require('./rules/prop-name-casing'),
210210
'quote-props': require('./rules/quote-props'),
211211
'require-component-is': require('./rules/require-component-is'),
212+
'require-default-export': require('./rules/require-default-export'),
212213
'require-default-prop': require('./rules/require-default-prop'),
213214
'require-direct-export': require('./rules/require-direct-export'),
214215
'require-emit-validator': require('./rules/require-emit-validator'),

lib/rules/require-default-export.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* @author ItMaga
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
module.exports = {
10+
meta: {
11+
type: 'problem',
12+
docs: {
13+
description: 'require components to be the default export',
14+
categories: undefined,
15+
url: 'https://eslint.vuejs.org/rules/require-default-export.html'
16+
},
17+
fixable: null,
18+
schema: [],
19+
messages: {
20+
missing: 'Missing default export.',
21+
mustBeDefaultExport: 'Component must be the default export.'
22+
}
23+
},
24+
/** @param {RuleContext} context */
25+
create(context) {
26+
const sourceCode = context.getSourceCode()
27+
const documentFragment = sourceCode.parserServices.getDocumentFragment?.()
28+
29+
const hasScript =
30+
documentFragment &&
31+
documentFragment.children.some(
32+
(e) => utils.isVElement(e) && e.name === 'script'
33+
)
34+
35+
if (utils.isScriptSetup(context) || !hasScript) {
36+
return {}
37+
}
38+
39+
let hasDefaultExport = false
40+
let hasDefinedComponent = false
41+
42+
return utils.compositingVisitors(
43+
utils.defineVueVisitor(context, {
44+
onVueObjectExit() {
45+
hasDefinedComponent = true
46+
}
47+
}),
48+
49+
{
50+
'Program > ExportDefaultDeclaration'() {
51+
hasDefaultExport = true
52+
},
53+
54+
/**
55+
* @param {Program} node
56+
*/
57+
'Program:exit'(node) {
58+
if (!hasDefaultExport && node.body.length > 0) {
59+
context.report({
60+
loc: node.tokens[node.tokens.length - 1].loc,
61+
messageId: hasDefinedComponent ? 'mustBeDefaultExport' : 'missing'
62+
})
63+
}
64+
}
65+
}
66+
)
67+
}
68+
}
+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/**
2+
* @author ItMaga
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('../../eslint-compat').RuleTester
8+
const rule = require('../../../lib/rules/require-default-export')
9+
10+
const tester = new RuleTester({
11+
languageOptions: {
12+
parser: require('vue-eslint-parser'),
13+
ecmaVersion: 2020,
14+
sourceType: 'module'
15+
}
16+
})
17+
18+
tester.run('require-default-export', rule, {
19+
valid: [
20+
{
21+
filename: 'test.vue',
22+
code: `
23+
<template>Without script</template>
24+
`
25+
},
26+
{
27+
filename: 'test.vue',
28+
code: `
29+
<script>
30+
import { ref } from 'vue';
31+
32+
export default {}
33+
</script>
34+
`
35+
},
36+
{
37+
filename: 'test.vue',
38+
code: `
39+
<script setup>
40+
const foo = 'foo';
41+
</script>
42+
`
43+
},
44+
{
45+
filename: 'test.vue',
46+
code: `
47+
<script>
48+
const component = {};
49+
50+
export default component;
51+
</script>
52+
`
53+
},
54+
{
55+
filename: 'test.vue',
56+
code: `
57+
<script>
58+
import {defineComponent} from 'vue';
59+
60+
export default defineComponent({});
61+
</script>
62+
`
63+
},
64+
{
65+
filename: 'test.js',
66+
code: `
67+
const foo = 'foo';
68+
export const bar = 'bar';
69+
`
70+
},
71+
{
72+
filename: 'test.js',
73+
code: `
74+
import {defineComponent} from 'vue';
75+
defineComponent({});
76+
`
77+
}
78+
],
79+
invalid: [
80+
{
81+
filename: 'test.vue',
82+
code: `
83+
<script>
84+
const foo = 'foo';
85+
</script>
86+
`,
87+
errors: [
88+
{
89+
messageId: 'missing',
90+
line: 4,
91+
endLine: 4,
92+
column: 7,
93+
endColumn: 16
94+
}
95+
]
96+
},
97+
{
98+
filename: 'test.vue',
99+
code: `
100+
<script>
101+
export const foo = 'foo';
102+
</script>
103+
`,
104+
errors: [
105+
{
106+
messageId: 'missing',
107+
line: 4,
108+
endLine: 4,
109+
column: 7,
110+
endColumn: 16
111+
}
112+
]
113+
},
114+
{
115+
filename: 'test.vue',
116+
code: `
117+
<script>
118+
const foo = 'foo';
119+
120+
export { foo };
121+
</script>
122+
`,
123+
errors: [
124+
{
125+
messageId: 'missing',
126+
line: 6,
127+
endLine: 6,
128+
column: 7,
129+
endColumn: 16
130+
}
131+
]
132+
},
133+
{
134+
filename: 'test.vue',
135+
code: `
136+
<script>
137+
export const foo = 'foo';
138+
export const bar = 'bar';
139+
</script>
140+
`,
141+
errors: [
142+
{
143+
messageId: 'missing',
144+
line: 5,
145+
endLine: 5,
146+
column: 7,
147+
endColumn: 16
148+
}
149+
]
150+
},
151+
{
152+
filename: 'test.vue',
153+
code: `
154+
<script>
155+
import { defineComponent } from 'vue';
156+
157+
export const component = defineComponent({});
158+
</script>
159+
`,
160+
errors: [
161+
{
162+
messageId: 'mustBeDefaultExport',
163+
line: 6,
164+
endLine: 6,
165+
column: 7,
166+
endColumn: 16
167+
}
168+
]
169+
},
170+
{
171+
filename: 'test.vue',
172+
code: `
173+
<script>
174+
import Vue from 'vue';
175+
176+
const component = Vue.component('foo', {});
177+
</script>
178+
`,
179+
errors: [
180+
{
181+
messageId: 'mustBeDefaultExport',
182+
line: 6,
183+
endLine: 6,
184+
column: 7,
185+
endColumn: 16
186+
}
187+
]
188+
}
189+
]
190+
})

0 commit comments

Comments
 (0)