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 7.1.0 to 7.2.0

1

lib/configs/essential.js

@@ -9,3 +9,2 @@ /*

rules: {
'vue/custom-event-name-casing': 'error',
'vue/no-arrow-functions-in-watch': 'error',

@@ -12,0 +11,0 @@ 'vue/no-async-in-computed-properties': 'error',

@@ -9,3 +9,2 @@ /*

rules: {
'vue/custom-event-name-casing': 'error',
'vue/no-arrow-functions-in-watch': 'error',

@@ -12,0 +11,0 @@ 'vue/no-async-in-computed-properties': 'error',

92

lib/rules/custom-event-name-casing.js

@@ -13,3 +13,3 @@ /**

const utils = require('../utils')
const { isKebabCase } = require('../utils/casing')
const casing = require('../utils/casing')
const { toRegExp } = require('../utils/regexp')

@@ -21,10 +21,4 @@

/**
* Check whether the given event name is valid.
* @param {string} name The name to check.
* @returns {boolean} `true` if the given event name is valid.
*/
function isValidEventName(name) {
return isKebabCase(name) || name.startsWith('update:')
}
const ALLOWED_CASE_OPTIONS = ['kebab-case', 'camelCase']
const DEFAULT_CASE = 'kebab-case'

@@ -69,2 +63,14 @@ /**

const OBJECT_OPTION_SCHEMA = {
type: 'object',
properties: {
ignores: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
additionalItems: false
}
},
additionalProperties: false
}
module.exports = {

@@ -74,23 +80,27 @@ meta: {

docs: {
description: 'enforce custom event names always use "kebab-case"',
categories: ['vue3-essential', 'essential'],
description: 'enforce specific casing for custom event name',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/custom-event-name-casing.html'
},
fixable: null,
schema: [
{
type: 'object',
properties: {
ignores: {
type: 'array',
items: { type: 'string' },
uniqueItems: true,
additionalItems: false
}
schema: {
anyOf: [
{
type: 'array',
items: [
{
enum: ALLOWED_CASE_OPTIONS
},
OBJECT_OPTION_SCHEMA
]
},
additionalProperties: false
}
],
// For backward compatibility
{
type: 'array',
items: [OBJECT_OPTION_SCHEMA]
}
]
},
messages: {
unexpected: "Custom event name '{{name}}' must be kebab-case."
unexpected: "Custom event name '{{name}}' must be {{caseType}}."
}

@@ -100,8 +110,25 @@ },

create(context) {
/** @type {Map<ObjectExpression, {contextReferenceIds:Set<Identifier>,emitReferenceIds:Set<Identifier>}>} */
const setupContexts = new Map()
const options = context.options[0] || {}
const options =
context.options.length === 1 && typeof context.options[0] !== 'string'
? // For backward compatibility
[undefined, context.options[0]]
: context.options
const caseType = options[0] || DEFAULT_CASE
const objectOption = options[1] || {}
const caseChecker = casing.getChecker(caseType)
/** @type {RegExp[]} */
const ignores = (options.ignores || []).map(toRegExp)
const ignores = (objectOption.ignores || []).map(toRegExp)
/**
* Check whether the given event name is valid.
* @param {string} name The name to check.
* @returns {boolean} `true` if the given event name is valid.
*/
function isValidEventName(name) {
return caseChecker(name) || name.startsWith('update:')
}
/**
* @param { Literal & { value: string } } nameLiteralNode

@@ -111,3 +138,3 @@ */

const name = nameLiteralNode.value
if (ignores.some((re) => re.test(name)) || isValidEventName(name)) {
if (isValidEventName(name) || ignores.some((re) => re.test(name))) {
return

@@ -119,3 +146,4 @@ }

data: {
name
name,
caseType
}

@@ -200,3 +228,6 @@ })

