Socket
Socket
Sign inDemoInstall

eslint-plugin-vue

Package Overview
Dependencies
Maintainers
5
Versions
170
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-vue - npm Package Compare versions

Comparing version 9.16.1 to 9.17.0

lib/rules/no-ref-object-reactivity-loss.js

1

lib/configs/essential.js

@@ -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",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc