Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 66bf635

Browse files
ota-meshiFloEdelmann
andauthored
Add support for defineModel (#2360)
Co-authored-by: Flo Edelmann <git@flo-edelmann.de>
1 parent 26fc85e commit 66bf635

26 files changed

+807
-71
lines changed

‎docs/rules/define-macros-order.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ This rule reports the `defineProps` and `defineEmits` compiler macros when they
2727
}
2828
```
2929

30-
- `order` (`string[]`) ... The order of defineEmits and defineProps macros. You can also add `"defineOptions"`and `"defineSlots"`.
30+
- `order` (`string[]`) ... The order of defineEmits and defineProps macros. You can also add `"defineOptions"`, `"defineSlots"`, and `"defineModel"`.
3131
- `defineExposeLast` (`boolean`) ... Force `defineExpose` at the end.
3232

3333
### `{ "order": ["defineProps", "defineEmits"] }` (default)
@@ -69,14 +69,15 @@ defineEmits(/* ... */)
6969

7070
</eslint-code-block>
7171

72-
### `{ "order": ["defineOptions", "defineProps", "defineEmits", "defineSlots"] }`
72+
### `{ "order": ["defineOptions", "defineModel", "defineProps", "defineEmits", "defineSlots"] }`
7373

74-
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']}]}">
74+
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineModel', 'defineProps', 'defineEmits', 'defineSlots']}]}">
7575

7676
```vue
7777
<!-- ✓ GOOD -->
7878
<script setup>
7979
defineOptions({/* ... */})
80+
const model = defineModel()
8081
defineProps(/* ... */)
8182
defineEmits(/* ... */)
8283
const slots = defineSlots()
@@ -85,7 +86,7 @@ const slots = defineSlots()
8586

8687
</eslint-code-block>
8788

88-
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']}]}">
89+
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineModel', 'defineProps', 'defineEmits', 'defineSlots']}]}">
8990

9091
```vue
9192
<!-- ✗ BAD -->
@@ -94,18 +95,20 @@ defineEmits(/* ... */)
9495
const slots = defineSlots()
9596
defineProps(/* ... */)
9697
defineOptions({/* ... */})
98+
const model = defineModel()
9799
</script>
98100
```
99101

100102
</eslint-code-block>
101103

102-
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots']}]}">
104+
<eslint-code-block fix :rules="{'vue/define-macros-order': ['error', {order: ['defineOptions', 'defineModel', 'defineProps', 'defineEmits', 'defineSlots']}]}">
103105