const { contextReferenceIds, emitReferenceIds } = setupContext
if (emitReferenceIds.has(node.callee)) {
if (
node.callee.type === 'Identifier' &&
emitReferenceIds.has(node.callee)
) {
// verify setup(props,{emit}) {emit()}

@@ -209,2 +240,3 @@ verify(nameLiteralNode)

emit.name === 'emit' &&
emit.member.object.type === 'Identifier' &&
contextReferenceIds.has(emit.member.object)

@@ -211,0 +243,0 @@ ) {

@@ -31,11 +31,15 @@ /**

for (const property of watchValue.properties) {
if (
property.type === 'Property' &&
property.value.type === 'ArrowFunctionExpression'
) {
context.report({
node: property,
message: 'You should not use an arrow function to define a watcher.'
})
if (property.type !== 'Property') {
continue
}
for (const handler of utils.iterateWatchHandlerValues(property)) {
if (handler.type === 'ArrowFunctionExpression') {
context.report({
node: handler,
message:
'You should not use an arrow function to define a watcher.'
})
}
}
}

@@ -42,0 +46,0 @@ })

@@ -637,22 +637,15 @@ /**

if (property.kind === 'init') {
/** @type {Expression | null} */
let handlerValueNode = null
if (property.value.type === 'ObjectExpression') {
const handler = utils.findProperty(property.value, 'handler')
if (handler) {
handlerValueNode = handler.value
for (const handlerValueNode of utils.iterateWatchHandlerValues(
property
)) {
if (
handlerValueNode.type === 'Literal' ||
handlerValueNode.type === 'TemplateLiteral'
) {
const name = utils.getStringLiteralValue(handlerValueNode)
if (name != null) {
watcherUsedProperties.add(name)
}
}
} else {
handlerValueNode = property.value
}
if (
handlerValueNode &&
(handlerValueNode.type === 'Literal' ||
handlerValueNode.type === 'TemplateLiteral')
) {
const name = utils.getStringLiteralValue(handlerValueNode)
if (name != null) {
watcherUsedProperties.add(name)
}
}
}

@@ -703,7 +696,7 @@ }

'computed' ||
utils.getStaticPropertyName(property) !== 'handler'
utils.getStaticPropertyName(property) !== 'get'
) {
return
}
// check { computed: { foo: { handler: (vm) => vm.prop } } }
// check { computed: { foo: { get: () => vm.prop } } }
} else {

@@ -710,0 +703,0 @@ return

@@ -14,22 +14,2 @@ /**

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------
/**
* Check whether the given node is an well-known element or not.
* @param {VElement} node The element node to check.
* @returns {boolean} `true` if the name is an well-known element name.
*/
function isWellKnownElement(node) {
if (
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
utils.isHtmlWellKnownElementName(node.rawName) ||
utils.isSvgWellKnownElementName(node.rawName)
) {
return true
}
return false
}
// ------------------------------------------------------------------------------
// Rule Definition

@@ -65,5 +45,2 @@ // ------------------------------------------------------------------------------

}
if (!isWellKnownElement(element)) {
return
}
if (

@@ -70,0 +47,0 @@ !utils.hasDirective(element, 'if') &&

@@ -12,2 +12,6 @@ /**

/**
* @typedef { import('../utils').ComponentPropertyData } ComponentPropertyData
*/
// ------------------------------------------------------------------------------

@@ -100,75 +104,108 @@ // Helpers

return utils.defineTemplateBodyVisitor(context, {
...(always
? {
/** @param {Identifier} node */
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer > Identifier"(
node
) {
context.report({
node,
message:
"Method calls inside of 'v-on' directives must have parentheses."
})
}
if (always) {
return utils.defineTemplateBodyVisitor(context, {
/** @param {Identifier} node */
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] > VExpressionContainer > Identifier"(
node
) {
context.report({
node,
message:
"Method calls inside of 'v-on' directives must have parentheses."
})
}
})
}
const option = context.options[1] || {}
const ignoreIncludesComment = !!option.ignoreIncludesComment
/** @type {Set<string>} */
const useArgsMethods = new Set()
return utils.defineTemplateBodyVisitor(
context,
{
/** @param {VOnExpression} node */
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] VOnExpression"(
node
) {
const expression = getInvalidNeverCallExpression(node)
if (!expression) {
return
}
: {
/** @param {VOnExpression} node */
"VAttribute[directive=true][key.name.name='on'][key.argument!=null] VOnExpression"(
node
) {
const expression = getInvalidNeverCallExpression(node)
if (!expression) {
return
}
const option = context.options[1] || {}
const ignoreIncludesComment = !!option.ignoreIncludesComment
const tokenStore = context.parserServices.getTemplateBodyTokenStore()
const tokens = tokenStore.getTokens(node.parent, {
includeComments: true
})
/** @type {Token | undefined} */
let leftQuote
/** @type {Token | undefined} */
let rightQuote
if (isQuote(tokens[0])) {
leftQuote = tokens.shift()
rightQuote = tokens.pop()
}
const tokenStore = context.parserServices.getTemplateBodyTokenStore()
const tokens = tokenStore.getTokens(node.parent, {
includeComments: true
})
/** @type {Token | undefined} */
let leftQuote
/** @type {Token | undefined} */
let rightQuote
if (isQuote(tokens[0])) {
leftQuote = tokens.shift()
rightQuote = tokens.pop()
}
const hasComment = tokens.some(
(token) => token.type === 'Block' || token.type === 'Line'
)
const hasComment = tokens.some(
(token) => token.type === 'Block' || token.type === 'Line'
)
if (ignoreIncludesComment && hasComment) {
return
}
if (ignoreIncludesComment && hasComment) {
return
}
context.report({
node: expression,
message:
"Method calls without arguments inside of 'v-on' directives must not have parentheses.",
fix: hasComment
? null /* The comment is included and cannot be fixed. */
: (fixer) => {
/** @type {Range} */
const range =
leftQuote && rightQuote
? [leftQuote.range[1], rightQuote.range[0]]
: [
tokens[0].range[0],
tokens[tokens.length - 1].range[1]
]
if (expression.callee.type === 'Identifier') {
if (useArgsMethods.has(expression.callee.name)) {
// The behavior of target method can change given the arguments.
return
}
}
return fixer.replaceTextRange(
range,
context.getSourceCode().getText(expression.callee)
)
}
})
context.report({
node: expression,
message:
"Method calls without arguments inside of 'v-on' directives must not have parentheses.",
fix: hasComment
? null /* The comment is included and cannot be fixed. */
: (fixer) => {
/** @type {Range} */
const range =
leftQuote && rightQuote
? [leftQuote.range[1], rightQuote.range[0]]
: [tokens[0].range[0], tokens[tokens.length - 1].range[1]]
return fixer.replaceTextRange(
range,
context.getSourceCode().getText(expression.callee)
)
}
})
}
},
utils.defineVueVisitor(context, {
onVueObjectEnter(node) {
for (const method of utils.iterateProperties(
node,
new Set(['methods'])
)) {
if (useArgsMethods.has(method.name)) {
continue
}
})
})
if (method.type !== 'object') {
continue
}
const value = method.property.value
if (
(value.type === 'FunctionExpression' ||
value.type === 'ArrowFunctionExpression') &&
value.params.length > 0
) {
useArgsMethods.add(method.name)
}
}
}
})
)
}
}

@@ -23,7 +23,3 @@ /**

function isValidElement(node) {
if (
(!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) ||
utils.isHtmlWellKnownElementName(node.rawName) ||
utils.isSvgWellKnownElementName(node.rawName)
) {
if (!utils.isCustomComponent(node)) {
// non Vue-component

@@ -30,0 +26,0 @@ return false

@@ -10,2 +10,6 @@ /**

/**
* @typedef { { expr: VForExpression, variables: VVariable[] } } VSlotVForVariables
*/
/**
* Get all `v-slot` directives on a given element.

@@ -97,12 +101,36 @@ * @param {VElement} node The VElement node to check.

* @param {VDirective} currentVSlot The current `v-slot` directive node.
* @param {VSlotVForVariables | null} currentVSlotVForVars The current `v-for` variables.
* @param {SourceCode} sourceCode The source code.
* @param {ParserServices.TokenStore} tokenStore The token store.
* @returns {VDirective[][]} The array of the group of `v-slot` directives.
*/
function filterSameSlot(vSlotGroups, currentVSlot, sourceCode) {
function filterSameSlot(
vSlotGroups,
currentVSlot,
currentVSlotVForVars,
sourceCode,
tokenStore
) {
const currentName = getNormalizedName(currentVSlot, sourceCode)
return vSlotGroups
.map((vSlots) =>
vSlots.filter(
(vSlot) => getNormalizedName(vSlot, sourceCode) === currentName
)
vSlots.filter((vSlot) => {
if (getNormalizedName(vSlot, sourceCode) !== currentName) {
return false
}
const vForExpr = getVSlotVForVariableIfUsingIterationVars(
vSlot,
utils.getDirective(vSlot.parent.parent, 'for')
)
if (!currentVSlotVForVars || !vForExpr) {
return !currentVSlotVForVars && !vForExpr
}
if (
!equalVSlotVForVariables(currentVSlotVForVars, vForExpr, tokenStore)
) {
return false
}
//
return true
})
)

@@ -113,8 +141,85 @@ .filter((slots) => slots.length >= 1)

/**
* Check whether a given argument node is using an iteration variable that the element defined.
* Determines whether the two given `v-slot` variables are considered to be equal.
* @param {VSlotVForVariables} a First element.
* @param {VSlotVForVariables} b Second element.
* @param {ParserServices.TokenStore} tokenStore The token store.
* @returns {boolean} `true` if the elements are considered to be equal.
*/
function equalVSlotVForVariables(a, b, tokenStore) {
if (a.variables.length !== b.variables.length) {
return false
}
if (!equal(a.expr.right, b.expr.right)) {
return false
}
const checkedVarNames = new Set()
const len = Math.min(a.expr.left.length, b.expr.left.length)
for (let index = 0; index < len; index++) {
const aPtn = a.expr.left[index]
const bPtn = b.expr.left[index]
const aVar = a.variables.find(
(v) => aPtn.range[0] <= v.id.range[0] && v.id.range[1] <= aPtn.range[1]
)
const bVar = b.variables.find(
(v) => bPtn.range[0] <= v.id.range[0] && v.id.range[1] <= bPtn.range[1]
)
if (aVar && bVar) {
if (aVar.id.name !== bVar.id.name) {
return false
}
if (!equal(aPtn, bPtn)) {
return false
}
checkedVarNames.add(aVar.id.name)
} else if (aVar || bVar) {
return false
}
}
for (const v of a.variables) {
if (!checkedVarNames.has(v.id.name)) {
if (b.variables.every((bv) => v.id.name !== bv.id.name)) {
return false
}
}
}
return true
/**
* Determines whether the two given nodes are considered to be equal.
* @param {ASTNode} a First node.
* @param {ASTNode} b Second node.
* @returns {boolean} `true` if the nodes are considered to be equal.
*/
function equal(a, b) {
if (a.type !== b.type) {
return false
}
return utils.equalTokens(a, b, tokenStore)
}
}
/**
* Gets the `v-for` directive and variable that provide the variables used by the given` v-slot` directive.
* @param {VDirective} vSlot The current `v-slot` directive node.
* @param {VDirective | null} [vFor] The current `v-for` directive node.
* @returns { VSlotVForVariables | null } The VSlotVForVariable.
*/
function getVSlotVForVariableIfUsingIterationVars(vSlot, vFor) {
const expr =
vFor && vFor.value && /** @type {VForExpression} */ (vFor.value.expression)
const variables =
expr && getUsingIterationVars(vSlot.key.argument, vSlot.parent.parent)
return expr && variables && variables.length ? { expr, variables } : null
}
/**
* Gets iterative variables if a given argument node is using iterative variables that the element defined.
* @param {VExpressionContainer|VIdentifier|null} argument The argument node to check.
* @param {VElement} element The element node which has the argument.
* @returns {boolean} `true` if the argument node is using the iteration variable.
* @returns {VVariable[]} The argument node is using iteration variables.
*/
function isUsingIterationVar(argument, element) {
function getUsingIterationVars(argument, element) {
const vars = []
if (argument && argument.type === 'VExpressionContainer') {

@@ -128,7 +233,7 @@ for (const { variable } of argument.references) {

) {
return true
vars.push(variable)
}
}
}
return false
return vars
}

@@ -213,2 +318,5 @@

const sourceCode = context.getSourceCode()
const tokenStore =
context.parserServices.getTemplateBodyTokenStore &&
context.parserServices.getTemplateBodyTokenStore()
const options = context.options[0] || {}

@@ -264,8 +372,14 @@ const allowModifiers = options.allowModifiers === true

if (ownerElement === parentElement) {
const vFor = utils.getDirective(element, 'for')
const vSlotVForVar = getVSlotVForVariableIfUsingIterationVars(
node,
vFor
)
const vSlotGroupsOfSameSlot = filterSameSlot(
vSlotGroupsOnChildren,
node,
sourceCode
vSlotVForVar,
sourceCode,
tokenStore
)
const vFor = utils.getDirective(element, 'for')
if (

@@ -282,3 +396,3 @@ vSlotGroupsOfSameSlot.length >= 2 &&

}
if (vFor && !isUsingIterationVar(node.key.argument, element)) {
if (vFor && !vSlotVForVar) {
// E.g., <template v-for="x of xs" #one></template>

@@ -285,0 +399,0 @@ context.report({

@@ -593,2 +593,4 @@ /**

!this.isHtmlWellKnownElementName(node.rawName)) ||
(this.isSvgElementNode(node) &&
!this.isSvgWellKnownElementName(node.rawName)) ||
this.hasAttribute(node, 'is') ||

@@ -1479,2 +1481,9 @@ this.hasDirective(node, 'bind', 'is') ||

/**
* Return generator with the all handler nodes defined in the given watcher property.
* @param {Property|Expression} property
* @returns {IterableIterator<Expression>}
*/
iterateWatchHandlerValues,
/**
* Wraps composition API trace map in both 'vue' and '@vue/composition-api' imports

@@ -1993,1 +2002,24 @@ * @param {import('eslint-utils').TYPES.TraceMap} map

}
/**
* Return generator with the all handler nodes defined in the given watcher property.
* @param {Property|Expression} property
* @returns {IterableIterator<Expression>}
*/
function* iterateWatchHandlerValues(property) {
const value = property.type === 'Property' ? property.value : property
if (value.type === 'ObjectExpression') {
const handler = findProperty(value, 'handler')
if (handler) {
yield handler.value
}
} else if (value.type === 'ArrayExpression') {
for (const element of value.elements.filter(isDef)) {
if (element.type !== 'SpreadElement') {
yield* iterateWatchHandlerValues(element)
}
}
} else {
yield value
}
}
{
"name": "eslint-plugin-vue",
"version": "7.1.0",
"version": "7.2.0",
"description": "Official ESLint plugin for Vue.js",

@@ -58,3 +58,3 @@ "main": "lib/index.js",

"semver": "^7.3.2",
"vue-eslint-parser": "^7.1.1"
"vue-eslint-parser": "^7.2.0"
},

@@ -61,0 +61,0 @@ "devDependencies": {

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