Skip to content

Commit

Permalink
Only allow modelValue in vue/no-deprecated-model-definition (#2255)
Browse files Browse the repository at this point in the history
  • Loading branch information
FloEdelmann committed Jul 29, 2023
1 parent 6080fb3 commit b8814c7
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 45 deletions.
6 changes: 3 additions & 3 deletions docs/rules/no-deprecated-model-definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,16 @@ export default defineComponent({

### `"allowVue3Compat": true`

Allow `model` definitions with prop/event names that match the Vue.js 3.0.0+ `v-model` syntax, e.g. `fooBar`/`update:fooBar`.
Allow `model` definitions with prop/event names that match the Vue.js 3.0.0+ `v-model` syntax, i.e. `modelValue`/`update:modelValue` or `model-value`/`update:model-value`.

<eslint-code-block :rules="{'vue/no-deprecated-model-definition': ['error', { allowVue3Compat: true }]}">

```vue
<script>
export default defineComponent({
model: {
prop: 'fooBar',
event: 'update:fooBar'
prop: 'modelValue',
event: 'update:modelValue'
}
})
</script>
Expand Down
83 changes: 51 additions & 32 deletions lib/rules/no-deprecated-model-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,8 @@

const utils = require('../utils')

/**
* @param {RuleContext} context
* @param {ASTNode} node
*/
function reportWithoutSuggestion(context, node) {
context.report({
node,
messageId: 'deprecatedModel'
})
}
const allowedPropNames = new Set(['modelValue', 'model-value'])
const allowedEventNames = new Set(['update:modelValue', 'update:model-value'])

/**
* @param {ObjectExpression} node
Expand All @@ -39,6 +31,15 @@ function findPropertyValue(node, key) {
return property.value
}

/**
* @param {RuleFixer} fixer
* @param {Literal} node
* @param {string} text
*/
function replaceLiteral(fixer, node, text) {
return fixer.replaceTextRange([node.range[0] + 1, node.range[1] - 1], text)
}

module.exports = {
meta: {
type: 'problem',
Expand All @@ -62,7 +63,10 @@ module.exports = {
],
messages: {
deprecatedModel: '`model` definition is deprecated.',
renameEvent: 'Rename event to `{{expectedEventName}}`.'
vue3Compat:
'`model` definition is deprecated. You may use the Vue 3-compatible `modelValue`/`update:modelValue` though.',
changeToModelValue: 'Change to `modelValue`/`update:modelValue`.',
changeToKebabModelValue: 'Change to `model-value`/`update:model-value`.'
}
},
/** @param {RuleContext} context */
Expand All @@ -76,35 +80,50 @@ module.exports = {
}

if (!allowVue3Compat) {
reportWithoutSuggestion(context, modelProperty)
context.report({
node: modelProperty,
messageId: 'deprecatedModel'
})
return
}

const propName = findPropertyValue(modelProperty.value, 'prop')
const eventName = findPropertyValue(modelProperty.value, 'event')

if (!propName || !eventName) {
reportWithoutSuggestion(context, modelProperty)
return
}

const expectedEventName = `update:${propName.value}`
if (eventName.value !== expectedEventName) {
if (
!propName ||
!eventName ||
typeof propName.value !== 'string' ||
typeof eventName.value !== 'string' ||
!allowedPropNames.has(propName.value) ||
!allowedEventNames.has(eventName.value)
) {
context.report({
node: modelProperty,
messageId: 'deprecatedModel',
suggest: [
{
messageId: 'renameEvent',
data: { expectedEventName },
fix(fixer) {
return fixer.replaceTextRange(
[eventName.range[0] + 1, eventName.range[1] - 1],
expectedEventName
)
}
}
]
messageId: 'vue3Compat',
suggest:
propName && eventName
? [
{
messageId: 'changeToModelValue',
*fix(fixer) {
const newPropName = 'modelValue'
const newEventName = 'update:modelValue'
yield replaceLiteral(fixer, propName, newPropName)
yield replaceLiteral(fixer, eventName, newEventName)
}
},
{
messageId: 'changeToKebabModelValue',
*fix(fixer) {
const newPropName = 'model-value'
const newEventName = 'update:model-value'
yield replaceLiteral(fixer, propName, newPropName)
yield replaceLiteral(fixer, eventName, newEventName)
}
}
]
: []
})
}
})
Expand Down
140 changes: 130 additions & 10 deletions tests/lib/rules/no-deprecated-model-definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ tester.run('no-deprecated-model-definition', rule, {
<script>
export default {
model: {
prop: 'fooBar',
event: 'update:fooBar'
prop: 'modelValue',
event: 'update:modelValue'
}
}
</script>
Expand All @@ -45,8 +45,8 @@ tester.run('no-deprecated-model-definition', rule, {
<script>
export default defineComponent({
model: {
prop: 'foo-bar',
event: 'update:foo-bar'
prop: 'model-value',
event: 'update:model-value'
}
})
</script>
Expand Down Expand Up @@ -133,7 +133,8 @@ tester.run('no-deprecated-model-definition', rule, {
options: [{ allowVue3Compat: true }],
errors: [
{
message: '`model` definition is deprecated.',
message:
'`model` definition is deprecated. You may use the Vue 3-compatible `modelValue`/`update:modelValue` though.',
line: 4,
column: 11,
endLine: 6,
Expand All @@ -155,7 +156,8 @@ tester.run('no-deprecated-model-definition', rule, {
options: [{ allowVue3Compat: true }],
errors: [
{
message: '`model` definition is deprecated.',
message:
'`model` definition is deprecated. You may use the Vue 3-compatible `modelValue`/`update:modelValue` though.',
line: 4,
column: 11,
endLine: 6,
Expand All @@ -178,20 +180,138 @@ tester.run('no-deprecated-model-definition', rule, {
options: [{ allowVue3Compat: true }],
errors: [
{
message: '`model` definition is deprecated.',
message:
'`model` definition is deprecated. You may use the Vue 3-compatible `modelValue`/`update:modelValue` though.',
line: 4,
column: 11,
endLine: 7,
endColumn: 12,
suggestions: [
{
desc: 'Rename event to `update:foo`.',
desc: 'Change to `modelValue`/`update:modelValue`.',
output: `
<script>
export default defineComponent({
model: {
prop: 'foo',
event: 'update:foo'
prop: 'modelValue',
event: 'update:modelValue'
}
})
</script>
`
},
{
desc: 'Change to `model-value`/`update:model-value`.',
output: `
<script>
export default defineComponent({
model: {
prop: 'model-value',
event: 'update:model-value'
}
})
</script>
`
}
]
}
]
},
{
filename: 'test.vue',
code: `
<script>
export default {
model: {
prop: "fooBar",
event: "update:fooBar"
}
}
</script>
`,
options: [{ allowVue3Compat: true }],
errors: [
{
message:
'`model` definition is deprecated. You may use the Vue 3-compatible `modelValue`/`update:modelValue` though.',
line: 4,
column: 11,
endLine: 7,
endColumn: 12,
suggestions: [
{
desc: 'Change to `modelValue`/`update:modelValue`.',
output: `
<script>
export default {
model: {
prop: "modelValue",
event: "update:modelValue"
}
}
</script>
`
},
{
desc: 'Change to `model-value`/`update:model-value`.',
output: `
<script>
export default {
model: {
prop: "model-value",
event: "update:model-value"
}
}
</script>
`
}
]
}
]
},
{
filename: 'test.vue',
code: `
<script>
export default defineComponent({
model: {
prop: 'foo-bar',
event: 'update:foo-bar'
}
})
</script>
`,
options: [{ allowVue3Compat: true }],
errors: [
{
message:
'`model` definition is deprecated. You may use the Vue 3-compatible `modelValue`/`update:modelValue` though.',
line: 4,
column: 11,
endLine: 7,
endColumn: 12,
suggestions: [
{
desc: 'Change to `modelValue`/`update:modelValue`.',
output: `
<script>
export default defineComponent({
model: {
prop: 'modelValue',
event: 'update:modelValue'
}
})
</script>
`
},
{
desc: 'Change to `model-value`/`update:model-value`.',
output: `
<script>
export default defineComponent({
model: {
prop: 'model-value',
event: 'update:model-value'
}
})
</script>
Expand Down

0 comments on commit b8814c7

Please sign in to comment.