// Copy of https://github.com/prettier/plugin-pug/blob/main/src/utils/vue.ts import type { Rule, SourceCode } from 'eslint'; import type { AttributeToken, TagToken, Token as PugToken } from 'pug-lexer'; import type { AssignmentProperty, ASTNode, CallExpression, Declaration, ESNode, Expression, Identifier, Literal, MemberExpression, MethodDefinition, NewExpression, ObjectExpression, PrivateIdentifier, Property, SpreadElement, Super, TemplateLiteral, TSAsExpression, VAttribute, VDirective, VDocumentFragment, VElement, VExpressionContainer, VText } from '../util-types/ast'; import type { Token } from '../util-types/node'; import type { ParserServices, TemplateListener } from '../util-types/parser-services'; import type { VueObjectType } from '../util-types/utils'; import { isHtmlWellKnownElementName } from './html-element'; import { getAttributeTokens } from './pug-utils'; import { isSvgWellKnownElementName } from './svg-element'; /** * Indicates whether the attribute name is a Vue event binding. * * --- * * Example event binding: * ``` * v-btn(@click="doSomething") Do Something * ``` * * In this case `name` is `@click`. * * --- * * Checks for: `v-on:`. * * Also shorthands like `@*` are checked. * * --- * * @param name Name of tag attribute. * @returns `true` if `name` passes the vue event binding check, otherwise `false`. */ export function isVueEventBinding(name: string): boolean { return /^(v-on:|@).*/.test(name); } /** * Indicates whether the attribute name is a Vue expression. * * --- * * Example expression: * ``` * v-text-field(v-model="value", :label="label") Do Something * ``` * * In this case `name` is `v-model` and `:label`. * * --- * * Checks for: `v-bind`, `v-slot`, `v-model`, `v-if`, `v-else-if`, `v-for`, * `v-text` and `v-html`. * * Also shorthands like `:*` are checked. * * --- * * @param name Name of tag attribute. * @returns `true` if `name` passes the vue expression check, otherwise `false`. */ export function isVueExpression(name: string): boolean { return /^((v-(bind|slot))?:|v-(model|slot|if|for|else-if|text|html)|#).*/.test(name); } /** * Indicates whether the attribute name is a Vue v-for and includes a `of`. * * --- * * Example expression: * ``` * tr(v-for="item of items", :key="item.id") * ``` * * In this case `name` is `v-for` and it includes a `of`. * * --- * * Checks for: `v-for` and `of`. * * --- * * @param name Name of tag attribute. * @param val Value of tag attribute. * @returns `true` if `name` and `val` passes the vue `v-for` with `of` check, otherwise `false`. */ export function isVueVForWithOf(name: string, val: string): boolean { return 'v-for' === name && val.includes('of'); } /** * Indicates whether the attribute name is a Vue v-bind. * * --- * * Example expression: * ``` * v-btn(v-bind="$attrs") * ``` * * In this case `name` is `v-bind`. * * --- * * Checks for: `v-bind`. * * --- * * @param name Name of tag attribute. * @returns `true` if `name` passes the vue `v-bind` check, otherwise `false`. */ export function isVueVBindExpression(name: string): boolean { return 'v-bind' === name; } /** * Indicates whether the attribute name is a Vue v-on. * * --- * * Example expression: * ``` * v-btn(v-on="on") * ``` * * In this case `name` is `v-on`. * * --- * * Checks for: `v-on`. * * --- * * @param name Name of tag attribute. * @returns `true` if `name` passes the vue `v-on` check, otherwise `false`. */ export function isVueVOnExpression(name: string): boolean { return 'v-on' === name; } export function isVueBinding(token: AttributeToken): boolean { return ( isVueExpression(token.name) || isVueEventBinding(token.name) || (typeof token.val === 'string' && isVueVForWithOf(token.name, token.val)) || isVueVBindExpression(token.name) || isVueVOnExpression(token.name) ); } // --------------------------------------------------------------------------- /** * Gets the parent node of the given node. This method returns a value ignoring `X as F`. */ export function getParent(node: Expression): ASTNode { let parent: ASTNode = node.parent; while (parent.type === 'TSAsExpression') { parent = parent.parent; } return parent; } export function isVueFile(path: string): boolean { return path.endsWith('.vue') || path.endsWith('.jsx'); } /** * Check whether the given node is a Vue component based * on the filename and default export type * `export default {}` in `.vue || .jsx`. * * @param node Node to check. * @param path File name with extension. */ export function isVueComponentFile(node: ESNode, path: string): boolean { return isVueFile(path) && node.type === 'ExportDefaultDeclaration' && node.declaration.type === 'ObjectExpression'; } /** * Retrieve `TSAsExpression#expression` value if the given node a `TSAsExpression` node. Otherwise, pass through it. * * @param node The node to address. * @returns The `TSAsExpression#expression` value if the node is a `TSAsExpression` node. Otherwise, the node. */ export function skipTSAsExpression( node: T | TSAsExpression ): T { if (!node) { return node; } if (node.type === 'TSAsExpression') { // @ts-expect-error return skipTSAsExpression(node.expression); } return node; } /** * Checks whether the given node is VElement. */ export function isVElement(node: VElement | VExpressionContainer | VText): node is VElement { return node.type === 'VElement'; } /** * Get the attribute which has the given name. * * @param node The start tag node to check. * @param name The attribute name to check. * @param value The attribute value to check. * @returns The found attribute. */ export function getAttribute(node: VElement, name: string, value?: string): VAttribute | VDirective | null { return ( node.startTag.attributes.find((node) => { return ( !node.directive && node.key.name === name && (value === undefined || (node.value != null && node.value.value === value)) ); }) ?? null ); } /** * Check whether the given start tag has specific directive. * * @param node The start tag node to check. * @param name The attribute name to check. * @param value The attribute value to check. * @returns `true` if the start tag has the attribute. */ export function hasAttribute(node: VElement, name: string, value?: string): boolean { return Boolean(getAttribute(node, name, value)); } /** * Gets the element of `