Skip to content

Commit ddfd676

Browse files
waynzhFloEdelmann
andauthored
feat: add vue/no-import-compiler-macros rule (#2684)
Co-authored-by: Flo Edelmann <[email protected]>
1 parent 2d6e118 commit ddfd676

File tree

5 files changed

+359
-0
lines changed

5 files changed

+359
-0
lines changed

docs/rules/index.md

+2
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ For example:
233233
| [vue/no-deprecated-model-definition] | disallow deprecated `model` definition (in Vue.js 3.0.0+) | :bulb: | :warning: |
234234
| [vue/no-duplicate-attr-inheritance] | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` | | :hammer: |
235235
| [vue/no-empty-component-block] | disallow the `<template>` `<script>` `<style>` block to be empty | :wrench: | :hammer: |
236+
| [vue/no-import-compiler-macros] | disallow importing Vue compiler macros | :wrench: | :warning: |
236237
| [vue/no-multiple-objects-in-class] | disallow passing multiple objects in an array to class | | :hammer: |
237238
| [vue/no-potential-component-option-typo] | disallow a potential typo in your component property | :bulb: | :hammer: |
238239
| [vue/no-ref-object-reactivity-loss] | disallow usages of ref objects that can lead to loss of reactivity | | :warning: |
@@ -467,6 +468,7 @@ The following rules extend the rules provided by ESLint itself and apply them to
467468
[vue/no-expose-after-await]: ./no-expose-after-await.md
468469
[vue/no-extra-parens]: ./no-extra-parens.md
469470
[vue/no-implicit-coercion]: ./no-implicit-coercion.md
471+
[vue/no-import-compiler-macros]: ./no-import-compiler-macros.md
470472
[vue/no-invalid-model-keys]: ./no-invalid-model-keys.md
471473
[vue/no-irregular-whitespace]: ./no-irregular-whitespace.md
472474
[vue/no-lifecycle-after-await]: ./no-lifecycle-after-await.md
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-import-compiler-macros
5+
description: disallow importing Vue compiler macros
6+
---
7+
8+
# vue/no-import-compiler-macros
9+
10+
> disallow importing Vue compiler macros
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+
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.
14+
15+
## :book: Rule Details
16+
17+
This rule disallow importing vue compiler macros.
18+
19+
<eslint-code-block fix :rules="{'vue/no-import-compiler-macros': ['error']}">
20+
21+
```vue
22+
<script setup>
23+
/* ✗ BAD */
24+
import { defineProps, withDefaults } from 'vue'
25+
</script>
26+
```
27+
28+
</eslint-code-block>
29+
30+
<eslint-code-block fix :rules="{'vue/no-import-compiler-macros': ['error']}">
31+
32+
```vue
33+
<script setup>
34+
/* ✓ GOOD */
35+
import { ref } from 'vue'
36+
</script>
37+
```
38+
39+
</eslint-code-block>
40+
41+
## :wrench: Options
42+
43+
Nothing.
44+
45+
## :books: Further Reading
46+
47+
- [defineProps() & defineEmits()]
48+
49+
[defineProps() & defineEmits()]: https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits
50+
51+
## :mag: Implementation
52+
53+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-import-compiler-macros.js)
54+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-import-compiler-macros.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ const plugin = {
127127
'no-expose-after-await': require('./rules/no-expose-after-await'),
128128
'no-extra-parens': require('./rules/no-extra-parens'),
129129
'no-implicit-coercion': require('./rules/no-implicit-coercion'),
130+
'no-import-compiler-macros': require('./rules/no-import-compiler-macros'),
130131
'no-invalid-model-keys': require('./rules/no-invalid-model-keys'),
131132
'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
132133
'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'),
+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* @author Wayne Zhang
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const COMPILER_MACROS = new Set([
8+
'defineProps',
9+
'defineEmits',
10+
'defineExpose',
11+
'withDefaults',
12+
'defineModel',
13+
'defineOptions',
14+
'defineSlots'
15+
])
16+
17+
const VUE_MODULES = new Set(['@vue/runtime-core', '@vue/runtime-dom', 'vue'])
18+
19+
/**
20+
* @param {Token} node
21+
*/
22+
function isComma(node) {
23+
return node.type === 'Punctuator' && node.value === ','
24+
}
25+
26+
module.exports = {
27+
meta: {
28+
type: 'problem',
29+
docs: {
30+
description: 'disallow importing Vue compiler macros',
31+
categories: undefined,
32+
url: 'https://eslint.vuejs.org/rules/no-import-compiler-macros.html'
33+
},
34+
fixable: 'code',
35+
schema: [],
36+
messages: {
37+
noImportCompilerMacros:
38+
"'{{name}}' is a compiler macro and doesn't need to be imported."
39+
}
40+
},
41+
/**
42+
* @param {RuleContext} context
43+
* @returns {RuleListener}
44+
*/
45+
create(context) {
46+
const sourceCode = context.getSourceCode()
47+
48+
return {
49+
ImportDeclaration(node) {
50+
if (node.specifiers.length === 0 || !VUE_MODULES.has(node.source.value))
51+
return
52+
53+
for (const specifier of node.specifiers) {
54+
if (
55+
specifier.type !== 'ImportSpecifier' ||
56+
!COMPILER_MACROS.has(specifier.imported.name)
57+
) {
58+
continue
59+
}
60+
61+
context.report({
62+
node: specifier,
63+
messageId: 'noImportCompilerMacros',
64+
data: {
65+
name: specifier.imported.name
66+
},
67+
fix: (fixer) => {
68+
const isOnlySpecifier = node.specifiers.length === 1
69+
const isLastSpecifier =
70+
specifier === node.specifiers[node.specifiers.length - 1]
71+
72+
if (isOnlySpecifier) {
73+
return fixer.remove(node)
74+
} else if (isLastSpecifier) {
75+
const precedingComma = sourceCode.getTokenBefore(
76+
specifier,
77+
isComma
78+
)
79+
return fixer.removeRange([
80+
precedingComma ? precedingComma.range[0] : specifier.range[0],
81+
specifier.range[1]
82+
])
83+
} else {
84+
const subsequentComma = sourceCode.getTokenAfter(
85+
specifier,
86+
isComma
87+
)
88+
return fixer.removeRange([
89+
specifier.range[0],
90+
subsequentComma
91+
? subsequentComma.range[1]
92+
: specifier.range[1]
93+
])
94+
}
95+
}
96+
})
97+
}
98+
}
99+
}
100+
}
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/**
2+
* @author Wayne Zhang
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/no-import-compiler-macros')
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('no-import-compiler-macros', rule, {
19+
valid: [
20+
{
21+
filename: 'test.vue',
22+
code: `
23+
<script setup>
24+
import { ref, computed } from 'vue'
25+
import { someFunction } from '@vue/runtime-core'
26+
</script>
27+
`
28+
},
29+
{
30+
filename: 'test.vue',
31+
code: `
32+
<script>
33+
import { defineProps } from 'some-other-package'
34+
</script>
35+
`
36+
}
37+
],
38+
invalid: [
39+
{
40+
filename: 'test.vue',
41+
code: `
42+
<script setup>
43+
import { defineProps } from 'vue'
44+
</script>
45+
`,
46+
output: `
47+
<script setup>
48+
49+
</script>
50+
`,
51+
errors: [
52+
{
53+
messageId: 'noImportCompilerMacros',
54+
data: {
55+
name: 'defineProps'
56+
},
57+
line: 3,
58+
column: 16
59+
}
60+
]
61+
},
62+
{
63+
filename: 'test.vue',
64+
code: `
65+
<script setup>
66+
import {
67+
ref,
68+
defineProps
69+
} from 'vue'
70+
</script>
71+
`,
72+
output: `
73+
<script setup>
74+
import {
75+
ref
76+
} from 'vue'
77+
</script>
78+
`,
79+
errors: [
80+
{
81+
messageId: 'noImportCompilerMacros',
82+
data: {
83+
name: 'defineProps'
84+
},
85+
line: 5,
86+
column: 9
87+
}
88+
]
89+
},
90+
{
91+
filename: 'test.vue',
92+
code: `
93+
<script setup>
94+
import { ref, defineProps } from 'vue'
95+
import { defineEmits, computed } from '@vue/runtime-core'
96+
import { defineExpose, watch, withDefaults } from '@vue/runtime-dom'
97+
</script>
98+
`,
99+
output: `
100+
<script setup>
101+
import { ref } from 'vue'
102+
import { computed } from '@vue/runtime-core'
103+
import { watch } from '@vue/runtime-dom'
104+
</script>
105+
`,
106+
errors: [
107+
{
108+
messageId: 'noImportCompilerMacros',
109+
data: {
110+
name: 'defineProps'
111+
},
112+
line: 3,
113+
column: 21
114+
},
115+
{
116+
messageId: 'noImportCompilerMacros',
117+
data: {
118+
name: 'defineEmits'
119+
},
120+
line: 4,
121+
column: 16
122+
},
123+
{
124+
messageId: 'noImportCompilerMacros',
125+
data: {
126+
name: 'defineExpose'
127+
},
128+
line: 5,
129+
column: 16
130+
},
131+
{
132+
messageId: 'noImportCompilerMacros',
133+
data: {
134+
name: 'withDefaults'
135+
},
136+
line: 5,
137+
column: 37
138+
}
139+
]
140+
},
141+
{
142+
filename: 'test.vue',
143+
code: `
144+
<script setup>
145+
import { defineModel, defineOptions } from 'vue'
146+
</script>
147+
`,
148+
output: `
149+
<script setup>
150+
import { defineOptions } from 'vue'
151+
</script>
152+
`,
153+
errors: [
154+
{
155+
messageId: 'noImportCompilerMacros',
156+
data: {
157+
name: 'defineModel'
158+
},
159+
line: 3,
160+
column: 16
161+
},
162+
{
163+
messageId: 'noImportCompilerMacros',
164+
data: {
165+
name: 'defineOptions'
166+
},
167+
line: 3,
168+
column: 29
169+
}
170+
]
171+
},
172+
{
173+
filename: 'test.vue',
174+
code: `
175+
<script setup lang="ts">
176+
import { ref as refFoo, defineSlots as defineSlotsFoo, type computed } from '@vue/runtime-core'
177+
</script>
178+
`,
179+
output: `
180+
<script setup lang="ts">
181+
import { ref as refFoo, type computed } from '@vue/runtime-core'
182+
</script>
183+
`,
184+
languageOptions: {
185+
parserOptions: {
186+
parser: require.resolve('@typescript-eslint/parser')
187+
}
188+
},
189+
errors: [
190+
{
191+
messageId: 'noImportCompilerMacros',
192+
data: {
193+
name: 'defineSlots'
194+
},
195+
line: 3,
196+
column: 31
197+
}
198+
]
199+
}
200+
]
201+
})

0 commit comments

Comments
 (0)