diff --git a/lib/rules/no-async-in-computed-properties.js b/lib/rules/no-async-in-computed-properties.js index e3080d7d5..da11e0426 100644 --- a/lib/rules/no-async-in-computed-properties.js +++ b/lib/rules/no-async-in-computed-properties.js @@ -80,13 +80,13 @@ module.exports = { create(context) { /** * @typedef {object} ScopeStack - * @property {ScopeStack} upper + * @property {ScopeStack | null} upper * @property {BlockStatement | Expression} body */ /** @type {Map} */ const computedPropertiesMap = new Map() - /** @type {ScopeStack} */ - let scopeStack + /** @type {ScopeStack | null} */ + let scopeStack = null const expressionTypes = { promise: 'asynchronous action', @@ -105,11 +105,14 @@ module.exports = { verify(node, node.body, 'async', computedPropertiesMap.get(vueNode)) } - scopeStack = { upper: scopeStack, body: node.body } + scopeStack = { + upper: scopeStack, + body: node.body + } } function onFunctionExit() { - scopeStack = scopeStack.upper + scopeStack = scopeStack && scopeStack.upper } /** @@ -146,6 +149,9 @@ module.exports = { ':function:exit': onFunctionExit, NewExpression(node, { node: vueNode }) { + if (!scopeStack) { + return + } if ( node.callee.type === 'Identifier' && node.callee.name === 'Promise' @@ -160,6 +166,9 @@ module.exports = { }, CallExpression(node, { node: vueNode }) { + if (!scopeStack) { + return + } if (isPromise(node)) { verify( node, @@ -178,6 +187,9 @@ module.exports = { }, AwaitExpression(node, { node: vueNode }) { + if (!scopeStack) { + return + } verify( node, scopeStack.body, diff --git a/lib/rules/no-bare-strings-in-template.js b/lib/rules/no-bare-strings-in-template.js index 29bc17dbb..e8dc5bfcd 100644 --- a/lib/rules/no-bare-strings-in-template.js +++ b/lib/rules/no-bare-strings-in-template.js @@ -160,7 +160,7 @@ module.exports = { /** @param {RuleContext} context */ create(context) { /** - * @typedef { { upper: ElementStack, name: string, attrs: Set } } ElementStack + * @typedef { { upper: ElementStack | null, name: string, attrs: Set } } ElementStack */ const opts = context.options[0] || {} /** @type {string[]} */ @@ -173,8 +173,8 @@ module.exports = { 'gu' ) - /** @type {ElementStack} */ - let elementStack + /** @type {ElementStack | null} */ + let elementStack = null /** * Gets the bare string from given string * @param {string} str @@ -231,11 +231,11 @@ module.exports = { } }, 'VElement:exit'() { - elementStack = elementStack.upper + elementStack = elementStack && elementStack.upper }, /** @param {VAttribute|VDirective} node */ VAttribute(node) { - if (!node.value) { + if (!node.value || !elementStack) { return } if (node.directive === false) { diff --git a/lib/rules/no-lifecycle-after-await.js b/lib/rules/no-lifecycle-after-await.js index 2b5304c9b..7688d5b70 100644 --- a/lib/rules/no-lifecycle-after-await.js +++ b/lib/rules/no-lifecycle-after-await.js @@ -47,7 +47,7 @@ module.exports = { */ /** * @typedef {object} ScopeStack - * @property {ScopeStack} upper + * @property {ScopeStack | null} upper * @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} functionNode */ /** @type {Set} */ @@ -55,8 +55,8 @@ module.exports = { /** @type {Map} */ const setupFunctions = new Map() - /** @type {ScopeStack} */ - let scopeStack + /** @type {ScopeStack | null} */ + let scopeStack = null return Object.assign( { @@ -81,7 +81,10 @@ module.exports = { }, utils.defineVueVisitor(context, { ':function'(node) { - scopeStack = { upper: scopeStack, functionNode: node } + scopeStack = { + upper: scopeStack, + functionNode: node + } }, onSetupFunctionEnter(node) { setupFunctions.set(node, { @@ -90,18 +93,20 @@ module.exports = { }) }, AwaitExpression() { - const setupFunctionData = setupFunctions.get( - scopeStack && scopeStack.functionNode - ) + if (!scopeStack) { + return + } + const setupFunctionData = setupFunctions.get(scopeStack.functionNode) if (!setupFunctionData) { return } setupFunctionData.afterAwait = true }, CallExpression(node) { - const setupFunctionData = setupFunctions.get( - scopeStack && scopeStack.functionNode - ) + if (!scopeStack) { + return + } + const setupFunctionData = setupFunctions.get(scopeStack.functionNode) if (!setupFunctionData || !setupFunctionData.afterAwait) { return } @@ -118,7 +123,7 @@ module.exports = { } }, ':function:exit'(node) { - scopeStack = scopeStack.upper + scopeStack = scopeStack && scopeStack.upper setupFunctions.delete(node) } diff --git a/lib/rules/no-setup-props-destructure.js b/lib/rules/no-setup-props-destructure.js index 43b1856ee..3c6ce4446 100644 --- a/lib/rules/no-setup-props-destructure.js +++ b/lib/rules/no-setup-props-destructure.js @@ -68,17 +68,20 @@ module.exports = { } /** * @typedef {object} ScopeStack - * @property {ScopeStack} upper + * @property {ScopeStack | null} upper * @property {FunctionDeclaration | FunctionExpression | ArrowFunctionExpression} functionNode */ /** - * @type {ScopeStack} + * @type {ScopeStack | null} */ - let scopeStack + let scopeStack = null return utils.defineVueVisitor(context, { ':function'(node) { - scopeStack = { upper: scopeStack, functionNode: node } + scopeStack = { + upper: scopeStack, + functionNode: node + } }, onSetupFunctionEnter(node) { const propsParam = utils.unwrapAssignmentPattern(node.params[0]) @@ -113,6 +116,9 @@ module.exports = { setupScopePropsReferenceIds.set(node, propsReferenceIds) }, VariableDeclarator(node) { + if (!scopeStack) { + return + } const propsReferenceIds = setupScopePropsReferenceIds.get( scopeStack.functionNode ) @@ -122,6 +128,9 @@ module.exports = { verify(node.id, node.init, propsReferenceIds) }, AssignmentExpression(node) { + if (!scopeStack) { + return + } const propsReferenceIds = setupScopePropsReferenceIds.get( scopeStack.functionNode ) @@ -131,7 +140,7 @@ module.exports = { verify(node.left, node.right, propsReferenceIds) }, ':function:exit'(node) { - scopeStack = scopeStack.upper + scopeStack = scopeStack && scopeStack.upper setupScopePropsReferenceIds.delete(node) } diff --git a/lib/rules/no-side-effects-in-computed-properties.js b/lib/rules/no-side-effects-in-computed-properties.js index 761b1ac61..4d5c79e49 100644 --- a/lib/rules/no-side-effects-in-computed-properties.js +++ b/lib/rules/no-side-effects-in-computed-properties.js @@ -32,16 +32,26 @@ module.exports = { /** @type {Map} */ const computedPropertiesMap = new Map() - /** @type { { upper: any, body: null | BlockStatement | Expression } } */ - let scopeStack = { upper: null, body: null } + /** + * @typedef {object} ScopeStack + * @property {ScopeStack | null} upper + * @property {BlockStatement | Expression | null} body + */ + /** + * @type {ScopeStack | null} + */ + let scopeStack = null /** @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} node */ function onFunctionEnter(node) { - scopeStack = { upper: scopeStack, body: node.body } + scopeStack = { + upper: scopeStack, + body: node.body + } } function onFunctionExit() { - scopeStack = scopeStack.upper + scopeStack = scopeStack && scopeStack.upper } return utils.defineVueVisitor(context, { @@ -59,6 +69,9 @@ module.exports = { node, { node: vueNode } ) { + if (!scopeStack) { + return + } const targetBody = scopeStack.body const computedProperty = /** @type {ComponentComputedProperty[]} */ (computedPropertiesMap.get( vueNode diff --git a/lib/rules/no-template-shadow.js b/lib/rules/no-template-shadow.js index c228642f4..44f26cf57 100644 --- a/lib/rules/no-template-shadow.js +++ b/lib/rules/no-template-shadow.js @@ -40,11 +40,11 @@ module.exports = { /** * @typedef {object} ScopeStack - * @property {ScopeStack} parent + * @property {ScopeStack | null} parent * @property {Identifier[]} nodes */ - /** @type {ScopeStack} */ - let scope + /** @type {ScopeStack | null} */ + let scopeStack = null // ---------------------------------------------------------------------- // Public @@ -55,10 +55,10 @@ module.exports = { { /** @param {VElement} node */ VElement(node) { - scope = { - parent: scope, - nodes: scope - ? scope.nodes.slice() // make copy + scopeStack = { + parent: scopeStack, + nodes: scopeStack + ? scopeStack.nodes.slice() // make copy : [] } if (node.variables) { @@ -66,7 +66,7 @@ module.exports = { const varNode = variable.id const name = varNode.name if ( - scope.nodes.some((node) => node.name === name) || + scopeStack.nodes.some((node) => node.name === name) || jsVars.has(name) ) { context.report({ @@ -79,13 +79,13 @@ module.exports = { } }) } else { - scope.nodes.push(varNode) + scopeStack.nodes.push(varNode) } } } }, 'VElement:exit'() { - scope = scope.parent + scopeStack = scopeStack && scopeStack.parent } }, utils.executeOnVue(context, (obj) => { diff --git a/lib/rules/no-watch-after-await.js b/lib/rules/no-watch-after-await.js index d9a8c3822..fc46dcd6f 100644 --- a/lib/rules/no-watch-after-await.js +++ b/lib/rules/no-watch-after-await.js @@ -58,11 +58,11 @@ module.exports = { /** * @typedef {object} ScopeStack - * @property {ScopeStack} upper + * @property {ScopeStack | null} upper * @property {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} functionNode */ - /** @type {ScopeStack} */ - let scopeStack + /** @type {ScopeStack | null} */ + let scopeStack = null return Object.assign( { @@ -88,7 +88,10 @@ module.exports = { utils.defineVueVisitor(context, { /** @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} node */ ':function'(node) { - scopeStack = { upper: scopeStack, functionNode: node } + scopeStack = { + upper: scopeStack, + functionNode: node + } }, onSetupFunctionEnter(node) { setupFunctions.set(node, { @@ -97,6 +100,9 @@ module.exports = { }) }, AwaitExpression() { + if (!scopeStack) { + return + } const setupFunctionData = setupFunctions.get(scopeStack.functionNode) if (!setupFunctionData) { return @@ -105,6 +111,9 @@ module.exports = { }, /** @param {CallExpression} node */ CallExpression(node) { + if (!scopeStack) { + return + } const setupFunctionData = setupFunctions.get(scopeStack.functionNode) if (!setupFunctionData || !setupFunctionData.afterAwait) { return @@ -119,7 +128,7 @@ module.exports = { }, /** @param {FunctionExpression | ArrowFunctionExpression | FunctionDeclaration} node */ ':function:exit'(node) { - scopeStack = scopeStack.upper + scopeStack = scopeStack && scopeStack.upper setupFunctions.delete(node) } diff --git a/lib/rules/require-direct-export.js b/lib/rules/require-direct-export.js index 4464569ff..c3eaebd01 100644 --- a/lib/rules/require-direct-export.js +++ b/lib/rules/require-direct-export.js @@ -39,14 +39,14 @@ module.exports = { /** * @typedef {object} ScopeStack - * @property {ScopeStack} upper + * @property {ScopeStack | null} upper * @property {boolean} withinVue3FunctionalBody */ /** @type { { body: BlockStatement, hasReturnArgument: boolean } } */ let maybeVue3Functional - /** @type {ScopeStack} */ - let scopeStack + /** @type {ScopeStack | null} */ + let scopeStack = null return { /** @param {Declaration | Expression} node */ diff --git a/lib/rules/require-valid-default-prop.js b/lib/rules/require-valid-default-prop.js index 5003d20c7..3779fa9ab 100644 --- a/lib/rules/require-valid-default-prop.js +++ b/lib/rules/require-valid-default-prop.js @@ -106,17 +106,29 @@ module.exports = { */ const vueObjectPropsContexts = new Map() - /** @type { { upper: any, body: null | BlockStatement | Expression, returnTypes?: null | ReturnType[] } } */ - let scopeStack = { upper: null, body: null, returnTypes: null } + /** + * @typedef {object} ScopeStack + * @property {ScopeStack | null} upper + * @property {BlockStatement | Expression} body + * @property {null | ReturnType[]} [returnTypes] + */ + /** + * @type {ScopeStack | null} + */ + let scopeStack = null /** * @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node */ function onFunctionEnter(node) { - scopeStack = { upper: scopeStack, body: node.body, returnTypes: null } + scopeStack = { + upper: scopeStack, + body: node.body, + returnTypes: null + } } function onFunctionExit() { - scopeStack = scopeStack.upper + scopeStack = scopeStack && scopeStack.upper } /** diff --git a/lib/rules/return-in-emits-validator.js b/lib/rules/return-in-emits-validator.js index 15493a8d0..c4ced9074 100644 --- a/lib/rules/return-in-emits-validator.js +++ b/lib/rules/return-in-emits-validator.js @@ -57,15 +57,15 @@ module.exports = { /** * @typedef {object} ScopeStack - * @property {ScopeStack} upper + * @property {ScopeStack | null} upper * @property {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} functionNode * @property {boolean} hasReturnValue * @property {boolean} possibleOfReturnTrue */ /** - * @type {ScopeStack} + * @type {ScopeStack | null} */ - let scopeStack + let scopeStack = null return Object.assign( {}, @@ -105,6 +105,9 @@ module.exports = { }, /** @param {ReturnStatement} node */ ReturnStatement(node) { + if (!scopeStack) { + return + } if (node.argument) { scopeStack.hasReturnValue = true @@ -115,7 +118,7 @@ module.exports = { }, /** @param {FunctionExpression | FunctionDeclaration | ArrowFunctionExpression} node */ ':function:exit'(node) { - if (!scopeStack.possibleOfReturnTrue) { + if (scopeStack && !scopeStack.possibleOfReturnTrue) { const emits = emitsValidators.find((e) => e.value === node) if (emits) { context.report({ @@ -130,7 +133,7 @@ module.exports = { } } - scopeStack = scopeStack.upper + scopeStack = scopeStack && scopeStack.upper } }) ) diff --git a/lib/rules/sort-keys.js b/lib/rules/sort-keys.js index 5e4fa3c10..664186bdd 100644 --- a/lib/rules/sort-keys.js +++ b/lib/rules/sort-keys.js @@ -154,7 +154,7 @@ module.exports = { /** * @typedef {object} ObjectStack - * @property {ObjectStack} ObjectStack.upper + * @property {ObjectStack | null} ObjectStack.upper * @property {string | null} ObjectStack.prevName * @property {number} ObjectStack.numKeys * @property {VueState} ObjectStack.vueState @@ -170,17 +170,17 @@ module.exports = { /** * The stack to save the previous property's name for each object literals. - * @type {ObjectStack} + * @type {ObjectStack | null} */ - let stack + let objectStack return { ObjectExpression(node) { /** @type {VueState} */ const vueState = {} - const upperVueState = (stack && stack.vueState) || {} - stack = { - upper: stack, + const upperVueState = (objectStack && objectStack.vueState) || {} + objectStack = { + upper: objectStack, prevName: null, numKeys: node.properties.length, vueState @@ -232,24 +232,30 @@ module.exports = { } }, 'ObjectExpression:exit'() { - stack = stack.upper + objectStack = objectStack && objectStack.upper }, SpreadElement(node) { + if (!objectStack) { + return + } if (node.parent.type === 'ObjectExpression') { - stack.prevName = null + objectStack.prevName = null } }, 'ObjectExpression > Property'(node) { - stack.vueState.currentProperty = node - if (stack.vueState.ignore) { + if (!objectStack) { + return + } + objectStack.vueState.currentProperty = node + if (objectStack.vueState.ignore) { return } - const prevName = stack.prevName - const numKeys = stack.numKeys + const prevName = objectStack.prevName + const numKeys = objectStack.numKeys const thisName = getPropertyName(node) if (thisName !== null) { - stack.prevName = thisName + objectStack.prevName = thisName } if (prevName === null || thisName === null || numKeys < minKeys) { diff --git a/lib/rules/this-in-template.js b/lib/rules/this-in-template.js index 10c89a81e..0efa03035 100644 --- a/lib/rules/this-in-template.js +++ b/lib/rules/this-in-template.js @@ -41,44 +41,47 @@ module.exports = { const options = context.options[0] !== 'always' ? 'never' : 'always' /** * @typedef {object} ScopeStack - * @property {ScopeStack} parent + * @property {ScopeStack | null} parent * @property {Identifier[]} nodes */ - /** @type {ScopeStack} */ - let scope + /** @type {ScopeStack | null} */ + let scopeStack = null return utils.defineTemplateBodyVisitor(context, { /** @param {VElement} node */ VElement(node) { - scope = { - parent: scope, - nodes: scope - ? scope.nodes.slice() // make copy + scopeStack = { + parent: scopeStack, + nodes: scopeStack + ? scopeStack.nodes.slice() // make copy : [] } if (node.variables) { for (const variable of node.variables) { const varNode = variable.id const name = varNode.name - if (!scope.nodes.some((node) => node.name === name)) { + if (!scopeStack.nodes.some((node) => node.name === name)) { // Prevent adding duplicates - scope.nodes.push(varNode) + scopeStack.nodes.push(varNode) } } } }, 'VElement:exit'() { - scope = scope.parent + scopeStack = scopeStack && scopeStack.parent }, ...(options === 'never' ? { /** @param { ThisExpression & { parent: MemberExpression } } node */ 'VExpressionContainer MemberExpression > ThisExpression'(node) { + if (!scopeStack) { + return + } const propertyName = utils.getStaticPropertyName(node.parent) if ( !propertyName || - scope.nodes.some((el) => el.name === propertyName) || + scopeStack.nodes.some((el) => el.name === propertyName) || RESERVED_NAMES.has(propertyName) || // this.class | this['class'] /^[0-9].*$|[^a-zA-Z0-9_]/.test(propertyName) // this['0aaaa'] | this['foo-bar bas'] ) { @@ -95,6 +98,9 @@ module.exports = { : { /** @param {VExpressionContainer} node */ VExpressionContainer(node) { + if (!scopeStack) { + return + } if (node.parent.type === 'VDirectiveKey') { // We cannot use `.` in dynamic arguments because the right of the `.` becomes a modifier. // For example, In `:[this.prop]` case, `:[this` is an argument and `prop]` is a modifier. @@ -103,7 +109,9 @@ module.exports = { if (node.references) { for (const reference of node.references) { if ( - !scope.nodes.some((el) => el.name === reference.id.name) + !scopeStack.nodes.some( + (el) => el.name === reference.id.name + ) ) { context.report({ node: reference.id, diff --git a/tests/lib/rules/no-async-in-computed-properties.js b/tests/lib/rules/no-async-in-computed-properties.js index b7f0283eb..de6769091 100644 --- a/tests/lib/rules/no-async-in-computed-properties.js +++ b/tests/lib/rules/no-async-in-computed-properties.js @@ -223,6 +223,14 @@ ruleTester.run('no-async-in-computed-properties', rule, { } `, parserOptions + }, + { + code: ` + Vue.component('test',{ + data1: new Promise(), + data2: Promise.resolve(), + })`, + parserOptions } ], diff --git a/tests/lib/rules/no-setup-props-destructure.js b/tests/lib/rules/no-setup-props-destructure.js index e81d680fc..c0e35df06 100644 --- a/tests/lib/rules/no-setup-props-destructure.js +++ b/tests/lib/rules/no-setup-props-destructure.js @@ -159,6 +159,12 @@ tester.run('no-setup-props-destructure', rule, { } ` + }, + { + code: ` + Vue.component('test', { + el: a = b + })` } ], invalid: [ diff --git a/tests/lib/rules/no-watch-after-await.js b/tests/lib/rules/no-watch-after-await.js index 9d91fbb2b..9bc7c04e6 100644 --- a/tests/lib/rules/no-watch-after-await.js +++ b/tests/lib/rules/no-watch-after-await.js @@ -91,6 +91,12 @@ tester.run('no-watch-after-await', rule, { } ` + }, + { + code: ` + Vue.component('test', { + el: foo() + })` } ], invalid: [