-
-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
closes #28
- Loading branch information
Showing
4 changed files
with
418 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Prevent the use of removed slot variables (no-deprecated-slots) | ||
|
||
:wrench: This rule is partially fixable with `eslint --fix` | ||
|
||
## Rule Details | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```html | ||
<v-dialog> | ||
<template #activator="{ on }"> | ||
<v-btn v-on="on" /> | ||
</template> | ||
</v-dialog> | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```html | ||
<v-dialog> | ||
<template #activator="{ props }"> | ||
<v-btn v-bind="props" /> | ||
</template> | ||
</v-dialog> | ||
``` | ||
|
||
Variable shadowing is not currently handled, the following will produce incorrect output that must be fixed manually: | ||
|
||
```html | ||
<v-menu> | ||
<template #activator="{ on: menuOn }"> | ||
<v-tooltip> | ||
<template #activator="{ on: tooltipOn }"> | ||
<v-btn v-on="{ ...tooltipOn, ...menuOn }" /> | ||
</template> | ||
</v-tooltip> | ||
</template> | ||
</v-menu> | ||
``` | ||
|
||
### Options | ||
|
||
This rule has no configuration options. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
'use strict' | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'Prevent the use of removed and deprecated slots.', | ||
category: 'recommended', | ||
}, | ||
fixable: 'code', | ||
schema: [], | ||
messages: { | ||
changedProps: `{{ component }}'s '{{ slot }}' slot has changed props`, | ||
invalidProps: `Slot has invalid props`, | ||
}, | ||
}, | ||
|
||
create (context) { | ||
const template = context.parserServices.getTemplateBodyTokenStore() | ||
let scopeStack | ||
|
||
return context.parserServices.defineTemplateBodyVisitor({ | ||
VElement (node) { | ||
scopeStack = { | ||
parent: scopeStack, | ||
nodes: scopeStack ? [...scopeStack.nodes] : [], | ||
} | ||
for (const variable of node.variables) { | ||
scopeStack.nodes.push(variable.id) | ||
} | ||
|
||
if (node.name !== 'template') return | ||
if (!['v-dialog', 'v-menu', 'v-tooltip'].includes(node.parent.name)) return | ||
|
||
const directive = node.startTag.attributes.find(attr => { | ||
return ( | ||
attr.directive && | ||
attr.key.name.name === 'slot' && | ||
attr.key.argument?.name === 'activator' | ||
) | ||
}) | ||
|
||
if ( | ||
!directive || | ||
!directive.value || | ||
directive.value.type !== 'VExpressionContainer' || | ||
!directive.value.expression || | ||
directive.value.expression.params.length !== 1 | ||
) return | ||
|
||
const param = directive.value.expression.params[0] | ||
if (param.type === 'Identifier') { | ||
// #activator="data" | ||
const boundVariables = {} | ||
node.variables.find(variable => variable.id.name === param.name)?.references.forEach(ref => { | ||
if (ref.id.parent.type !== 'MemberExpression') return | ||
if ( | ||
// v-bind="data.props" | ||
ref.id.parent.property.name === 'props' && | ||
ref.id.parent.parent.parent.directive && | ||
ref.id.parent.parent.parent.key.name.name === 'bind' && | ||
!ref.id.parent.parent.parent.key.argument | ||
) return | ||
if (ref.id.parent.property.name === 'on') { | ||
boundVariables.on = ref.id | ||
} else if (ref.id.parent.property.name === 'attrs') { | ||
boundVariables.attrs = ref.id | ||
} | ||
}) | ||
if (boundVariables.on) { | ||
const ref = boundVariables.on | ||
context.report({ | ||
node: ref, | ||
messageId: 'changedProps', | ||
data: { | ||
component: node.parent.name, | ||
slot: directive.key.argument.name, | ||
}, | ||
fix (fixer) { | ||
return fixer.replaceText(ref.parent.parent.parent, `v-bind="${param.name}.props"`) | ||
}, | ||
}) | ||
} | ||
if (boundVariables.attrs) { | ||
const ref = boundVariables.attrs | ||
if (!boundVariables.on) { | ||
context.report({ | ||
node: boundVariables.attrs, | ||
messageId: 'invalidProps', | ||
}) | ||
} else { | ||
context.report({ | ||
node: ref, | ||
messageId: 'changedProps', | ||
data: { | ||
component: node.parent.name, | ||
slot: directive.key.argument.name, | ||
}, | ||
fix (fixer) { | ||
return fixer.removeRange([ref.parent.parent.parent.range[0] - 1, ref.parent.parent.parent.range[1]]) | ||
}, | ||
}) | ||
} | ||
} | ||
} else if (param.type === 'ObjectPattern') { | ||
// #activator="{ on, attrs }" | ||
const boundVariables = {} | ||
param.properties.forEach(prop => { | ||
node.variables.find(variable => variable.id.name === prop.value.name)?.references.forEach(ref => { | ||
if (prop.key.name === 'on') { | ||
boundVariables.on = { prop, id: ref.id } | ||
} else if (prop.key.name === 'attrs') { | ||
boundVariables.attrs = { prop, id: ref.id } | ||
} | ||
}) | ||
}) | ||
if (boundVariables.on || boundVariables.attrs) { | ||
if (boundVariables.attrs && !boundVariables.on) { | ||
context.report({ | ||
node: boundVariables.attrs.prop.key, | ||
messageId: 'invalidProps', | ||
}) | ||
} else { | ||
context.report({ | ||
node: param, | ||
messageId: 'changedProps', | ||
data: { | ||
component: node.parent.name, | ||
slot: directive.key.argument.name, | ||
}, | ||
* fix (fixer) { | ||
if (boundVariables.on) { | ||
const ref = boundVariables.on | ||
yield fixer.replaceText(ref.prop, 'props') | ||
yield fixer.replaceText(ref.id.parent.parent, `v-bind="props"`) | ||
} | ||
if (boundVariables.attrs) { | ||
const ref = boundVariables.attrs | ||
const isLast = ref.prop === param.properties.at(-1) | ||
if (isLast) { | ||
const comma = template.getTokenBefore(ref.prop, { filter: token => token.value === ',' }) | ||
if (comma) { | ||
yield fixer.removeRange([comma.start, ref.prop.end]) | ||
} else { | ||
yield fixer.removeRange([ref.prop.start - 1, ref.prop.end]) | ||
} | ||
} else { | ||
const comma = template.getTokenAfter(ref.prop, { filter: token => token.value === ',' }) | ||
if (comma) { | ||
yield fixer.removeRange([ref.prop.start - 1, comma.end]) | ||
} else { | ||
yield fixer.removeRange([ref.prop.start - 1, ref.prop.end]) | ||
} | ||
} | ||
yield fixer.removeRange([ref.id.parent.parent.range[0] - 1, ref.id.parent.parent.range[1]]) | ||
} | ||
}, | ||
}) | ||
} | ||
} | ||
} else { | ||
context.report({ | ||
node: directive, | ||
messageId: 'invalidProps', | ||
}) | ||
} | ||
}, | ||
'VElement:exit' () { | ||
scopeStack = scopeStack && scopeStack.parent | ||
}, | ||
}) | ||
}, | ||
} |
Oops, something went wrong.