From 928e0c62f3370dd405e567eb8f5763b1678dfbb7 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Tue, 19 Oct 2021 08:30:45 +0900 Subject: [PATCH] Fix false positives for vars inside type in vue/valid-define-emits and vue/valid-define-props rules (#1658) --- lib/rules/valid-define-emits.js | 3 ++ lib/rules/valid-define-props.js | 3 ++ lib/utils/indent-ts.js | 40 ++--------------------- lib/utils/index.js | 24 +++++++++++++- lib/utils/ts-ast-utils.js | 46 +++++++++++++++++++++++++++ tests/lib/rules/valid-define-emits.js | 44 +++++++++++++++++++++++++ tests/lib/rules/valid-define-props.js | 44 +++++++++++++++++++++++++ 7 files changed, 165 insertions(+), 39 deletions(-) diff --git a/lib/rules/valid-define-emits.js b/lib/rules/valid-define-emits.js index 1fabbc025..b16fc300e 100644 --- a/lib/rules/valid-define-emits.js +++ b/lib/rules/valid-define-emits.js @@ -85,6 +85,9 @@ module.exports = { !utils.inRange(defineEmits.range, def.name) ) ) { + if (utils.withinTypeNode(node)) { + continue + } //`defineEmits` are referencing locally declared variables. context.report({ node, diff --git a/lib/rules/valid-define-props.js b/lib/rules/valid-define-props.js index b67af7c8c..273eae70a 100644 --- a/lib/rules/valid-define-props.js +++ b/lib/rules/valid-define-props.js @@ -86,6 +86,9 @@ module.exports = { !utils.inRange(defineProps.range, def.name) ) ) { + if (utils.withinTypeNode(node)) { + continue + } //`defineProps` are referencing locally declared variables. context.report({ node, diff --git a/lib/utils/indent-ts.js b/lib/utils/indent-ts.js index ed2080750..b98e9d105 100644 --- a/lib/utils/indent-ts.js +++ b/lib/utils/indent-ts.js @@ -12,6 +12,7 @@ const { isClosingBracketToken, isOpeningBracketToken } = require('eslint-utils') +const { isTypeNode } = require('./ts-ast-utils') /** * @typedef {import('../../typings/eslint-plugin-vue/util-types/indent-helper').TSNodeListener} TSNodeListener @@ -224,46 +225,9 @@ function defineVisitor({ */ // eslint-disable-next-line complexity -- ignore '*[type=/^TS/]'(node) { - if ( - node.type !== 'TSAnyKeyword' && - node.type !== 'TSArrayType' && - node.type !== 'TSBigIntKeyword' && - node.type !== 'TSBooleanKeyword' && - node.type !== 'TSConditionalType' && - node.type !== 'TSConstructorType' && - node.type !== 'TSFunctionType' && - node.type !== 'TSImportType' && - node.type !== 'TSIndexedAccessType' && - node.type !== 'TSInferType' && - node.type !== 'TSIntersectionType' && - node.type !== 'TSIntrinsicKeyword' && - node.type !== 'TSLiteralType' && - node.type !== 'TSMappedType' && - node.type !== 'TSNamedTupleMember' && - node.type !== 'TSNeverKeyword' && - node.type !== 'TSNullKeyword' && - node.type !== 'TSNumberKeyword' && - node.type !== 'TSObjectKeyword' && - node.type !== 'TSOptionalType' && - node.type !== 'TSRestType' && - node.type !== 'TSStringKeyword' && - node.type !== 'TSSymbolKeyword' && - node.type !== 'TSTemplateLiteralType' && - node.type !== 'TSThisType' && - node.type !== 'TSTupleType' && - node.type !== 'TSTypeLiteral' && - node.type !== 'TSTypeOperator' && - node.type !== 'TSTypePredicate' && - node.type !== 'TSTypeQuery' && - node.type !== 'TSTypeReference' && - node.type !== 'TSUndefinedKeyword' && - node.type !== 'TSUnionType' && - node.type !== 'TSUnknownKeyword' && - node.type !== 'TSVoidKeyword' - ) { + if (!isTypeNode(node)) { return } - /** @type {TypeNode} */ const typeNode = node if (/** @type {any} */ (typeNode.parent).type === 'TSParenthesizedType') { return diff --git a/lib/utils/index.js b/lib/utils/index.js index 36614cd14..5c49d15f2 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -54,7 +54,8 @@ const { traverseNodes, getFallbackKeys } = vueEslintParser.AST const { findVariable } = require('eslint-utils') const { getComponentPropsFromTypeDefine, - getComponentEmitsFromTypeDefine + getComponentEmitsFromTypeDefine, + isTypeNode } = require('./ts-ast-utils') /** @@ -1717,6 +1718,10 @@ module.exports = { * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it. */ skipChainExpression, + /** + * Checks whether the given node is in a type annotation. + */ + withinTypeNode, findVariableByIdentifier, getScope, /** @@ -2226,6 +2231,23 @@ function skipChainExpression(node) { return node } +/** + * Checks whether the given node is in a type annotation. + * @param {ESNode} node + * @returns {boolean} + */ +function withinTypeNode(node) { + /** @type {ASTNode | null} */ + let target = node + while (target) { + if (isTypeNode(target)) { + return true + } + target = target.parent + } + return false +} + /** * Gets the property name of a given node. * @param {Property|AssignmentProperty|MethodDefinition|MemberExpression} node - The node to get. diff --git a/lib/utils/ts-ast-utils.js b/lib/utils/ts-ast-utils.js index b801b6374..6777859b8 100644 --- a/lib/utils/ts-ast-utils.js +++ b/lib/utils/ts-ast-utils.js @@ -4,6 +4,7 @@ const { findVariable } = require('eslint-utils') * @typedef {import('@typescript-eslint/types').TSESTree.TSInterfaceBody} TSInterfaceBody * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} TSTypeLiteral * @typedef {import('@typescript-eslint/types').TSESTree.Parameter} TSESTreeParameter + * @typedef {import('@typescript-eslint/types').TSESTree.Node} Node * */ /** @@ -12,10 +13,55 @@ const { findVariable } = require('eslint-utils') */ module.exports = { + isTypeNode, getComponentPropsFromTypeDefine, getComponentEmitsFromTypeDefine } +/** + * @param {Node | ASTNode} node + * @returns {node is TypeNode} + */ +function isTypeNode(node) { + return ( + node.type === 'TSAnyKeyword' || + node.type === 'TSArrayType' || + node.type === 'TSBigIntKeyword' || + node.type === 'TSBooleanKeyword' || + node.type === 'TSConditionalType' || + node.type === 'TSConstructorType' || + node.type === 'TSFunctionType' || + node.type === 'TSImportType' || + node.type === 'TSIndexedAccessType' || + node.type === 'TSInferType' || + node.type === 'TSIntersectionType' || + node.type === 'TSIntrinsicKeyword' || + node.type === 'TSLiteralType' || + node.type === 'TSMappedType' || + node.type === 'TSNamedTupleMember' || + node.type === 'TSNeverKeyword' || + node.type === 'TSNullKeyword' || + node.type === 'TSNumberKeyword' || + node.type === 'TSObjectKeyword' || + node.type === 'TSOptionalType' || + node.type === 'TSRestType' || + node.type === 'TSStringKeyword' || + node.type === 'TSSymbolKeyword' || + node.type === 'TSTemplateLiteralType' || + node.type === 'TSThisType' || + node.type === 'TSTupleType' || + node.type === 'TSTypeLiteral' || + node.type === 'TSTypeOperator' || + node.type === 'TSTypePredicate' || + node.type === 'TSTypeQuery' || + node.type === 'TSTypeReference' || + node.type === 'TSUndefinedKeyword' || + node.type === 'TSUnionType' || + node.type === 'TSUnknownKeyword' || + node.type === 'TSVoidKeyword' + ) +} + /** * @param {TypeNode} node * @returns {node is TSTypeLiteral} diff --git a/tests/lib/rules/valid-define-emits.js b/tests/lib/rules/valid-define-emits.js index 542805848..557101d67 100644 --- a/tests/lib/rules/valid-define-emits.js +++ b/tests/lib/rules/valid-define-emits.js @@ -73,6 +73,50 @@ tester.run('valid-define-emits', rule, { }) ` + }, + { + // https://github.com/vuejs/eslint-plugin-vue/issues/1656 + filename: 'test.vue', + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + code: ` + + ` + }, + { + filename: 'test.vue', + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + code: ` + + ` } ], invalid: [ diff --git a/tests/lib/rules/valid-define-props.js b/tests/lib/rules/valid-define-props.js index cb1f50daf..10894e069 100644 --- a/tests/lib/rules/valid-define-props.js +++ b/tests/lib/rules/valid-define-props.js @@ -76,6 +76,50 @@ tester.run('valid-define-props', rule, { }) ` + }, + { + // https://github.com/vuejs/eslint-plugin-vue/issues/1656 + filename: 'test.vue', + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + code: ` + + ` + }, + { + filename: 'test.vue', + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + }, + code: ` + + ` } ], invalid: [