104106
```vue
105107
<!-- ✗ BAD -->
106108
<script setup>
107109
const bar = ref()
108110
defineOptions({/* ... */})
111+
const model = defineModel()
109112
defineProps(/* ... */)
110113
defineEmits(/* ... */)
111114
const slots = defineSlots()

‎docs/rules/no-unsupported-features.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ This rule reports unsupported Vue.js syntax on the specified version.
3030
- `ignores` ... You can use this `ignores` option to ignore the given features.
3131
The `"ignores"` option accepts an array of the following strings.
3232
- Vue.js 3.4.0+
33+
- `"define-model"` ... `defineModel()` macro.
3334
- `"v-bind-same-name-shorthand"` ... `v-bind` same-name shorthand.
3435
- Vue.js 3.3.0+
3536
- `"define-slots"` ... `defineSlots()` macro.

‎lib/rules/define-macros-order.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,14 @@ const MACROS_EMITS = 'defineEmits'
1010
const MACROS_PROPS = 'defineProps'
1111
const MACROS_OPTIONS = 'defineOptions'
1212
const MACROS_SLOTS = 'defineSlots'
13-
const ORDER_SCHEMA = [MACROS_EMITS, MACROS_PROPS, MACROS_OPTIONS, MACROS_SLOTS]
13+
const MACROS_MODEL = 'defineModel'
14+
const ORDER_SCHEMA = [
15+
MACROS_EMITS,
16+
MACROS_PROPS,
17+
MACROS_OPTIONS,
18+
MACROS_SLOTS,
19+
MACROS_MODEL
20+
]
1421
const DEFAULT_ORDER = [MACROS_PROPS, MACROS_EMITS]
1522

1623
/**
@@ -116,6 +123,9 @@ function create(context) {
116123
onDefineSlotsExit(node) {
117124
macrosNodes.set(MACROS_SLOTS, getDefineMacrosStatement(node))
118125
},
126+
onDefineModelExit(node) {
127+
macrosNodes.set(MACROS_MODEL, getDefineMacrosStatement(node))
128+
},
119129
onDefineExposeExit(node) {
120130
defineExposeNode = getDefineMacrosStatement(node)
121131
}

‎lib/rules/no-undef-properties.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,26 @@ module.exports = {
321321
const propertyReferences =
322322
propertyReferenceExtractor.extractFromPattern(pattern)
323323
ctx.verifyReferences(propertyReferences)
324+
},
325+
onDefineModelEnter(node, model) {
326+
const ctx = getVueComponentContext(programNode)
327+
328+
ctx.defineProperties.set(model.name.modelName, {
329+
isProps: true
330+
})
331+
332+
if (
333+
!node.parent ||
334+
node.parent.type !== 'VariableDeclarator' ||
335+
node.parent.init !== node
336+
) {
337+
return
338+
}
339+
340+
const pattern = node.parent.id
341+
const propertyReferences =
342+
propertyReferenceExtractor.extractFromPattern(pattern)
343+
ctx.verifyReferences(propertyReferences)
324344
}
325345
}),
326346
utils.defineVueVisitor(context, {

‎lib/rules/no-unsupported-features.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const FEATURES = {
3737
'define-options': require('./syntaxes/define-options'),
3838
'define-slots': require('./syntaxes/define-slots'),
3939
// Vue.js 3.4.0+
40+
'define-model': require('./syntaxes/define-model'),
4041
'v-bind-same-name-shorthand': require('./syntaxes/v-bind-same-name-shorthand')
4142
}
4243

@@ -128,6 +129,8 @@ module.exports = {
128129
forbiddenDefineSlots:
129130
'`defineSlots()` macros are not supported until Vue.js "3.3.0".',
130131
// Vue.js 3.4.0+
132+
forbiddenDefineModel:
133+
'`defineModel()` macros are not supported until Vue.js "3.4.0".',
131134
forbiddenVBindSameNameShorthand:
132135
'`v-bind` same-name shorthand is not supported until Vue.js "3.4.0".'
133136
}

‎lib/rules/no-unused-emit-declarations.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,17 @@ module.exports = {
332332
emitReferenceIds
333333
})
334334
},
335+
onDefineModelEnter(node, model) {
336+
if (
337+
node.parent &&
338+
node.parent.type === 'VariableDeclarator' &&
339+
node.parent.init === node
340+
) {
341+
// If the return value of defineModel() is stored in a variable, we can mark the 'update:modelName' event as used if that that variable is used.
342+
// If that variable is unused, it will already be reported by `no-unused-var` rule.
343+
emitCalls.set(`update:${model.name.modelName}`, node)
344+
}
345+
},
335346
...callVisitor
336347
}),
337348
{

‎lib/rules/no-unused-properties.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const {
3030
* @typedef {object} ComponentNonObjectPropertyData
3131
* @property {string} name
3232
* @property {GroupName} groupName
33-
* @property {'array' | 'type' | 'infer-type'} type
33+
* @property {'array' | 'type' | 'infer-type' | 'model' } type
3434
* @property {ASTNode} node
3535
*
3636
* @typedef { ComponentNonObjectPropertyData | ComponentObjectPropertyData } ComponentPropertyData
@@ -406,7 +406,9 @@ module.exports = {
406406
if (!groups.has('props')) {
407407
return
408408
}
409-
const container = getVueComponentPropertiesContainer(node)
409+
const container = getVueComponentPropertiesContainer(
410+
context.getSourceCode().ast
411+
)
410412

411413
for (const prop of props) {
412414
if (!prop.propName) {
@@ -452,6 +454,35 @@ module.exports = {
452454
const propertyReferences =
453455
propertyReferenceExtractor.extractFromPattern(pattern)
454456
container.propertyReferencesForProps.push(propertyReferences)
457+
},
458+
onDefineModelEnter(node, model) {
459+
if (!groups.has('props')) {
460+
return
461+
}
462+
const container = getVueComponentPropertiesContainer(
463+
context.getSourceCode().ast
464+
)
465+
if (
466+
node.parent &&
467+
node.parent.type === 'VariableDeclarator' &&
468+
node.parent.init === node
469+
) {
470+
// If the return value of defineModel() is stored in a variable, we can mark the model prop as used if that that variable is used.
471+
// If that variable is unused, it will already be reported by `no-unused-var` rule.
472+
container.propertyReferences.push(
473+
propertyReferenceExtractor.extractFromName(
474+
model.name.modelName,
475+
model.name.node || node
476+
)
477+
)
478+
return
479+
}
480+
container.properties.push({
481+
type: 'model',
482+
name: model.name.modelName,
483+
groupName: 'props',
484+
node: model.name.node || node
485+
})
455486
}
456487
}),
457488
utils.defineVueVisitor(context, {

‎lib/rules/require-prop-types.js

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,29 @@ module.exports = {
2626
},
2727
/** @param {RuleContext} context */
2828
create(context) {
29+
/**
30+
* @param {Expression} node
31+
* @returns {boolean|null}
32+
*/
33+
function optionHasType(node) {
34+
switch (node.type) {
35+
case 'ObjectExpression': {
36+
// foo: {
37+
return objectHasType(node)
38+
}
39+
case 'ArrayExpression': {
40+
// foo: [
41+
return node.elements.length > 0
42+
}
43+
case 'FunctionExpression':
44+
case 'ArrowFunctionExpression': {
45+
return false
46+
}
47+
}
48+
49+
// Unknown
50+
return null
51+
}
2952
/**
3053
* @param {ObjectExpression} node
3154
* @returns {boolean}
@@ -44,38 +67,15 @@ module.exports = {
4467
)
4568
return Boolean(typeProperty || validatorProperty)
4669
}
47-
4870
/**
4971
* @param {ComponentProp} prop
5072
*/
5173
function checkProperty(prop) {
5274
if (prop.type !== 'object' && prop.type !== 'array') {
5375
return
5476
}
55-
let hasType = true
56-
57-
if (prop.type === 'array') {
58-
hasType = false
59-
} else {
60-
const { value } = prop
61-
switch (value.type) {
62-
case 'ObjectExpression': {
63-
// foo: {
64-
hasType = objectHasType(value)
65-
break
66-
}
67-
case 'ArrayExpression': {
68-
// foo: [
69-
hasType = value.elements.length > 0
70-
break
71-
}
72-
case 'FunctionExpression':
73-
case 'ArrowFunctionExpression': {
74-
hasType = false
75-
break
76-
}
77-
}
78-
}
77+
const hasType =
78+
prop.type === 'array' ? false : optionHasType(prop.value) ?? true
7979

8080
if (!hasType) {
8181
const { node, propName } = prop
@@ -99,6 +99,19 @@ module.exports = {
9999
for (const prop of props) {
100100
checkProperty(prop)
101101
}
102+
},
103+
onDefineModelEnter(node, model) {
104+
if (model.typeNode) return
105+
if (model.options && (optionHasType(model.options) ?? true)) {
106+
return
107+
}
108+
context.report({
109+
node: model.options || node,
110+
messageId: 'requireType',
111+
data: {
112+
name: model.name.modelName
113+
}
114+
})
102115
}
103116
}),
104117
utils.executeOnVue(context, (obj) => {

‎lib/rules/syntaxes/define-model.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @author Yosuke Ota
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../../utils/index')
8+
9+
module.exports = {
10+
supported: '>=3.4.0',
11+
/** @param {RuleContext} context @returns {RuleListener} */
12+
createScriptVisitor(context) {
13+
return utils.defineScriptSetupVisitor(context, {
14+
onDefineModelEnter(node) {
15+
context.report({
16+
node,
17+
messageId: 'forbiddenDefineModel'
18+
})
19+
}
20+
})
21+
}
22+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /