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.0.0-alpha.10 to 7.0.0-beta.0

lib/rules/syntaxes/v-is.js

2

lib/configs/base.js

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

parserOptions: {
ecmaVersion: 2018,
ecmaVersion: 2020,
sourceType: 'module'

@@ -12,0 +12,0 @@ },

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

'vue/valid-v-if': 'error',
'vue/valid-v-is': 'error',
'vue/valid-v-model': 'error',

@@ -65,0 +66,0 @@ 'vue/valid-v-on': 'error',

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

'vue/require-default-prop': 'warn',
'vue/require-explicit-emits': 'warn',
'vue/require-prop-types': 'warn',

@@ -28,0 +29,0 @@ 'vue/singleline-html-element-content-newline': 'warn',

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

'valid-v-if': require('./rules/valid-v-if'),
'valid-v-is': require('./rules/valid-v-is'),
'valid-v-model': require('./rules/valid-v-model'),

@@ -163,0 +164,0 @@ 'valid-v-on': require('./rules/valid-v-on'),

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

return ATTRS.UNIQUE
} else if (name === 'is') {
return ATTRS.DEFINITION
} else {

@@ -89,0 +91,0 @@ return ATTRS.OTHER_DIRECTIVES

@@ -138,12 +138,9 @@ /**

},
fix: (fixer) => {
*fix(fixer) {
yield fixer.replaceText(open, `<${casingName}`)
const endTag = node.endTag
if (!endTag) {
return fixer.replaceText(open, `<${casingName}`)
if (endTag) {
const endTagOpen = tokens.getFirstToken(endTag)
yield fixer.replaceText(endTagOpen, `</${casingName}`)
}
const endTagOpen = tokens.getFirstToken(endTag)
return [
fixer.replaceText(open, `<${casingName}`),
fixer.replaceText(endTagOpen, `</${casingName}`)
]
}

@@ -150,0 +147,0 @@ })

@@ -51,3 +51,3 @@ /**

function getCalleeMemberNode(node) {
const callee = node.callee
const callee = utils.skipChainExpression(node.callee)

@@ -120,3 +120,3 @@ if (callee.type === 'MemberExpression') {

onSetupFunctionEnter(node, { node: vueNode }) {
const contextParam = utils.unwrapAssignmentPattern(node.params[1])
const contextParam = utils.skipDefaultParamValue(node.params[1])
if (!contextParam) {

@@ -123,0 +123,0 @@ // no arguments

@@ -166,3 +166,3 @@ /**

},
fix: (fixer) => {
fix(fixer) {
const tokens = context.parserServices.getTemplateBodyTokenStore()

@@ -191,3 +191,3 @@ const close = tokens.getLastToken(node.startTag)

},
fix: (fixer) => {
fix(fixer) {
const tokens = context.parserServices.getTemplateBodyTokenStore()

@@ -194,0 +194,0 @@ const close = tokens.getLastToken(node.startTag)

@@ -29,12 +29,13 @@ /**

function isTimedFunction(node) {
const callee = utils.skipChainExpression(node.callee)
return (
((node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
TIMED_FUNCTIONS.indexOf(node.callee.name) !== -1) ||
callee.type === 'Identifier' &&
TIMED_FUNCTIONS.indexOf(callee.name) !== -1) ||
(node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'window' &&
node.callee.property.type === 'Identifier' &&
TIMED_FUNCTIONS.indexOf(node.callee.property.name) !== -1)) &&
callee.type === 'MemberExpression' &&
callee.object.type === 'Identifier' &&
callee.object.name === 'window' &&
callee.property.type === 'Identifier' &&
TIMED_FUNCTIONS.indexOf(callee.property.name) !== -1)) &&
node.arguments.length

@@ -48,14 +49,12 @@ )

function isPromise(node) {
if (
node.type === 'CallExpression' &&
node.callee.type === 'MemberExpression'
) {
const callee = utils.skipChainExpression(node.callee)
if (node.type === 'CallExpression' && callee.type === 'MemberExpression') {
return (
// hello.PROMISE_FUNCTION()
(node.callee.property.type === 'Identifier' &&
PROMISE_FUNCTIONS.indexOf(node.callee.property.name) !== -1) || // Promise.PROMISE_METHOD()
(node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'Promise' &&
node.callee.property.type === 'Identifier' &&
PROMISE_METHODS.indexOf(node.callee.property.name) !== -1)
(callee.property.type === 'Identifier' &&
PROMISE_FUNCTIONS.indexOf(callee.property.name) !== -1) || // Promise.PROMISE_METHOD()
(callee.object.type === 'Identifier' &&
callee.object.name === 'Promise' &&
callee.property.type === 'Identifier' &&
PROMISE_METHODS.indexOf(callee.property.name) !== -1)
)

@@ -62,0 +61,0 @@ }

@@ -35,9 +35,22 @@ /**

return utils.defineVueVisitor(context, {
/** @param {MemberExpression & {parent: CallExpression}} node */
'CallExpression > MemberExpression'(node) {
const call = node.parent
/** @param {MemberExpression & ({parent: CallExpression} | {parent: ChainExpression & {parent: CallExpression}})} node */
'CallExpression > MemberExpression, CallExpression > ChainExpression > MemberExpression'(
node
) {
const call =
node.parent.type === 'ChainExpression'
? node.parent.parent
: node.parent
if (call.optional) {
// It is OK because checking whether it is deprecated.
// e.g. `this.$on?.()`
return
}
if (
call.callee !== node ||
node.property.type !== 'Identifier' ||
!['$on', '$off', '$once'].includes(node.property.name)
utils.skipChainExpression(call.callee) !== node ||
!['$on', '$off', '$once'].includes(
utils.getStaticPropertyName(node) || ''
)
) {

@@ -44,0 +57,0 @@ return

@@ -42,3 +42,3 @@ /**

messageId: 'syncModifierIsDeprecated',
fix: (fixer) => {
fix(fixer) {
if (node.key.argument == null) {

@@ -45,0 +45,0 @@ // is using spread syntax

@@ -50,3 +50,3 @@ /**

messageId: 'numberModifierIsDeprecated',
fix: (fixer) => {
fix(fixer) {
const key = keyCodeToKey[keyCodes]

@@ -53,0 +53,0 @@ if (!key) return null

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

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

@@ -35,3 +37,3 @@ // Rule Definition

) {
const config = node.object
const config = utils.skipChainExpression(node.object)
if (

@@ -38,0 +40,0 @@ config.type !== 'MemberExpression' ||

@@ -103,11 +103,8 @@ /**

MemberExpression(node) {
const object = node.object
const object = utils.skipChainExpression(node.object)
if (object.type !== 'MemberExpression') {
return
}
if (
object.property.type !== 'Identifier' ||
(object.property.name !== '$slots' &&
object.property.name !== '$scopedSlots')
) {
const name = utils.getStaticPropertyName(object)
if (!name || (name !== '$slots' && name !== '$scopedSlots')) {
return

@@ -114,0 +111,0 @@ }

@@ -52,14 +52,14 @@ /**

const rightNode = utils.skipChainExpression(right)
if (
left.type !== 'ArrayPattern' &&
left.type !== 'ObjectPattern' &&
right.type !== 'MemberExpression'
rightNode.type !== 'MemberExpression'
) {
return
}
/** @type {Expression | Super} */
let rightId = right
let rightId = rightNode
while (rightId.type === 'MemberExpression') {
rightId = rightId.object
rightId = utils.skipChainExpression(rightId.object)
}

@@ -88,3 +88,3 @@ if (rightId.type === 'Identifier' && propsReferenceIds.has(rightId)) {

onSetupFunctionEnter(node) {
const propsParam = utils.unwrapAssignmentPattern(node.params[0])
const propsParam = utils.skipDefaultParamValue(node.params[0])
if (!propsParam) {

@@ -91,0 +91,0 @@ // no arguments

@@ -98,3 +98,3 @@ /**

/** @param {VDirective} node */
"VAttribute[directive=true][key.name.name='bind'][key.argument.name='is']"(
"VAttribute[directive=true][key.name.name='bind'][key.argument.name='is'], VAttribute[directive=true][key.name.name='is']"(
node

@@ -101,0 +101,0 @@ ) {

@@ -27,3 +27,4 @@ /**

'v-model-argument': require('./syntaxes/v-model-argument'),
'v-model-custom-modifiers': require('./syntaxes/v-model-custom-modifiers')
'v-model-custom-modifiers': require('./syntaxes/v-model-custom-modifiers'),
'v-is': require('./syntaxes/v-is')
}

@@ -97,3 +98,4 @@

forbiddenVModelCustomModifiers:
'Custom modifiers on `v-model` are not supported until Vue.js "3.0.0".'
'Custom modifiers on `v-model` are not supported until Vue.js "3.0.0".',
forbiddenVIs: '`v-is` are not supported until Vue.js "3.0.0".'
}

@@ -100,0 +102,0 @@ },

@@ -70,3 +70,3 @@ /**

/** @param {VDirective} node */
"VAttribute[directive=true][key.name.name='bind'][key.argument.name='is']"(
"VAttribute[directive=true][key.name.name='bind'][key.argument.name='is'], VAttribute[directive=true][key.name.name='is']"(
node

@@ -73,0 +73,0 @@ ) {

@@ -224,3 +224,3 @@ /**

/**
* @param {Identifier | MemberExpression | ThisExpression} node
* @param {Identifier | MemberExpression | ChainExpression | ThisExpression} node
* @param {RuleContext} context

@@ -308,2 +308,10 @@ * @returns {UsedProps}

}
} else if (parent.type === 'ChainExpression') {
const { usedNames, unknown, calls } = extractPatternOrThisProperties(
parent,
context
)
result.usedNames.addAll(usedNames)
result.unknown = result.unknown || unknown
result.calls.push(...calls)
}

@@ -580,3 +588,3 @@ return result

const container = getVueComponentPropertiesContainer(node)
const watcherNames = new Set()
const watcherUsedProperties = new Set()
for (const watcher of utils.iterateProperties(

@@ -586,10 +594,38 @@ node,

)) {
// Process `watch: { foo /* <- this */ () {} }`
let path
for (const seg of watcher.name.split('.')) {
path = path ? `${path}.${seg}` : seg
watcherNames.add(path)
watcherUsedProperties.add(path)
}
// Process `watch: { x: 'foo' /* <- this */ }`
if (watcher.type === 'object') {
const property = watcher.property
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
}
} else {
handlerValueNode = property.value
}
if (
handlerValueNode &&
(handlerValueNode.type === 'Literal' ||
handlerValueNode.type === 'TemplateLiteral')
) {
const name = utils.getStringLiteralValue(handlerValueNode)
if (name != null) {
watcherUsedProperties.add(name)
}
}
}
}
}
for (const prop of utils.iterateProperties(node, groups)) {
if (watcherNames.has(prop.name)) {
if (watcherUsedProperties.has(prop.name)) {
continue

@@ -596,0 +632,0 @@ }

/**
* @author Yosuke Ota
*
* issue https://github.com/vuejs/eslint-plugin-vue/issues/403
* Style guide: https://vuejs.org/v2/style-guide/#Avoid-v-if-with-v-for-essential
*
* I implemented it with reference to `no-confusing-v-for-v-if`
* Style guide: https://v3.vuejs.org/style-guide/#avoid-v-if-with-v-for-essential
*/

@@ -9,0 +6,0 @@ 'use strict'

@@ -145,3 +145,3 @@ /**

return [fixer.replaceText(node, text.replace(/\\([\s\S])/g, '$1'))]
return fixer.replaceText(node, text.replace(/\\([\s\S])/g, '$1'))
}

@@ -148,0 +148,0 @@ })

@@ -114,6 +114,6 @@ /**

messageId: 'unexpected',
fix(fixer) {
*fix(fixer) {
if (hasComment || hasEscape) {
// cannot fix
return null
return
}

@@ -130,2 +130,4 @@ const text = sourceCode.getText(value)

yield fixer.removeRange(keyDirectiveRange)
let attrValue

@@ -141,6 +143,3 @@ if (quoteChar === '"') {

}
return [
fixer.removeRange(keyDirectiveRange),
fixer.replaceText(expression, attrValue)
]
yield fixer.replaceText(expression, attrValue)
}

@@ -147,0 +146,0 @@ })

@@ -10,3 +10,4 @@ /**

/**
* @param {CallExpression} node
* @param {CallExpression | ChainExpression} node
* @returns {boolean}
*/

@@ -36,2 +37,5 @@ function isMaybeUsedStopHandle(node) {

}
if (parent.type === 'ChainExpression') {
return isMaybeUsedStopHandle(parent)
}
}

@@ -38,0 +42,0 @@ return false

@@ -188,3 +188,5 @@ /**

node.type !== 'SpreadElement' &&
node.type !== 'TemplateLiteral'
node.type !== 'TemplateLiteral' &&
// es2020
node.type !== 'ChainExpression'
) {

@@ -293,3 +295,3 @@ // Can not be sure that a node has no side effects

},
fix(fixer) {
*fix(fixer) {
const propertyNode = property.node

@@ -307,3 +309,3 @@ const firstUnorderedPropertyNode = firstUnorderedProperty.node

if (hasSideEffectsPossibility) {
return null
return
}

@@ -319,2 +321,7 @@ const afterComma = sourceCode.getTokenAfter(propertyNode)

const removeStart = hasAfterComma
? codeStart
: beforeComma.range[0]
yield fixer.removeRange([removeStart, codeEnd])
const propertyCode =

@@ -327,10 +334,3 @@ sourceCode.text.slice(codeStart, codeEnd) +

const removeStart = hasAfterComma
? codeStart
: beforeComma.range[0]
return [
fixer.removeRange([removeStart, codeEnd]),
fixer.insertTextAfter(insertTarget, propertyCode)
]
yield fixer.insertTextAfter(insertTarget, propertyCode)
}

@@ -337,0 +337,0 @@ })

@@ -51,4 +51,4 @@ /**

messageId: 'never',
fix(fixer) {
return paddingLines.map(([prevToken, nextToken]) => {
*fix(fixer) {
for (const [prevToken, nextToken] of paddingLines) {
const start = prevToken.range[1]

@@ -58,4 +58,4 @@ const end = nextToken.range[0]

const lastSpaces = splitLines(paddingText).pop()
return fixer.replaceTextRange([start, end], `\n${lastSpaces}`)
})
yield fixer.replaceTextRange([start, end], `\n${lastSpaces}`)
}
}

@@ -62,0 +62,0 @@ })

@@ -85,8 +85,14 @@ /**

if (prop.value.type !== 'ObjectExpression') {
return (
(prop.value.type !== 'CallExpression' &&
prop.value.type !== 'Identifier') ||
(prop.value.type === 'Identifier' &&
NATIVE_TYPES.has(prop.value.name))
)
if (prop.value.type === 'Identifier') {
return NATIVE_TYPES.has(prop.value.name)
}
if (
prop.value.type === 'CallExpression' ||
prop.value.type === 'MemberExpression'
) {
// OK
return false
}
// NG
return true
}

@@ -103,3 +109,3 @@

* Detects whether given value node is a Boolean type
* @param {Expression | Pattern} value
* @param {Expression} value
* @return {boolean}

@@ -128,3 +134,3 @@ */

function isBooleanProp(prop) {
const value = utils.unwrapTypes(prop.value)
const value = utils.skipTSAsExpression(prop.value)

@@ -131,0 +137,0 @@ return (

@@ -88,3 +88,3 @@ /**

description: 'require `emits` option with name triggered by `$emit()`',
categories: undefined,
categories: ['vue3-strongly-recommended'],
url: 'https://eslint.vuejs.org/rules/require-explicit-emits.html'

@@ -265,3 +265,3 @@ },

'CallExpression[arguments.0.type=Literal]'(node, { node: vueNode }) {
const callee = node.callee
const callee = utils.skipChainExpression(node.callee)
const nameLiteralNode = node.arguments[0]

@@ -292,10 +292,11 @@ if (!nameLiteralNode || typeof nameLiteralNode.value !== 'string') {

verify(emitsDeclarations, nameLiteralNode, vueNode)
} else if (
emit &&
emit.name === 'emit' &&
emit.member.object.type === 'Identifier' &&
contextReferenceIds.has(emit.member.object)
) {
// verify setup(props,context) {context.emit()}
verify(emitsDeclarations, nameLiteralNode, vueNode)
} else if (emit && emit.name === 'emit') {
const memObject = utils.skipChainExpression(emit.member.object)
if (
memObject.type === 'Identifier' &&
contextReferenceIds.has(memObject)
) {
// verify setup(props,context) {context.emit()}
verify(emitsDeclarations, nameLiteralNode, vueNode)
}
}

@@ -306,3 +307,4 @@ }

if (emit && emit.name === '$emit') {
if (utils.isThis(emit.member.object, context)) {
const memObject = utils.skipChainExpression(emit.member.object)
if (utils.isThis(memObject, context)) {
// verify this.$emit()

@@ -309,0 +311,0 @@ verify(emitsDeclarations, nameLiteralNode, vueNode)

@@ -36,3 +36,3 @@ /**

* Verify the given node
* @param {MemberExpression | Identifier} node The node to verify
* @param {MemberExpression | Identifier | ChainExpression} node The node to verify
* @param {Expression} reportNode The node to report

@@ -62,2 +62,8 @@ */

if (parent.type === 'ChainExpression') {
// (this.$slots?.foo).x
verify(parent, reportNode)
return
}
if (

@@ -102,10 +108,7 @@ // this.$slots.foo.xxx

MemberExpression(node) {
const object = node.object
const object = utils.skipChainExpression(node.object)
if (object.type !== 'MemberExpression') {
return
}
if (
object.property.type !== 'Identifier' ||
object.property.name !== '$slots'
) {
if (utils.getStaticPropertyName(object) !== '$slots') {
return

@@ -112,0 +115,0 @@ }

@@ -51,3 +51,3 @@ /**

/**
* @param {Expression | Pattern} node
* @param {Expression} node
* @returns {string[]}

@@ -126,6 +126,7 @@ */

/**
* @param {Expression | Pattern} node
* @param {Expression} targetNode
* @returns { StandardValueType | FunctionExprValueType | FunctionValueType | null }
*/
function getValueType(node) {
function getValueType(targetNode) {
const node = utils.skipChainExpression(targetNode)
if (node.type === 'CallExpression') {

@@ -132,0 +133,0 @@ // Symbol(), Number() ...

@@ -59,5 +59,5 @@ /**

* @param {boolean} vBind `true` if `slotAttr` is `v-bind:slot`
* @returns {Fix[]} fix data
* @returns {IterableIterator<Fix>} fix data
*/
function fixSlotToVSlot(fixer, slotAttr, slotName, vBind) {
function* fixSlotToVSlot(fixer, slotAttr, slotName, vBind) {
const element = slotAttr.parent

@@ -82,7 +82,6 @@ const scopeAttr = element.attributes.find(

const replaceText = `v-slot${nameArgument}${scopeValue}`
const fixers = [fixer.replaceText(slotAttr || scopeAttr, replaceText)]
yield fixer.replaceText(slotAttr || scopeAttr, replaceText)
if (slotAttr && scopeAttr) {
fixers.push(fixer.remove(scopeAttr))
yield fixer.remove(scopeAttr)
}
return fixers
}

@@ -99,8 +98,8 @@ /**

// fix to use `v-slot`
fix(fixer) {
*fix(fixer) {
if (!canConvertFromSlotToVSlot(slotAttr)) {
return null
return
}
const slotName = slotAttr.value && slotAttr.value.value
return fixSlotToVSlot(fixer, slotAttr, slotName, false)
yield* fixSlotToVSlot(fixer, slotAttr, slotName, false)
}

@@ -119,5 +118,5 @@ })

// fix to use `v-slot`
fix(fixer) {
*fix(fixer) {
if (!canConvertFromVBindSlotToVSlot(slotAttr)) {
return null
return
}

@@ -128,3 +127,3 @@ const slotName =

sourceCode.getText(slotAttr.value.expression).trim()
return fixSlotToVSlot(fixer, slotAttr, slotName, true)
yield* fixSlotToVSlot(fixer, slotAttr, slotName, true)
}

@@ -131,0 +130,0 @@ })

@@ -77,12 +77,13 @@ /**

messageId: 'forbiddenSlotScopeAttribute',
fix: fixToUpgrade
? // fix to use `v-slot`
(fixer) => {
const startTag = scopeAttr.parent
if (!canConvertToVSlot(startTag)) {
return null
}
return fixSlotScopeToVSlot(fixer, scopeAttr)
}
: null
fix(fixer) {
if (!fixToUpgrade) {
return null
}
// fix to use `v-slot`
const startTag = scopeAttr.parent
if (!canConvertToVSlot(startTag)) {
return null
}
return fixSlotScopeToVSlot(fixer, scopeAttr)
}
})

@@ -89,0 +90,0 @@ }

@@ -72,3 +72,3 @@ /**

// fix to use `slot` (downgrade)
fix: (fixer) => {
fix(fixer) {
if (!canConvertToSlot(vSlotAttr)) {

@@ -75,0 +75,0 @@ return null

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

}
if (expression.optional) {
// Allow optional chaining
return null
}
const callee = expression.callee

@@ -90,0 +94,0 @@ if (callee.type !== 'Identifier') {

@@ -35,4 +35,19 @@ /**

/**
* Check whether the given node can be LHS.
* Check whether the given node is a MemberExpression containing an optional chaining.
* e.g.
* - `a?.b`
* - `a?.b.c`
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is a MemberExpression containing an optional chaining.
*/
function isOptionalChainingMemberExpression(node) {
return (
node.type === 'ChainExpression' &&
node.expression.type === 'MemberExpression'
)
}
/**
* Check whether the given node can be LHS (left-hand side).
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node can be LHS.

@@ -44,2 +59,29 @@ */

/**
* Check whether the given node is a MemberExpression of a possibly null object.
* e.g.
* - `(a?.b).c`
* - `(null).foo`
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is a MemberExpression of a possibly null object.
*/
function maybeNullObjectMemberExpression(node) {
if (node.type !== 'MemberExpression') {
return false
}
const { object } = node
if (object.type === 'ChainExpression') {
// `(a?.b).c`
return true
}
if (object.type === 'Literal' && object.value === null && !object.bigint) {
// `(null).foo`
return true
}
if (object.type === 'MemberExpression') {
return maybeNullObjectMemberExpression(object)
}
return false
}
// ------------------------------------------------------------------------------

@@ -62,4 +104,8 @@ // Rule Definition

"'.sync' modifiers aren't supported on <{{name}}> non Vue-components.",
unexpectedOptionalChaining:
"Optional chaining cannot appear in 'v-bind' with '.sync' modifiers.",
unexpectedNonLhsExpression:
"'.sync' modifiers require the attribute value which is valid as LHS.",
unexpectedNullObject:
"'.sync' modifier has potential null object property access.",
unexpectedUpdateIterationVariable:

@@ -89,12 +135,29 @@ "'.sync' modifiers cannot update the iteration variable '{{varName}}' itself."

if (!node.value || !node.value.expression) {
if (!node.value) {
return
}
if (!isLhs(node.value.expression)) {
const expression = node.value.expression
if (!expression) {
// Parsing error
return
}
if (isOptionalChainingMemberExpression(expression)) {
context.report({
node,
loc: node.loc,
messageId: 'unexpectedOptionalChaining'
})
} else if (!isLhs(expression)) {
context.report({
node,
loc: node.loc,
messageId: 'unexpectedNonLhsExpression'
})
} else if (maybeNullObjectMemberExpression(expression)) {
context.report({
node,
loc: node.loc,
messageId: 'unexpectedNullObject'
})
}

@@ -101,0 +164,0 @@

@@ -40,4 +40,19 @@ /**

/**
* Check whether the given node can be LHS.
* Check whether the given node is a MemberExpression containing an optional chaining.
* e.g.
* - `a?.b`
* - `a?.b.c`
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is a MemberExpression containing an optional chaining.
*/
function isOptionalChainingMemberExpression(node) {
return (
node.type === 'ChainExpression' &&
node.expression.type === 'MemberExpression'
)
}
/**
* Check whether the given node can be LHS (left-hand side).
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node can be LHS.

@@ -50,2 +65,29 @@ */

/**
* Check whether the given node is a MemberExpression of a possibly null object.
* e.g.
* - `(a?.b).c`
* - `(null).foo`
* @param {ASTNode} node The node to check.
* @returns {boolean} `true` if the node is a MemberExpression of a possibly null object.
*/
function maybeNullObjectMemberExpression(node) {
if (node.type !== 'MemberExpression') {
return false
}
const { object } = node
if (object.type === 'ChainExpression') {
// `(a?.b).c`
return true
}
if (object.type === 'Literal' && object.value === null && !object.bigint) {
// `(null).foo`
return true
}
if (object.type === 'MemberExpression') {
return maybeNullObjectMemberExpression(object)
}
return false
}
/**
* Get the variable by names.

@@ -81,2 +123,3 @@ * @param {string} name The variable name to find.

/** @type {RuleModule} */
module.exports = {

@@ -91,3 +134,21 @@ meta: {

fixable: null,
schema: []
schema: [],
messages: {
unexpectedInvalidElement:
"'v-model' directives aren't supported on <{{name}}> elements.",
unexpectedInputFile:
"'v-model' directives don't support 'file' input type.",
unexpectedArgument: "'v-model' directives require no argument.",
unexpectedModifier:
"'v-model' directives don't support the modifier '{{name}}'.",
missingValue: "'v-model' directives require that attribute value.",
unexpectedOptionalChaining:
"Optional chaining cannot appear in 'v-model' directives.",
unexpectedNonLhsExpression:
"'v-model' directives require the attribute value which is valid as LHS.",
unexpectedNullObject:
"'v-model' directive has potential null object property access.",
unexpectedUpdateIterationVariable:
"'v-model' directives cannot update the iteration variable '{{varName}}' itself."
}
},

@@ -106,4 +167,3 @@ /** @param {RuleContext} context */

loc: node.loc,
message:
"'v-model' directives aren't supported on <{{name}}> elements.",
messageId: 'unexpectedInvalidElement',
data: { name }

@@ -117,3 +177,3 @@ })

loc: node.loc,
message: "'v-model' directives don't support 'file' input type."
messageId: 'unexpectedInputFile'
})

@@ -127,3 +187,3 @@ }

loc: node.loc,
message: "'v-model' directives require no argument."
messageId: 'unexpectedArgument'
})

@@ -137,4 +197,3 @@ }

loc: node.loc,
message:
"'v-model' directives don't support the modifier '{{name}}'.",
messageId: 'unexpectedModifier',
data: { name: modifier.name }

@@ -150,17 +209,29 @@ })

loc: node.loc,
message: "'v-model' directives require that attribute value."
messageId: 'missingValue'
})
return
}
if (!node.value.expression) {
const expression = node.value.expression
if (!expression) {
// Parsing error
return
}
if (!isLhs(node.value.expression)) {
if (isOptionalChainingMemberExpression(expression)) {
context.report({
node,
loc: node.loc,
message:
"'v-model' directives require the attribute value which is valid as LHS."
messageId: 'unexpectedOptionalChaining'
})
} else if (!isLhs(expression)) {
context.report({
node,
loc: node.loc,
messageId: 'unexpectedNonLhsExpression'
})
} else if (maybeNullObjectMemberExpression(expression)) {
context.report({
node,
loc: node.loc,
messageId: 'unexpectedNullObject'
})
}

@@ -179,4 +250,4 @@

loc: node.loc,
message:
"'v-model' directives cannot update the iteration variable '{{varName}}' itself.",
messageId: 'unexpectedUpdateIterationVariable',
data: { varName: id.name }

@@ -183,0 +254,0 @@ })

@@ -86,3 +86,3 @@ /**

* @typedef { { type: 'array', name: string, groupName: GroupName, node: Literal | TemplateLiteral } } ComponentArrayPropertyData
* @typedef { { type: 'object', name: string, groupName: GroupName, node: Identifier | Literal | TemplateLiteral } } ComponentObjectPropertyData
* @typedef { { type: 'object', name: string, groupName: GroupName, node: Identifier | Literal | TemplateLiteral, property: Property } } ComponentObjectPropertyData
* @typedef { ComponentArrayPropertyData | ComponentObjectPropertyData } ComponentPropertyData

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

},
/**

@@ -329,14 +328,2 @@ * Checks whether the given value is defined.

/**
* Check whether the given node is the root element or not.
* @param {VElement} node The element node to check.
* @returns {boolean} `true` if the node is the root element.
*/
isRootElement(node) {
return (
node.parent.type === 'VDocumentFragment' ||
node.parent.parent.type === 'VDocumentFragment'
)
},
/**
* Get the previous sibling element of the given element.

@@ -597,3 +584,4 @@ * @param {VElement} node The element node to get the previous sibling element.

this.hasAttribute(node, 'is') ||
this.hasDirective(node, 'bind', 'is')
this.hasDirective(node, 'bind', 'is') ||
this.hasDirective(node, 'is')
)

@@ -655,33 +643,3 @@ },

},
/**
* Parse member expression node to get array with all of its parts
* @param {ESNode} node MemberExpression
* @returns {string[]}
*/
parseMemberExpression(node) {
const members = []
if (node.type === 'MemberExpression') {
/** @type {Expression | Super} */
let memberExpression = node
while (memberExpression.type === 'MemberExpression') {
if (memberExpression.property.type === 'Identifier') {
members.push(memberExpression.property.name)
}
memberExpression = memberExpression.object
}
if (memberExpression.type === 'ThisExpression') {
members.push('this')
} else if (memberExpression.type === 'Identifier') {
members.push(memberExpression.name)
}
}
return members.reverse()
},
/**
* Gets the property name of a given node.

@@ -732,3 +690,3 @@ * @param {Property|AssignmentProperty|MethodDefinition|MemberExpression} node - The node to get.

propName,
value: unwrapTypes(prop.value),
value: skipTSAsExpression(prop.value),
node: prop

@@ -741,3 +699,3 @@ }

propName: null,
value: unwrapTypes(prop.value),
value: skipTSAsExpression(prop.value),
node: prop

@@ -805,3 +763,3 @@ }

emitName,
value: unwrapTypes(prop.value),
value: skipTSAsExpression(prop.value),
node: prop

@@ -814,3 +772,3 @@ }

emitName: null,
value: unwrapTypes(prop.value),
value: skipTSAsExpression(prop.value),
node: prop

@@ -874,3 +832,3 @@ }

/** @type {Expression} */
const propValue = unwrapTypes(cp.value)
const propValue = skipTSAsExpression(cp.value)
/** @type {BlockStatement | null} */

@@ -1069,3 +1027,3 @@ let value = null

if (callee.type === 'MemberExpression') {
const calleeObject = unwrapTypes(callee.object)
const calleeObject = skipTSAsExpression(callee.object)

@@ -1139,3 +1097,3 @@ if (

/** @type {Set<Property> | undefined} */
let usedGetter = new Set()
let usedGetter
for (const item of node.properties) {

@@ -1175,3 +1133,9 @@ if (item.type === 'Property') {

}
yield { type: 'object', name, groupName, node: key }
yield {
type: 'object',
name,
groupName,
node: key,
property: item
}
}

@@ -1329,7 +1293,7 @@ }

const nodes = []
let n = node
let n = skipChainExpression(node)
while (n.type === 'MemberExpression') {
nodes.push(n)
n = n.object
n = skipChainExpression(n.object)
}

@@ -1340,39 +1304,2 @@

/**
* Parse CallExpression or MemberExpression to get simplified version without arguments
*
* @param {ESNode} node The node to parse (MemberExpression | CallExpression)
* @return {String} eg. 'this.asd.qwe().map().filter().test.reduce()'
*/
parseMemberOrCallExpression(node) {
const parsedCallee = []
let n = node
let isFunc
while (n.type === 'MemberExpression' || n.type === 'CallExpression') {
if (n.type === 'CallExpression') {
n = n.callee
isFunc = true
} else {
if (n.computed) {
parsedCallee.push(`[]${isFunc ? '()' : ''}`)
} else if (n.property.type === 'Identifier') {
parsedCallee.push(n.property.name + (isFunc ? '()' : ''))
}
isFunc = false
n = n.object
}
}
if (n.type === 'Identifier') {
parsedCallee.push(n.name)
}
if (n.type === 'ThisExpression') {
parsedCallee.push('this')
}
return parsedCallee.reverse().join('.').replace(/\.\[/g, '[')
},
/**
* return two string editdistance

@@ -1436,14 +1363,13 @@ * @param {string} a string a to compare

/**
* Unwrap typescript types like "X as F"
* @template T
* @param {T} node
* @return {T}
* Retrieve `TSAsExpression#expression` value if the given node a `TSAsExpression` node. Otherwise, pass through it.
*/
unwrapTypes,
skipTSAsExpression,
/**
* Unwrap AssignmentPattern like "(a = 1) => ret"
* @param { AssignmentPattern | RestElement | ArrayPattern | ObjectPattern | Identifier } node
* @return { RestElement | ArrayPattern | ObjectPattern | Identifier}
* Retrieve `AssignmentPattern#left` value if the given node a `AssignmentPattern` node. Otherwise, pass through it.
*/
unwrapAssignmentPattern,
skipDefaultParamValue,
/**
* Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
*/
skipChainExpression,

@@ -1498,2 +1424,3 @@ /**

const pathNodes = []
/** @type {MemberExpression | Identifier | ChainExpression} */
let node = props

@@ -1519,6 +1446,5 @@ let target = node.parent

} else if (target.type === 'CallExpression') {
if (node !== props && target.callee === node) {
const callName = getStaticPropertyName(
/** @type {MemberExpression} */ (node)
)
if (pathNodes.length > 0 && target.callee === node) {
const mem = pathNodes[pathNodes.length - 1]
const callName = getStaticPropertyName(mem)
if (

@@ -1546,2 +1472,6 @@ callName &&

}
} else if (target.type === 'ChainExpression') {
node = target
target = target.parent
continue // loop
}

@@ -1682,5 +1612,3 @@

(prop) =>
prop.type === 'Property' &&
getStaticPropertyName(prop) === name &&
filter(prop)
isProperty(prop) && getStaticPropertyName(prop) === name && filter(prop)
: /**

@@ -1690,3 +1618,3 @@ * @param {Property | SpreadElement} prop

*/
(prop) => prop.type === 'Property' && getStaticPropertyName(prop) === name
(prop) => isProperty(prop) && getStaticPropertyName(prop) === name
return node.properties.find(predicate) || null

@@ -1709,3 +1637,3 @@ }

(prop) =>
prop.type === 'Property' &&
isAssignmentProperty(prop) &&
getStaticPropertyName(prop) === name &&

@@ -1717,3 +1645,4 @@ filter(prop)

*/
(prop) => prop.type === 'Property' && getStaticPropertyName(prop) === name
(prop) =>
isAssignmentProperty(prop) && getStaticPropertyName(prop) === name
return node.properties.find(predicate) || null

@@ -1748,8 +1677,8 @@ }

/**
* Unwrap typescript types like "X as F"
* @template T
* @param {T} node
* @return {T}
* Retrieve `TSAsExpression#expression` value if the given node a `TSAsExpression` node. Otherwise, pass through it.
* @template T Node type
* @param {T | TSAsExpression} node The node to address.
* @returns {T} The `TSAsExpression#expression` value if the node is a `TSAsExpression` node. Otherwise, the node.
*/
function unwrapTypes(node) {
function skipTSAsExpression(node) {
if (!node) {

@@ -1761,4 +1690,5 @@ return node

// @ts-expect-error
return unwrapTypes(node.expression)
return skipTSAsExpression(node.expression)
}
// @ts-expect-error
return node

@@ -1794,14 +1724,17 @@ }

/**
* Unwrap AssignmentPattern like "(a = 1) => ret"
* @param { AssignmentPattern | RestElement | ArrayPattern | ObjectPattern | Identifier } node
* @return { RestElement | ArrayPattern | ObjectPattern | Identifier}
* Retrieve `AssignmentPattern#left` value if the given node a `AssignmentPattern` node. Otherwise, pass through it.
* @template T Node type
* @param {T | AssignmentPattern} node The node to address.
* @return {T} The `AssignmentPattern#left` value if the node is a `AssignmentPattern` node. Otherwise, the node.
*/
function unwrapAssignmentPattern(node) {
function skipDefaultParamValue(node) {
if (!node) {
return node
}
// @ts-expect-error
if (node.type === 'AssignmentPattern') {
// @ts-expect-error
return unwrapAssignmentPattern(node.left)
return skipDefaultParamValue(node.left)
}
// @ts-expect-error
return node

@@ -1811,2 +1744,21 @@ }

/**
* Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
* @template T Node type
* @param {T | ChainExpression} node The node to address.
* @returns {T} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node.
*/
function skipChainExpression(node) {
if (!node) {
return node
}
// @ts-expect-error
if (node.type === 'ChainExpression') {
// @ts-expect-error
return skipChainExpression(node.expression)
}
// @ts-expect-error
return node
}
/**
* Gets the property name of a given node.

@@ -1909,3 +1861,3 @@ * @param {Property|AssignmentProperty|MethodDefinition|MemberExpression} node - The node to get.

if (callee.type === 'MemberExpression') {
const calleeObject = unwrapTypes(callee.object)
const calleeObject = skipTSAsExpression(callee.object)

@@ -1964,3 +1916,4 @@ if (calleeObject.type === 'Identifier') {

node.arguments.length > 0 &&
unwrapTypes(node.arguments.slice(-1)[0]).type === 'ObjectExpression'
skipTSAsExpression(node.arguments.slice(-1)[0]).type ===
'ObjectExpression'
)

@@ -1983,3 +1936,3 @@ }

node.arguments.length &&
unwrapTypes(node.arguments[0]).type === 'ObjectExpression'
skipTSAsExpression(node.arguments[0]).type === 'ObjectExpression'
)

@@ -2004,3 +1957,3 @@ }

isVueComponentFile(parent, filePath) &&
unwrapTypes(parent.declaration) === node
skipTSAsExpression(parent.declaration) === node
) {

@@ -2013,3 +1966,3 @@ return 'export'

isVueComponent(parent) &&
unwrapTypes(parent.arguments.slice(-1)[0]) === node
skipTSAsExpression(parent.arguments.slice(-1)[0]) === node
) {

@@ -2020,3 +1973,6 @@ return 'definition'

// new Vue({})
if (isVueInstance(parent) && unwrapTypes(parent.arguments[0]) === node) {
if (
isVueInstance(parent) &&
skipTSAsExpression(parent.arguments[0]) === node
) {
return 'instance'

@@ -2023,0 +1979,0 @@ }

{
"name": "eslint-plugin-vue",
"version": "7.0.0-alpha.10",
"version": "7.0.0-beta.0",
"description": "Official ESLint plugin for Vue.js",

@@ -53,6 +53,6 @@ "main": "lib/index.js",

"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0"
"eslint": "^6.2.0 || ^7.0.0"
},
"dependencies": {
"eslint-utils": "^2.0.0",
"eslint-utils": "^2.1.0",
"natural-compare": "^1.4.0",

@@ -77,3 +77,2 @@ "semver": "^7.3.2",

"eslint-plugin-vue": "file:.",
"eslint-plugin-vue-libs": "^4.0.0",
"eslint4b": "^7.0.0",

@@ -80,0 +79,0 @@ "lodash": "^4.17.15",

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