Skip to content

Commit 9252468

Browse files
ItMagaFloEdelmann
andauthored
Add vue/require-default-export rule (#2494)
Co-authored-by: Flo Edelmann <git@flo-edelmann.de>
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 4 additions & 0 deletions
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

Lines changed: 57 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 68 additions & 0 deletions
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+
}
Lines changed: 190 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy