From f89a57f1a6eab8e7c4e7372e781b775597bf13ad Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Wed, 8 Sep 2021 11:45:19 +0900 Subject: [PATCH] Fix false positives for non component template in `no-raw-text` rule. --- lib/rules/no-raw-text.ts | 12 +- lib/utils/index.ts | 262 +++++++++++++++++++++++++++++++++ tests/lib/rules/no-raw-text.ts | 105 +++++++++++++ 3 files changed, 377 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-raw-text.ts b/lib/rules/no-raw-text.ts index 3ade123a..4bdc4c47 100644 --- a/lib/rules/no-raw-text.ts +++ b/lib/rules/no-raw-text.ts @@ -2,7 +2,7 @@ * @author kazuya kawaguchi (a.k.a. kazupon) */ import { parse, AST as VAST } from 'vue-eslint-parser' -import { defineTemplateBodyVisitor } from '../utils/index' +import { defineTemplateBodyVisitor, getVueObjectType } from '../utils/index' import type { JSXText, RuleContext, @@ -21,8 +21,10 @@ const config: { } = { ignorePattern: /^[^\S\s]$/, ignoreNodes: [], ignoreText: [] } const hasOnlyWhitespace = (value: string) => /^[\r\n\s\t\f\v]+$/.test(value) const hasTemplateElementValue = ( - value: any // eslint-disable-line @typescript-eslint/no-explicit-any + value: AnyValue ): value is { raw: string; cooked: string } => + value != null && + typeof value === 'object' && 'raw' in value && typeof value.raw === 'string' && 'cooked' in value && @@ -294,6 +296,12 @@ function create(context: RuleContext): RuleListener { if (!valueNode) { return } + if ( + getVueObjectType(context, node) == null || + valueNode.value == null + ) { + return + } const templateNode = getComponentTemplateNode(valueNode.value) VAST.traverseNodes(templateNode, { diff --git a/lib/utils/index.ts b/lib/utils/index.ts index fab936a0..eb53c979 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -339,6 +339,156 @@ export function defineCustomBlocksVisitor( return compositingVisitors(jsonVisitor, yamlVisitor) } +export type VueObjectType = + | 'mark' + | 'export' + | 'definition' + | 'instance' + | 'variable' + | 'components-option' +/** + * If the given object is a Vue component or instance, returns the Vue definition type. + * @param context The ESLint rule context object. + * @param node Node to check + * @returns The Vue definition type. + */ +export function getVueObjectType( + context: RuleContext, + node: VAST.ESLintObjectExpression +): VueObjectType | null { + if (node.type !== 'ObjectExpression' || !node.parent) { + return null + } + const parent = node.parent + if (parent.type === 'ExportDefaultDeclaration') { + // export default {} in .vue || .jsx + const ext = extname(context.getFilename()).toLowerCase() + if ( + (ext === '.vue' || ext === '.jsx' || !ext) && + skipTSAsExpression(parent.declaration) === node + ) { + const scriptSetup = getScriptSetupElement(context) + if ( + scriptSetup && + scriptSetup.range[0] <= parent.range[0] && + parent.range[1] <= scriptSetup.range[1] + ) { + // `export default` in ` + `, + ` + + `, + ` + + `, + ` + + `, + ` + const CONST = { + template: 'maybe const value' + } + const varValue = { + template: 'maybe normal variable' + } + ` ], invalid: [ @@ -498,6 +552,57 @@ tester.run('no-raw-text', rule as never, { line: 4 } ] + }, + { + code: ` + Vue.component('xxx', { + template: 'Vue.component' + }) + component('xxx', { + template: 'component' + }) + app.component('xxx', { + template: 'app.component' + }) + Vue.extend({ + template: 'Vue.extend' + }) + defineComponent({ + template: 'defineComponent' + }) + new Vue({ + template: 'new Vue' + }) + createApp({ + template: 'createApp' + }) + const MyButton = { + template: 'MyComponent' + } + const foo = { + components: { + Foo: { + template: 'components option' + } + } + } + // @vue/component + const bar = { + template: 'mark' + } + `, + errors: [ + "raw text 'Vue.component' is used", + "raw text 'component' is used", + "raw text 'app.component' is used", + "raw text 'Vue.extend' is used", + "raw text 'defineComponent' is used", + "raw text 'new Vue' is used", + "raw text 'createApp' is used", + "raw text 'MyComponent' is used", + "raw text 'components option' is used", + "raw text 'mark' is used" + ] } ] })