eslint-plugin-vue
Advanced tools
Comparing version 9.16.1 to 9.17.0
@@ -31,3 +31,2 @@ /* | ||
], | ||
'vue/no-setup-props-destructure': 'error', | ||
'vue/no-shared-component-data': 'error', | ||
@@ -34,0 +33,0 @@ 'vue/no-side-effects-in-computed-properties': 'error', |
@@ -45,3 +45,2 @@ /* | ||
'vue/no-reserved-props': 'error', | ||
'vue/no-setup-props-destructure': 'error', | ||
'vue/no-shared-component-data': 'error', | ||
@@ -48,0 +47,0 @@ 'vue/no-side-effects-in-computed-properties': 'error', |
@@ -117,2 +117,3 @@ /* | ||
'no-ref-object-destructure': require('./rules/no-ref-object-destructure'), | ||
'no-ref-object-reactivity-loss': require('./rules/no-ref-object-reactivity-loss'), | ||
'no-required-prop-with-default': require('./rules/no-required-prop-with-default'), | ||
@@ -135,2 +136,3 @@ 'no-reserved-component-names': require('./rules/no-reserved-component-names'), | ||
'no-setup-props-destructure': require('./rules/no-setup-props-destructure'), | ||
'no-setup-props-reactivity-loss': require('./rules/no-setup-props-reactivity-loss'), | ||
'no-shared-component-data': require('./rules/no-shared-component-data'), | ||
@@ -137,0 +139,0 @@ 'no-side-effects-in-computed-properties': require('./rules/no-side-effects-in-computed-properties'), |
@@ -15,3 +15,3 @@ /** | ||
* @param {string} key | ||
* @returns {Literal | undefined} | ||
* @returns {Literal | TemplateLiteral | undefined} | ||
*/ | ||
@@ -28,3 +28,3 @@ function findPropertyValue(node, key) { | ||
property.type !== 'Property' || | ||
property.value.type !== 'Literal' | ||
!utils.isStringLiteral(property.value) | ||
) { | ||
@@ -38,3 +38,3 @@ return undefined | ||
* @param {RuleFixer} fixer | ||
* @param {Literal} node | ||
* @param {Literal | TemplateLiteral} node | ||
* @param {string} text | ||
@@ -99,6 +99,8 @@ */ | ||
!eventName || | ||
typeof propName.value !== 'string' || | ||
typeof eventName.value !== 'string' || | ||
!allowedPropNames.has(propName.value) || | ||
!allowedEventNames.has(eventName.value) | ||
!allowedPropNames.has( | ||
utils.getStringLiteralValue(propName, true) ?? '' | ||
) || | ||
!allowedEventNames.has( | ||
utils.getStringLiteralValue(eventName, true) ?? '' | ||
) | ||
) { | ||
@@ -105,0 +107,0 @@ context.report({ |
@@ -82,3 +82,4 @@ /** | ||
messages: { | ||
duplicatedKey: "Duplicated key '{{name}}'." | ||
duplicateKey: | ||
"Duplicate key '{{name}}'. May cause name collision in script or template tag." | ||
} | ||
@@ -100,3 +101,3 @@ }, | ||
node: o.node, | ||
messageId: 'duplicatedKey', | ||
messageId: 'duplicateKey', | ||
data: { | ||
@@ -136,3 +137,3 @@ name: o.name | ||
node: variable.defs[0].node, | ||
messageId: 'duplicatedKey', | ||
messageId: 'duplicateKey', | ||
data: { | ||
@@ -139,0 +140,0 @@ name: prop.propName |
/** | ||
* @author Yosuke Ota <https://github.com/ota-meshi> | ||
* @author Yosuke Ota | ||
* See LICENSE file in root directory for full license. | ||
*/ | ||
'use strict' | ||
const baseRule = require('./no-ref-object-reactivity-loss') | ||
const utils = require('../utils') | ||
const { | ||
extractRefObjectReferences, | ||
extractReactiveVariableReferences | ||
} = require('../utils/ref-object-references') | ||
/** | ||
* @typedef {import('../utils/ref-object-references').RefObjectReferences} RefObjectReferences | ||
* @typedef {import('../utils/ref-object-references').RefObjectReference} RefObjectReference | ||
*/ | ||
/** | ||
* Checks whether writing assigns a value to the given pattern. | ||
* @param {Pattern | AssignmentProperty | Property} node | ||
* @returns {boolean} | ||
*/ | ||
function isUpdate(node) { | ||
const parent = node.parent | ||
if (parent.type === 'UpdateExpression' && parent.argument === node) { | ||
// e.g. `pattern++` | ||
return true | ||
} | ||
if (parent.type === 'AssignmentExpression' && parent.left === node) { | ||
// e.g. `pattern = 42` | ||
return true | ||
} | ||
if ( | ||
(parent.type === 'Property' && parent.value === node) || | ||
parent.type === 'ArrayPattern' || | ||
(parent.type === 'ObjectPattern' && | ||
parent.properties.includes(/** @type {any} */ (node))) || | ||
(parent.type === 'AssignmentPattern' && parent.left === node) || | ||
parent.type === 'RestElement' || | ||
(parent.type === 'MemberExpression' && parent.object === node) | ||
) { | ||
return isUpdate(parent) | ||
} | ||
return false | ||
} | ||
module.exports = { | ||
// eslint-disable-next-line eslint-plugin/require-meta-schema, eslint-plugin/prefer-message-ids, internal/no-invalid-meta, eslint-plugin/require-meta-type -- inherit schema from base rule | ||
meta: { | ||
type: 'problem', | ||
...baseRule.meta, | ||
// eslint-disable-next-line eslint-plugin/require-meta-docs-description, internal/no-invalid-meta-docs-categories, eslint-plugin/meta-property-ordering | ||
docs: { | ||
description: | ||
'disallow destructuring of ref objects that can lead to loss of reactivity', | ||
categories: undefined, | ||
...baseRule.meta.docs, | ||
url: 'https://eslint.vuejs.org/rules/no-ref-object-destructure.html' | ||
}, | ||
fixable: null, | ||
schema: [], | ||
messages: { | ||
getValueInSameScope: | ||
'Getting a value from the ref object in the same scope will cause the value to lose reactivity.', | ||
getReactiveVariableInSameScope: | ||
'Getting a reactive variable in the same scope will cause the value to lose reactivity.' | ||
} | ||
deprecated: true, | ||
replacedBy: ['no-ref-object-reactivity-loss'] | ||
}, | ||
/** | ||
* @param {RuleContext} context | ||
* @returns {RuleListener} | ||
*/ | ||
/** @param {RuleContext} context */ | ||
create(context) { | ||
/** | ||
* @typedef {object} ScopeStack | ||
* @property {ScopeStack | null} upper | ||
* @property {Program | FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node | ||
*/ | ||
/** @type {ScopeStack} */ | ||
let scopeStack = { upper: null, node: context.getSourceCode().ast } | ||
/** @type {Map<CallExpression, ScopeStack>} */ | ||
const scopes = new Map() | ||
const refObjectReferences = extractRefObjectReferences(context) | ||
const reactiveVariableReferences = | ||
extractReactiveVariableReferences(context) | ||
/** | ||
* Verify the given ref object value. `refObj = ref(); refObj.value;` | ||
* @param {Expression | Super | ObjectPattern} node | ||
*/ | ||
function verifyRefObjectValue(node) { | ||
const ref = refObjectReferences.get(node) | ||
if (!ref) { | ||
return | ||
} | ||
if (scopes.get(ref.define) !== scopeStack) { | ||
// Not in the same scope | ||
return | ||
} | ||
context.report({ | ||
node, | ||
messageId: 'getValueInSameScope' | ||
}) | ||
} | ||
/** | ||
* Verify the given reactive variable. `refVal = $ref(); refVal;` | ||
* @param {Identifier} node | ||
*/ | ||
function verifyReactiveVariable(node) { | ||
const ref = reactiveVariableReferences.get(node) | ||
if (!ref || ref.escape) { | ||
return | ||
} | ||
if (scopes.get(ref.define) !== scopeStack) { | ||
// Not in the same scope | ||
return | ||
} | ||
context.report({ | ||
node, | ||
messageId: 'getReactiveVariableInSameScope' | ||
}) | ||
} | ||
return { | ||
':function'(node) { | ||
scopeStack = { upper: scopeStack, node } | ||
}, | ||
':function:exit'() { | ||
scopeStack = scopeStack.upper || scopeStack | ||
}, | ||
CallExpression(node) { | ||
scopes.set(node, scopeStack) | ||
}, | ||
/** | ||
* Check for `refObj.value`. | ||
*/ | ||
'MemberExpression:exit'(node) { | ||
if (isUpdate(node)) { | ||
// e.g. `refObj.value = 42`, `refObj.value++` | ||
return | ||
} | ||
const name = utils.getStaticPropertyName(node) | ||
if (name !== 'value') { | ||
return | ||
} | ||
verifyRefObjectValue(node.object) | ||
}, | ||
/** | ||
* Check for `{value} = refObj`. | ||
*/ | ||
'ObjectPattern:exit'(node) { | ||
const prop = utils.findAssignmentProperty(node, 'value') | ||
if (!prop) { | ||
return | ||
} | ||
verifyRefObjectValue(node) | ||
}, | ||
/** | ||
* Check for reactive variable`. | ||
* @param {Identifier} node | ||
*/ | ||
'Identifier:exit'(node) { | ||
if (isUpdate(node)) { | ||
// e.g. `reactiveVariable = 42`, `reactiveVariable++` | ||
return | ||
} | ||
verifyReactiveVariable(node) | ||
} | ||
} | ||
return baseRule.create(context) | ||
} | ||
} |
@@ -6,267 +6,20 @@ /** | ||
'use strict' | ||
const { findVariable } = require('@eslint-community/eslint-utils') | ||
const utils = require('../utils') | ||
const baseRule = require('./no-setup-props-reactivity-loss') | ||
module.exports = { | ||
// eslint-disable-next-line eslint-plugin/require-meta-schema, eslint-plugin/prefer-message-ids, internal/no-invalid-meta, eslint-plugin/require-meta-type -- inherit schema from base rule | ||
meta: { | ||
type: 'suggestion', | ||
...baseRule.meta, | ||
// eslint-disable-next-line eslint-plugin/require-meta-docs-description, internal/no-invalid-meta-docs-categories, eslint-plugin/meta-property-ordering | ||
docs: { | ||
description: 'disallow destructuring of `props` passed to `setup`', | ||
categories: ['vue3-essential', 'essential'], | ||
...baseRule.meta.docs, | ||
url: 'https://eslint.vuejs.org/rules/no-setup-props-destructure.html' | ||
}, | ||
fixable: null, | ||
schema: [], | ||
messages: { | ||
destructuring: | ||
'Destructuring the `props` will cause the value to lose reactivity.', | ||
getProperty: | ||
'Getting a value from the `props` in root scope of `{{scopeName}}` will cause the value to lose reactivity.' | ||
} | ||
deprecated: true, | ||
replacedBy: ['no-setup-props-reactivity-loss'] | ||
}, | ||
/** | ||
* @param {RuleContext} context | ||
* @returns {RuleListener} | ||
**/ | ||
/** @param {RuleContext} context */ | ||
create(context) { | ||
/** | ||
* @typedef {object} ScopePropsReferences | ||
* @property {Set<Identifier>} refs | ||
* @property {string} scopeName | ||
*/ | ||
/** @type {Map<FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program, ScopePropsReferences>} */ | ||
const setupScopePropsReferenceIds = new Map() | ||
const wrapperExpressionTypes = new Set([ | ||
'ArrayExpression', | ||
'ObjectExpression' | ||
]) | ||
/** | ||
* @param {ESNode} node | ||
* @param {string} messageId | ||
* @param {string} scopeName | ||
*/ | ||
function report(node, messageId, scopeName) { | ||
context.report({ | ||
node, | ||
messageId, | ||
data: { | ||
scopeName | ||
} | ||
}) | ||
} | ||
/** | ||
* @param {Pattern} left | ||
* @param {Expression | null} right | ||
* @param {ScopePropsReferences} propsReferences | ||
*/ | ||
function verify(left, right, propsReferences) { | ||
if (!right) { | ||
return | ||
} | ||
const rightNode = utils.skipChainExpression(right) | ||
if ( | ||
wrapperExpressionTypes.has(rightNode.type) && | ||
isPropsMemberAccessed(rightNode, propsReferences) | ||
) { | ||
return report(rightNode, 'getProperty', propsReferences.scopeName) | ||
} | ||
if ( | ||
left.type !== 'ArrayPattern' && | ||
left.type !== 'ObjectPattern' && | ||
rightNode.type !== 'MemberExpression' | ||
) { | ||
return | ||
} | ||
/** @type {Expression | Super} */ | ||
let rightId = rightNode | ||
while (rightId.type === 'MemberExpression') { | ||
rightId = utils.skipChainExpression(rightId.object) | ||
} | ||
if (rightId.type === 'Identifier' && propsReferences.refs.has(rightId)) { | ||
report(left, 'getProperty', propsReferences.scopeName) | ||
} | ||
} | ||
/** | ||
* @param {Expression} node | ||
* @param {ScopePropsReferences} propsReferences | ||
*/ | ||
function isPropsMemberAccessed(node, propsReferences) { | ||
const propRefs = [...propsReferences.refs.values()] | ||
return propRefs.some((props) => { | ||
const isPropsInExpressionRange = utils.inRange(node.range, props) | ||
const isPropsMemberExpression = | ||
props.parent.type === 'MemberExpression' && | ||
props.parent.object === props | ||
return isPropsInExpressionRange && isPropsMemberExpression | ||
}) | ||
} | ||
/** | ||
* @typedef {object} ScopeStack | ||
* @property {ScopeStack | null} upper | ||
* @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program} scopeNode | ||
*/ | ||
/** | ||
* @type {ScopeStack | null} | ||
*/ | ||
let scopeStack = null | ||
/** | ||
* @param {Pattern | null} node | ||
* @param {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | Program} scopeNode | ||
* @param {string} scopeName | ||
*/ | ||
function processPattern(node, scopeNode, scopeName) { | ||
if (!node) { | ||
// no arguments | ||
return | ||
} | ||
if ( | ||
node.type === 'RestElement' || | ||
node.type === 'AssignmentPattern' || | ||
node.type === 'MemberExpression' | ||
) { | ||
// cannot check | ||
return | ||
} | ||
if (node.type === 'ArrayPattern' || node.type === 'ObjectPattern') { | ||
report(node, 'destructuring', scopeName) | ||
return | ||
} | ||
const variable = findVariable(context.getScope(), node) | ||
if (!variable) { | ||
return | ||
} | ||
const propsReferenceIds = new Set() | ||
for (const reference of variable.references) { | ||
// If reference is in another scope, we can't check it. | ||
if (reference.from !== context.getScope()) { | ||
continue | ||
} | ||
if (!reference.isRead()) { | ||
continue | ||
} | ||
propsReferenceIds.add(reference.identifier) | ||
} | ||
setupScopePropsReferenceIds.set(scopeNode, { | ||
refs: propsReferenceIds, | ||
scopeName | ||
}) | ||
} | ||
return utils.compositingVisitors( | ||
{ | ||
/** | ||
* @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | Program} node | ||
*/ | ||
'Program, :function'(node) { | ||
scopeStack = { | ||
upper: scopeStack, | ||
scopeNode: node | ||
} | ||
}, | ||
/** | ||
* @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression | Program} node | ||
*/ | ||
'Program, :function:exit'(node) { | ||
scopeStack = scopeStack && scopeStack.upper | ||
setupScopePropsReferenceIds.delete(node) | ||
}, | ||
/** | ||
* @param {CallExpression} node | ||
*/ | ||
CallExpression(node) { | ||
if (!scopeStack) { | ||
return | ||
} | ||
const propsReferenceIds = setupScopePropsReferenceIds.get( | ||
scopeStack.scopeNode | ||
) | ||
if (!propsReferenceIds) { | ||
return | ||
} | ||
if (isPropsMemberAccessed(node, propsReferenceIds)) { | ||
report(node, 'getProperty', propsReferenceIds.scopeName) | ||
} | ||
}, | ||
/** | ||
* @param {VariableDeclarator} node | ||
*/ | ||
VariableDeclarator(node) { | ||
if (!scopeStack) { | ||
return | ||
} | ||
const propsReferenceIds = setupScopePropsReferenceIds.get( | ||
scopeStack.scopeNode | ||
) | ||
if (!propsReferenceIds) { | ||
return | ||
} | ||
verify(node.id, node.init, propsReferenceIds) | ||
}, | ||
/** | ||
* @param {AssignmentExpression} node | ||
*/ | ||
AssignmentExpression(node) { | ||
if (!scopeStack) { | ||
return | ||
} | ||
const propsReferenceIds = setupScopePropsReferenceIds.get( | ||
scopeStack.scopeNode | ||
) | ||
if (!propsReferenceIds) { | ||
return | ||
} | ||
verify(node.left, node.right, propsReferenceIds) | ||
} | ||
}, | ||
utils.defineScriptSetupVisitor(context, { | ||
onDefinePropsEnter(node) { | ||
let target = node | ||
if ( | ||
target.parent && | ||
target.parent.type === 'CallExpression' && | ||
target.parent.arguments[0] === target && | ||
target.parent.callee.type === 'Identifier' && | ||
target.parent.callee.name === 'withDefaults' | ||
) { | ||
target = target.parent | ||
} | ||
if (!target.parent) { | ||
return | ||
} | ||
/** @type {Pattern|null} */ | ||
let id = null | ||
if (target.parent.type === 'VariableDeclarator') { | ||
id = target.parent.init === target ? target.parent.id : null | ||
} else if (target.parent.type === 'AssignmentExpression') { | ||
id = target.parent.right === target ? target.parent.left : null | ||
} | ||
processPattern(id, context.getSourceCode().ast, '<script setup>') | ||
} | ||
}), | ||
utils.defineVueVisitor(context, { | ||
onSetupFunctionEnter(node) { | ||
const propsParam = utils.skipDefaultParamValue(node.params[0]) | ||
processPattern(propsParam, node, 'setup()') | ||
} | ||
}) | ||
) | ||
return baseRule.create(context) | ||
} | ||
} |
{ | ||
"name": "eslint-plugin-vue", | ||
"version": "9.16.1", | ||
"version": "9.17.0", | ||
"description": "Official ESLint plugin for Vue.js", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1250754
298
40619