index.js 91 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136
  1. /**
  2. * @author Toru Nagashima <https://github.com/mysticatea>
  3. * @copyright 2017 Toru Nagashima. All rights reserved.
  4. * See LICENSE file in root directory for full license.
  5. */
  6. 'use strict'
  7. /**
  8. * @typedef {import('eslint').Rule.RuleModule} RuleModule
  9. * @typedef {import('estree').Position} Position
  10. * @typedef {import('eslint').Rule.CodePath} CodePath
  11. * @typedef {import('eslint').Rule.CodePathSegment} CodePathSegment
  12. */
  13. /**
  14. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentArrayProp} ComponentArrayProp
  15. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectProp} ComponentObjectProp
  16. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeProp} ComponentTypeProp
  17. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownProp} ComponentUnknownProp
  18. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentProp} ComponentProp
  19. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentArrayEmit} ComponentArrayEmit
  20. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentObjectEmit} ComponentObjectEmit
  21. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeEmit} ComponentTypeEmit
  22. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentUnknownEmit} ComponentUnknownEmit
  23. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentEmit} ComponentEmit
  24. */
  25. /**
  26. * @typedef { {key: string | null, value: BlockStatement | null} } ComponentComputedProperty
  27. */
  28. /**
  29. * @typedef { 'props' | 'asyncData' | 'data' | 'computed' | 'setup' | 'watch' | 'methods' | 'provide' | 'inject' | 'expose' } GroupName
  30. * @typedef { { type: 'array', name: string, groupName: GroupName, node: Literal | TemplateLiteral } } ComponentArrayPropertyData
  31. * @typedef { { type: 'object', name: string, groupName: GroupName, node: Identifier | Literal | TemplateLiteral, property: Property } } ComponentObjectPropertyData
  32. * @typedef { ComponentArrayPropertyData | ComponentObjectPropertyData } ComponentPropertyData
  33. */
  34. /**
  35. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').VueObjectType} VueObjectType
  36. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').VueObjectData} VueObjectData
  37. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').VueVisitor} VueVisitor
  38. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ScriptSetupVisitor} ScriptSetupVisitor
  39. */
  40. // ------------------------------------------------------------------------------
  41. // Helpers
  42. // ------------------------------------------------------------------------------
  43. const HTML_ELEMENT_NAMES = new Set(require('./html-elements.json'))
  44. const SVG_ELEMENT_NAMES = new Set(require('./svg-elements.json'))
  45. const VOID_ELEMENT_NAMES = new Set(require('./void-elements.json'))
  46. const VUE2_BUILTIN_COMPONENT_NAMES = new Set(
  47. require('./vue2-builtin-components')
  48. )
  49. const VUE3_BUILTIN_COMPONENT_NAMES = new Set(
  50. require('./vue3-builtin-components')
  51. )
  52. const path = require('path')
  53. const vueEslintParser = require('vue-eslint-parser')
  54. const { traverseNodes, getFallbackKeys } = vueEslintParser.AST
  55. const { findVariable } = require('eslint-utils')
  56. const {
  57. getComponentPropsFromTypeDefine,
  58. getComponentEmitsFromTypeDefine,
  59. isTypeNode
  60. } = require('./ts-ast-utils')
  61. /**
  62. * @type { WeakMap<RuleContext, Token[]> }
  63. */
  64. const componentComments = new WeakMap()
  65. /** @type { Map<string, RuleModule> | null } */
  66. let ruleMap = null
  67. /**
  68. * Get the core rule implementation from the rule name
  69. * @param {string} name
  70. * @returns {RuleModule | null}
  71. */
  72. function getCoreRule(name) {
  73. const map = ruleMap || (ruleMap = new (require('eslint').Linter)().getRules())
  74. return map.get(name) || null
  75. }
  76. /**
  77. * @template {object} T
  78. * @param {T} target
  79. * @param {Partial<T>[]} propsArray
  80. * @returns {T}
  81. */
  82. function newProxy(target, ...propsArray) {
  83. const result = new Proxy(
  84. {},
  85. {
  86. get(_object, key) {
  87. for (const props of propsArray) {
  88. if (key in props) {
  89. // @ts-expect-error
  90. return props[key]
  91. }
  92. }
  93. // @ts-expect-error
  94. return target[key]
  95. },
  96. has(_object, key) {
  97. return key in target
  98. },
  99. ownKeys(_object) {
  100. return Reflect.ownKeys(target)
  101. },
  102. getPrototypeOf(_object) {
  103. return Reflect.getPrototypeOf(target)
  104. }
  105. }
  106. )
  107. return /** @type {T} */ (result)
  108. }
  109. /**
  110. * Wrap the rule context object to override methods which access to tokens (such as getTokenAfter).
  111. * @param {RuleContext} context The rule context object.
  112. * @param {ParserServices.TokenStore} tokenStore The token store object for template.
  113. * @param {Object} options The option of this rule.
  114. * @param {boolean} [options.applyDocument] If `true`, apply check to document fragment.
  115. * @returns {RuleContext}
  116. */
  117. function wrapContextToOverrideTokenMethods(context, tokenStore, options) {
  118. const eslintSourceCode = context.getSourceCode()
  119. const rootNode = options.applyDocument
  120. ? context.parserServices.getDocumentFragment &&
  121. context.parserServices.getDocumentFragment()
  122. : eslintSourceCode.ast.templateBody
  123. /** @type {Token[] | null} */
  124. let tokensAndComments = null
  125. function getTokensAndComments() {
  126. if (tokensAndComments) {
  127. return tokensAndComments
  128. }
  129. tokensAndComments = rootNode
  130. ? tokenStore.getTokens(rootNode, {
  131. includeComments: true
  132. })
  133. : []
  134. return tokensAndComments
  135. }
  136. /** @param {number} index */
  137. function getNodeByRangeIndex(index) {
  138. if (!rootNode) {
  139. return eslintSourceCode.ast
  140. }
  141. /** @type {ASTNode} */
  142. let result = eslintSourceCode.ast
  143. /** @type {ASTNode[]} */
  144. const skipNodes = []
  145. let breakFlag = false
  146. traverseNodes(rootNode, {
  147. enterNode(node, parent) {
  148. if (breakFlag) {
  149. return
  150. }
  151. if (skipNodes[0] === parent) {
  152. skipNodes.unshift(node)
  153. return
  154. }
  155. if (node.range[0] <= index && index < node.range[1]) {
  156. result = node
  157. } else {
  158. skipNodes.unshift(node)
  159. }
  160. },
  161. leaveNode(node) {
  162. if (breakFlag) {
  163. return
  164. }
  165. if (result === node) {
  166. breakFlag = true
  167. } else if (skipNodes[0] === node) {
  168. skipNodes.shift()
  169. }
  170. }
  171. })
  172. return result
  173. }
  174. const sourceCode = newProxy(
  175. eslintSourceCode,
  176. {
  177. get tokensAndComments() {
  178. return getTokensAndComments()
  179. },
  180. getNodeByRangeIndex
  181. },
  182. tokenStore
  183. )
  184. const containerScopes = new WeakMap()
  185. /**
  186. * @param {ASTNode} node
  187. */
  188. function getContainerScope(node) {
  189. const exprContainer = getVExpressionContainer(node)
  190. if (!exprContainer) {
  191. return null
  192. }
  193. const cache = containerScopes.get(exprContainer)
  194. if (cache) {
  195. return cache
  196. }
  197. const programNode = eslintSourceCode.ast
  198. const parserOptions = context.parserOptions || {}
  199. const ecmaFeatures = parserOptions.ecmaFeatures || {}
  200. const ecmaVersion = parserOptions.ecmaVersion || 2020
  201. const sourceType = programNode.sourceType
  202. try {
  203. const eslintScope = createRequire(require.resolve('eslint'))(
  204. 'eslint-scope'
  205. )
  206. const expStmt = newProxy(exprContainer, {
  207. // @ts-expect-error
  208. type: 'ExpressionStatement'
  209. })
  210. const scopeProgram = newProxy(programNode, {
  211. // @ts-expect-error
  212. body: [expStmt]
  213. })
  214. const scope = eslintScope.analyze(scopeProgram, {
  215. ignoreEval: true,
  216. nodejsScope: false,
  217. impliedStrict: ecmaFeatures.impliedStrict,
  218. ecmaVersion,
  219. sourceType,
  220. fallback: getFallbackKeys
  221. })
  222. containerScopes.set(exprContainer, scope)
  223. return scope
  224. } catch (e) {
  225. // ignore
  226. // console.log(e)
  227. }
  228. return null
  229. }
  230. return newProxy(context, {
  231. getSourceCode() {
  232. return sourceCode
  233. },
  234. getDeclaredVariables(node) {
  235. const scope = getContainerScope(node)
  236. if (scope) {
  237. return scope.getDeclaredVariables(node)
  238. }
  239. return context.getDeclaredVariables(node)
  240. }
  241. })
  242. }
  243. /**
  244. * Wrap the rule context object to override report method to skip the dynamic argument.
  245. * @param {RuleContext} context The rule context object.
  246. * @returns {RuleContext}
  247. */
  248. function wrapContextToOverrideReportMethodToSkipDynamicArgument(context) {
  249. const sourceCode = context.getSourceCode()
  250. const templateBody = sourceCode.ast.templateBody
  251. if (!templateBody) {
  252. return context
  253. }
  254. /** @type {Range[]} */
  255. const directiveKeyRanges = []
  256. const traverseNodes = vueEslintParser.AST.traverseNodes
  257. traverseNodes(templateBody, {
  258. enterNode(node, parent) {
  259. if (
  260. parent &&
  261. parent.type === 'VDirectiveKey' &&
  262. node.type === 'VExpressionContainer'
  263. ) {
  264. directiveKeyRanges.push(node.range)
  265. }
  266. },
  267. leaveNode() {}
  268. })
  269. return newProxy(context, {
  270. report(descriptor, ...args) {
  271. let range = null
  272. if (descriptor.loc) {
  273. const startLoc = descriptor.loc.start || descriptor.loc
  274. const endLoc = descriptor.loc.end || startLoc
  275. range = [
  276. sourceCode.getIndexFromLoc(startLoc),
  277. sourceCode.getIndexFromLoc(endLoc)
  278. ]
  279. } else if (descriptor.node) {
  280. range = descriptor.node.range
  281. }
  282. if (range) {
  283. for (const directiveKeyRange of directiveKeyRanges) {
  284. if (
  285. range[0] < directiveKeyRange[1] &&
  286. directiveKeyRange[0] < range[1]
  287. ) {
  288. return
  289. }
  290. }
  291. }
  292. context.report(descriptor, ...args)
  293. }
  294. })
  295. }
  296. // ------------------------------------------------------------------------------
  297. // Exports
  298. // ------------------------------------------------------------------------------
  299. module.exports = {
  300. /**
  301. * Register the given visitor to parser services.
  302. * If the parser service of `vue-eslint-parser` was not found,
  303. * this generates a warning.
  304. *
  305. * @param {RuleContext} context The rule context to use parser services.
  306. * @param {TemplateListener} templateBodyVisitor The visitor to traverse the template body.
  307. * @param {RuleListener} [scriptVisitor] The visitor to traverse the script.
  308. * @param { { templateBodyTriggerSelector: "Program" | "Program:exit" } } [options] The options.
  309. * @returns {RuleListener} The merged visitor.
  310. */
  311. defineTemplateBodyVisitor,
  312. /**
  313. * Register the given visitor to parser services.
  314. * If the parser service of `vue-eslint-parser` was not found,
  315. * this generates a warning.
  316. *
  317. * @param {RuleContext} context The rule context to use parser services.
  318. * @param {TemplateListener} documentVisitor The visitor to traverse the document.
  319. * @param { { triggerSelector: "Program" | "Program:exit" } } [options] The options.
  320. * @returns {RuleListener} The merged visitor.
  321. */
  322. defineDocumentVisitor,
  323. /**
  324. * @callback WrapCoreRuleCreate
  325. * @param {RuleContext} ruleContext
  326. * @param {WrapCoreRuleCreateContext} wrapContext
  327. * @returns {TemplateListener}
  328. *
  329. * @typedef {object} WrapCoreRuleCreateContext
  330. * @property {RuleListener} coreHandlers
  331. */
  332. /**
  333. * @callback WrapCoreRulePreprocess
  334. * @param {RuleContext} ruleContext
  335. * @param {WrapCoreRulePreprocessContext} wrapContext
  336. * @returns {void}
  337. *
  338. * @typedef {object} WrapCoreRulePreprocessContext
  339. * @property { (override: Partial<RuleContext>) => RuleContext } wrapContextToOverrideProperties Wrap the rule context object to override
  340. * @property { (visitor: TemplateListener) => void } defineVisitor Define template body visitor
  341. */
  342. /**
  343. * Wrap a given core rule to apply it to Vue.js template.
  344. * @param {string} coreRuleName The name of the core rule implementation to wrap.
  345. * @param {Object} [options] The option of this rule.
  346. * @param {string[]} [options.categories] The categories of this rule.
  347. * @param {boolean} [options.skipDynamicArguments] If `true`, skip validation within dynamic arguments.
  348. * @param {boolean} [options.skipDynamicArgumentsReport] If `true`, skip report within dynamic arguments.
  349. * @param {boolean} [options.applyDocument] If `true`, apply check to document fragment.
  350. * @param {WrapCoreRulePreprocess} [options.preprocess] Preprocess to calling create of core rule.
  351. * @param {WrapCoreRuleCreate} [options.create] If define, extend core rule.
  352. * @returns {RuleModule} The wrapped rule implementation.
  353. */
  354. wrapCoreRule(coreRuleName, options) {
  355. const coreRule = getCoreRule(coreRuleName)
  356. if (!coreRule) {
  357. return {
  358. meta: {
  359. type: 'problem',
  360. docs: {
  361. url: `https://eslint.vuejs.org/rules/${coreRuleName}.html`
  362. }
  363. },
  364. create(context) {
  365. return defineTemplateBodyVisitor(context, {
  366. "VElement[name='template'][parent.type='VDocumentFragment']"(node) {
  367. context.report({
  368. node,
  369. message: `Failed to extend ESLint core rule "${coreRuleName}". You may be able to use this rule by upgrading the version of ESLint. If you cannot upgrade it, turn off this rule.`
  370. })
  371. }
  372. })
  373. }
  374. }
  375. }
  376. let description = coreRule.meta.docs.description
  377. if (description) {
  378. description += ' in `<template>`'
  379. }
  380. const {
  381. categories,
  382. skipDynamicArguments,
  383. skipDynamicArgumentsReport,
  384. applyDocument,
  385. preprocess,
  386. create
  387. } = options || {}
  388. return {
  389. create(context) {
  390. const tokenStore =
  391. context.parserServices.getTemplateBodyTokenStore &&
  392. context.parserServices.getTemplateBodyTokenStore()
  393. // The `context.getSourceCode()` cannot access the tokens of templates.
  394. // So override the methods which access to tokens by the `tokenStore`.
  395. if (tokenStore) {
  396. context = wrapContextToOverrideTokenMethods(context, tokenStore, {
  397. applyDocument
  398. })
  399. }
  400. if (skipDynamicArgumentsReport) {
  401. context =
  402. wrapContextToOverrideReportMethodToSkipDynamicArgument(context)
  403. }
  404. /** @type {TemplateListener} */
  405. const handlers = {}
  406. if (preprocess) {
  407. preprocess(context, {
  408. wrapContextToOverrideProperties(override) {
  409. context = newProxy(context, override)
  410. return context
  411. },
  412. defineVisitor(visitor) {
  413. compositingVisitors(handlers, visitor)
  414. }
  415. })
  416. }
  417. const coreHandlers = coreRule.create(context)
  418. compositingVisitors(handlers, coreHandlers)
  419. // Move `Program` handlers to `VElement[parent.type!='VElement']`
  420. if (handlers.Program) {
  421. handlers[
  422. applyDocument
  423. ? 'VDocumentFragment'
  424. : "VElement[parent.type!='VElement']"
  425. ] = /** @type {any} */ (handlers.Program)
  426. delete handlers.Program
  427. }
  428. if (handlers['Program:exit']) {
  429. handlers[
  430. applyDocument
  431. ? 'VDocumentFragment:exit'
  432. : "VElement[parent.type!='VElement']:exit"
  433. ] = /** @type {any} */ (handlers['Program:exit'])
  434. delete handlers['Program:exit']
  435. }
  436. if (skipDynamicArguments) {
  437. let withinDynamicArguments = false
  438. for (const name of Object.keys(handlers)) {
  439. const original = handlers[name]
  440. /** @param {any[]} args */
  441. handlers[name] = (...args) => {
  442. if (withinDynamicArguments) return
  443. // @ts-expect-error
  444. original(...args)
  445. }
  446. }
  447. handlers['VDirectiveKey > VExpressionContainer'] = () => {
  448. withinDynamicArguments = true
  449. }
  450. handlers['VDirectiveKey > VExpressionContainer:exit'] = () => {
  451. withinDynamicArguments = false
  452. }
  453. }
  454. if (create) {
  455. compositingVisitors(handlers, create(context, { coreHandlers }))
  456. }
  457. if (applyDocument) {
  458. // Apply the handlers to document.
  459. return defineDocumentVisitor(context, handlers)
  460. }
  461. // Apply the handlers to templates.
  462. return defineTemplateBodyVisitor(context, handlers)
  463. },
  464. meta: Object.assign({}, coreRule.meta, {
  465. docs: Object.assign({}, coreRule.meta.docs, {
  466. description,
  467. category: null,
  468. categories,
  469. url: `https://eslint.vuejs.org/rules/${path.basename(
  470. coreRule.meta.docs.url || ''
  471. )}.html`,
  472. extensionRule: true,
  473. coreRuleUrl: coreRule.meta.docs.url
  474. })
  475. })
  476. }
  477. },
  478. /**
  479. * Checks whether the given value is defined.
  480. * @template T
  481. * @param {T | null | undefined} v
  482. * @returns {v is T}
  483. */
  484. isDef,
  485. /**
  486. * Flattens arrays, objects and iterable objects.
  487. * @template T
  488. * @param {T | Iterable<T> | null | undefined} v
  489. * @returns {T[]}
  490. */
  491. flatten,
  492. /**
  493. * Get the previous sibling element of the given element.
  494. * @param {VElement} node The element node to get the previous sibling element.
  495. * @returns {VElement|null} The previous sibling element.
  496. */
  497. prevSibling(node) {
  498. let prevElement = null
  499. for (const siblingNode of (node.parent && node.parent.children) || []) {
  500. if (siblingNode === node) {
  501. return prevElement
  502. }
  503. if (siblingNode.type === 'VElement') {
  504. prevElement = siblingNode
  505. }
  506. }
  507. return null
  508. },
  509. /**
  510. * Check whether the given directive attribute has their empty value (`=""`).
  511. * @param {VDirective} node The directive attribute node to check.
  512. * @param {RuleContext} context The rule context to use parser services.
  513. * @returns {boolean} `true` if the directive attribute has their empty value (`=""`).
  514. */
  515. isEmptyValueDirective(node, context) {
  516. if (node.value == null) {
  517. return false
  518. }
  519. if (node.value.expression != null) {
  520. return false
  521. }
  522. let valueText = context.getSourceCode().getText(node.value)
  523. if (
  524. (valueText[0] === '"' || valueText[0] === "'") &&
  525. valueText[0] === valueText[valueText.length - 1]
  526. ) {
  527. // quoted
  528. valueText = valueText.slice(1, -1)
  529. }
  530. if (!valueText) {
  531. // empty
  532. return true
  533. }
  534. return false
  535. },
  536. /**
  537. * Check whether the given directive attribute has their empty expression value (e.g. `=" "`, `="/* &ast;/"`).
  538. * @param {VDirective} node The directive attribute node to check.
  539. * @param {RuleContext} context The rule context to use parser services.
  540. * @returns {boolean} `true` if the directive attribute has their empty expression value.
  541. */
  542. isEmptyExpressionValueDirective(node, context) {
  543. if (node.value == null) {
  544. return false
  545. }
  546. if (node.value.expression != null) {
  547. return false
  548. }
  549. const valueNode = node.value
  550. const tokenStore = context.parserServices.getTemplateBodyTokenStore()
  551. let quote1 = null
  552. let quote2 = null
  553. // `node.value` may be only comments, so cannot get the correct tokens with `tokenStore.getTokens(node.value)`.
  554. for (const token of tokenStore.getTokens(node)) {
  555. if (token.range[1] <= valueNode.range[0]) {
  556. continue
  557. }
  558. if (valueNode.range[1] <= token.range[0]) {
  559. // empty
  560. return true
  561. }
  562. if (
  563. !quote1 &&
  564. token.type === 'Punctuator' &&
  565. (token.value === '"' || token.value === "'")
  566. ) {
  567. quote1 = token
  568. continue
  569. }
  570. if (
  571. !quote2 &&
  572. quote1 &&
  573. token.type === 'Punctuator' &&
  574. token.value === quote1.value
  575. ) {
  576. quote2 = token
  577. continue
  578. }
  579. // not empty
  580. return false
  581. }
  582. // empty
  583. return true
  584. },
  585. /**
  586. * Get the attribute which has the given name.
  587. * @param {VElement} node The start tag node to check.
  588. * @param {string} name The attribute name to check.
  589. * @param {string} [value] The attribute value to check.
  590. * @returns {VAttribute | null} The found attribute.
  591. */
  592. getAttribute,
  593. /**
  594. * Check whether the given start tag has specific directive.
  595. * @param {VElement} node The start tag node to check.
  596. * @param {string} name The attribute name to check.
  597. * @param {string} [value] The attribute value to check.
  598. * @returns {boolean} `true` if the start tag has the attribute.
  599. */
  600. hasAttribute,
  601. /**
  602. * Get the directive list which has the given name.
  603. * @param {VElement | VStartTag} node The start tag node to check.
  604. * @param {string} name The directive name to check.
  605. * @returns {VDirective[]} The array of `v-slot` directives.
  606. */
  607. getDirectives,
  608. /**
  609. * Get the directive which has the given name.
  610. * @param {VElement} node The start tag node to check.
  611. * @param {string} name The directive name to check.
  612. * @param {string} [argument] The directive argument to check.
  613. * @returns {VDirective | null} The found directive.
  614. */
  615. getDirective,
  616. /**
  617. * Check whether the given start tag has specific directive.
  618. * @param {VElement} node The start tag node to check.
  619. * @param {string} name The directive name to check.
  620. * @param {string} [argument] The directive argument to check.
  621. * @returns {boolean} `true` if the start tag has the directive.
  622. */
  623. hasDirective,
  624. /**
  625. * Returns the list of all registered components
  626. * @param {ObjectExpression} componentObject
  627. * @returns { { node: Property, name: string }[] } Array of ASTNodes
  628. */
  629. getRegisteredComponents(componentObject) {
  630. const componentsNode = componentObject.properties.find(
  631. /**
  632. * @param {ESNode} p
  633. * @returns {p is (Property & { key: Identifier & {name: 'components'}, value: ObjectExpression })}
  634. */
  635. (p) => {
  636. return (
  637. p.type === 'Property' &&
  638. getStaticPropertyName(p) === 'components' &&
  639. p.value.type === 'ObjectExpression'
  640. )
  641. }
  642. )
  643. if (!componentsNode) {
  644. return []
  645. }
  646. return componentsNode.value.properties
  647. .filter(isProperty)
  648. .map((node) => {
  649. const name = getStaticPropertyName(node)
  650. return name ? { node, name } : null
  651. })
  652. .filter(isDef)
  653. },
  654. /**
  655. * Check whether the previous sibling element has `if` or `else-if` directive.
  656. * @param {VElement} node The element node to check.
  657. * @returns {boolean} `true` if the previous sibling element has `if` or `else-if` directive.
  658. */
  659. prevElementHasIf(node) {
  660. const prev = this.prevSibling(node)
  661. return (
  662. prev != null &&
  663. prev.startTag.attributes.some(
  664. (a) =>
  665. a.directive &&
  666. (a.key.name.name === 'if' || a.key.name.name === 'else-if')
  667. )
  668. )
  669. },
  670. /**
  671. * Returns a generator with all child element v-if chains of the given element.
  672. * @param {VElement} node The element node to check.
  673. * @returns {IterableIterator<VElement[]>}
  674. */
  675. *iterateChildElementsChains(node) {
  676. let vIf = false
  677. /** @type {VElement[]} */
  678. let elementChain = []
  679. for (const childNode of node.children) {
  680. if (childNode.type === 'VElement') {
  681. let connected
  682. if (hasDirective(childNode, 'if')) {
  683. connected = false
  684. vIf = true
  685. } else if (hasDirective(childNode, 'else-if')) {
  686. connected = vIf
  687. vIf = true
  688. } else if (hasDirective(childNode, 'else')) {
  689. connected = vIf
  690. vIf = false
  691. } else {
  692. connected = false
  693. vIf = false
  694. }
  695. if (connected) {
  696. elementChain.push(childNode)
  697. } else {
  698. if (elementChain.length) {
  699. yield elementChain
  700. }
  701. elementChain = [childNode]
  702. }
  703. } else if (childNode.type !== 'VText' || childNode.value.trim() !== '') {
  704. vIf = false
  705. }
  706. }
  707. if (elementChain.length) {
  708. yield elementChain
  709. }
  710. },
  711. /**
  712. * @param {ASTNode} node
  713. * @returns {node is Literal | TemplateLiteral}
  714. */
  715. isStringLiteral(node) {
  716. return (
  717. (node.type === 'Literal' && typeof node.value === 'string') ||
  718. (node.type === 'TemplateLiteral' && node.expressions.length === 0)
  719. )
  720. },
  721. /**
  722. * Check whether the given node is a custom component or not.
  723. * @param {VElement} node The start tag node to check.
  724. * @returns {boolean} `true` if the node is a custom component.
  725. */
  726. isCustomComponent(node) {
  727. return (
  728. (this.isHtmlElementNode(node) &&
  729. !this.isHtmlWellKnownElementName(node.rawName)) ||
  730. (this.isSvgElementNode(node) &&
  731. !this.isSvgWellKnownElementName(node.rawName)) ||
  732. hasAttribute(node, 'is') ||
  733. hasDirective(node, 'bind', 'is') ||
  734. hasDirective(node, 'is')
  735. )
  736. },
  737. /**
  738. * Check whether the given node is a HTML element or not.
  739. * @param {VElement} node The node to check.
  740. * @returns {boolean} `true` if the node is a HTML element.
  741. */
  742. isHtmlElementNode(node) {
  743. return node.namespace === vueEslintParser.AST.NS.HTML
  744. },
  745. /**
  746. * Check whether the given node is a SVG element or not.
  747. * @param {VElement} node The node to check.
  748. * @returns {boolean} `true` if the name is a SVG element.
  749. */
  750. isSvgElementNode(node) {
  751. return node.namespace === vueEslintParser.AST.NS.SVG
  752. },
  753. /**
  754. * Check whether the given name is a MathML element or not.
  755. * @param {VElement} node The node to check.
  756. * @returns {boolean} `true` if the node is a MathML element.
  757. */
  758. isMathMLElementNode(node) {
  759. return node.namespace === vueEslintParser.AST.NS.MathML
  760. },
  761. /**
  762. * Check whether the given name is an well-known element or not.
  763. * @param {string} name The name to check.
  764. * @returns {boolean} `true` if the name is an well-known element name.
  765. */
  766. isHtmlWellKnownElementName(name) {
  767. return HTML_ELEMENT_NAMES.has(name)
  768. },
  769. /**
  770. * Check whether the given name is an well-known SVG element or not.
  771. * @param {string} name The name to check.
  772. * @returns {boolean} `true` if the name is an well-known SVG element name.
  773. */
  774. isSvgWellKnownElementName(name) {
  775. return SVG_ELEMENT_NAMES.has(name)
  776. },
  777. /**
  778. * Check whether the given name is a void element name or not.
  779. * @param {string} name The name to check.
  780. * @returns {boolean} `true` if the name is a void element name.
  781. */
  782. isHtmlVoidElementName(name) {
  783. return VOID_ELEMENT_NAMES.has(name)
  784. },
  785. /**
  786. * Check whether the given name is Vue builtin component name or not.
  787. * @param {string} name The name to check.
  788. * @returns {boolean} `true` if the name is a builtin component name
  789. */
  790. isBuiltInComponentName(name) {
  791. return (
  792. VUE3_BUILTIN_COMPONENT_NAMES.has(name) ||
  793. VUE2_BUILTIN_COMPONENT_NAMES.has(name)
  794. )
  795. },
  796. /**
  797. * Check whether the given name is Vue builtin directive name or not.
  798. * @param {string} name The name to check.
  799. * @returns {boolean} `true` if the name is a builtin Directive name
  800. */
  801. isBuiltInDirectiveName(name) {
  802. return (
  803. name === 'bind' ||
  804. name === 'on' ||
  805. name === 'text' ||
  806. name === 'html' ||
  807. name === 'show' ||
  808. name === 'if' ||
  809. name === 'else' ||
  810. name === 'else-if' ||
  811. name === 'for' ||
  812. name === 'model' ||
  813. name === 'slot' ||
  814. name === 'pre' ||
  815. name === 'cloak' ||
  816. name === 'once' ||
  817. name === 'memo' ||
  818. name === 'is'
  819. )
  820. },
  821. /**
  822. * Gets the property name of a given node.
  823. * @param {Property|AssignmentProperty|MethodDefinition|MemberExpression} node - The node to get.
  824. * @return {string|null} The property name if static. Otherwise, null.
  825. */
  826. getStaticPropertyName,
  827. /**
  828. * Gets the string of a given node.
  829. * @param {Literal|TemplateLiteral} node - The node to get.
  830. * @return {string|null} The string if static. Otherwise, null.
  831. */
  832. getStringLiteralValue,
  833. /**
  834. * Get all props by looking at all component's properties
  835. * @param {ObjectExpression} componentObject Object with component definition
  836. * @return {(ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp)[]} Array of component props
  837. */
  838. getComponentPropsFromOptions,
  839. // TODO Since `utils` is used in some popular packages, we will remove it in the major version.
  840. /**
  841. * For backward compatibility.
  842. * @deprecated Use getComponentPropsFromOptions instead.
  843. */
  844. getComponentProps: getComponentPropsFromOptions,
  845. /**
  846. * Get all emits by looking at all component's properties
  847. * @param {ObjectExpression} componentObject Object with component definition
  848. * @return {(ComponentArrayEmit | ComponentObjectEmit | ComponentUnknownEmit)[]} Array of component emits
  849. */
  850. getComponentEmitsFromOptions,
  851. // TODO Since `utils` is used in some popular packages, we will remove it in the major version.
  852. /**
  853. * For backward compatibility.
  854. * @deprecated Use getComponentEmitsFromOptions instead.
  855. */
  856. getComponentEmits: getComponentEmitsFromOptions,
  857. /**
  858. * Get all computed properties by looking at all component's properties
  859. * @param {ObjectExpression} componentObject Object with component definition
  860. * @return {ComponentComputedProperty[]} Array of computed properties in format: [{key: String, value: ASTNode}]
  861. */
  862. getComputedProperties(componentObject) {
  863. const computedPropertiesNode = componentObject.properties.find(
  864. /**
  865. * @param {ESNode} p
  866. * @returns {p is (Property & { key: Identifier & {name: 'computed'}, value: ObjectExpression })}
  867. */
  868. (p) => {
  869. return (
  870. p.type === 'Property' &&
  871. getStaticPropertyName(p) === 'computed' &&
  872. p.value.type === 'ObjectExpression'
  873. )
  874. }
  875. )
  876. if (!computedPropertiesNode) {
  877. return []
  878. }
  879. return computedPropertiesNode.value.properties
  880. .filter(isProperty)
  881. .map((cp) => {
  882. const key = getStaticPropertyName(cp)
  883. /** @type {Expression} */
  884. const propValue = skipTSAsExpression(cp.value)
  885. /** @type {BlockStatement | null} */
  886. let value = null
  887. if (propValue.type === 'FunctionExpression') {
  888. value = propValue.body
  889. } else if (propValue.type === 'ObjectExpression') {
  890. const get =
  891. /** @type {(Property & { value: FunctionExpression }) | null} */ (
  892. findProperty(
  893. propValue,
  894. 'get',
  895. (p) => p.value.type === 'FunctionExpression'
  896. )
  897. )
  898. value = get ? get.value.body : null
  899. }
  900. return { key, value }
  901. })
  902. },
  903. /**
  904. * Get getter body from computed function
  905. * @param {CallExpression} callExpression call of computed function
  906. * @return {FunctionExpression | ArrowFunctionExpression | null} getter function
  907. */
  908. getGetterBodyFromComputedFunction(callExpression) {
  909. if (callExpression.arguments.length <= 0) {
  910. return null
  911. }
  912. const arg = callExpression.arguments[0]
  913. if (
  914. arg.type === 'FunctionExpression' ||
  915. arg.type === 'ArrowFunctionExpression'
  916. ) {
  917. return arg
  918. }
  919. if (arg.type === 'ObjectExpression') {
  920. const getProperty =
  921. /** @type {(Property & { value: FunctionExpression | ArrowFunctionExpression }) | null} */ (
  922. findProperty(
  923. arg,
  924. 'get',
  925. (p) =>
  926. p.value.type === 'FunctionExpression' ||
  927. p.value.type === 'ArrowFunctionExpression'
  928. )
  929. )
  930. return getProperty ? getProperty.value : null
  931. }
  932. return null
  933. },
  934. isVueFile,
  935. /**
  936. * Checks whether the current file is uses `<script setup>`
  937. * @param {RuleContext} context The ESLint rule context object.
  938. */
  939. isScriptSetup,
  940. /**
  941. * Gets the element of `<script setup>`
  942. * @param {RuleContext} context The ESLint rule context object.
  943. * @returns {VElement | null} the element of `<script setup>`
  944. */
  945. getScriptSetupElement,
  946. /**
  947. * Check if current file is a Vue instance or component and call callback
  948. * @param {RuleContext} context The ESLint rule context object.
  949. * @param { (node: ObjectExpression, type: VueObjectType) => void } cb Callback function
  950. */
  951. executeOnVue(context, cb) {
  952. return compositingVisitors(
  953. this.executeOnVueComponent(context, cb),
  954. this.executeOnVueInstance(context, cb)
  955. )
  956. },
  957. /**
  958. * Define handlers to traverse the Vue Objects.
  959. * Some special events are available to visitor.
  960. *
  961. * - `onVueObjectEnter` ... Event when Vue Object is found.
  962. * - `onVueObjectExit` ... Event when Vue Object visit ends.
  963. * - `onSetupFunctionEnter` ... Event when setup function found.
  964. * - `onRenderFunctionEnter` ... Event when render function found.
  965. *
  966. * @param {RuleContext} context The ESLint rule context object.
  967. * @param {VueVisitor} visitor The visitor to traverse the Vue Objects.
  968. */
  969. defineVueVisitor(context, visitor) {
  970. /** @type {VueObjectData | null} */
  971. let vueStack = null
  972. /**
  973. * @param {string} key
  974. * @param {ESNode} node
  975. */
  976. function callVisitor(key, node) {
  977. if (visitor[key] && vueStack) {
  978. // @ts-expect-error
  979. visitor[key](node, vueStack)
  980. }
  981. }
  982. /** @type {NodeListener} */
  983. const vueVisitor = {}
  984. for (const key in visitor) {
  985. vueVisitor[key] = (node) => callVisitor(key, node)
  986. }
  987. /**
  988. * @param {ObjectExpression} node
  989. */
  990. vueVisitor.ObjectExpression = (node) => {
  991. const type = getVueObjectType(context, node)
  992. if (type) {
  993. vueStack = {
  994. node,
  995. type,
  996. parent: vueStack,
  997. get functional() {
  998. const functional = node.properties.find(
  999. /**
  1000. * @param {Property | SpreadElement} p
  1001. * @returns {p is Property}
  1002. */
  1003. (p) =>
  1004. p.type === 'Property' &&
  1005. getStaticPropertyName(p) === 'functional'
  1006. )
  1007. if (!functional) {
  1008. return false
  1009. }
  1010. if (
  1011. functional.value.type === 'Literal' &&
  1012. functional.value.value === false
  1013. ) {
  1014. return false
  1015. }
  1016. return true
  1017. }
  1018. }
  1019. callVisitor('onVueObjectEnter', node)
  1020. }
  1021. callVisitor('ObjectExpression', node)
  1022. }
  1023. vueVisitor['ObjectExpression:exit'] = (node) => {
  1024. callVisitor('ObjectExpression:exit', node)
  1025. if (vueStack && vueStack.node === node) {
  1026. callVisitor('onVueObjectExit', node)
  1027. vueStack = vueStack.parent
  1028. }
  1029. }
  1030. if (
  1031. visitor.onSetupFunctionEnter ||
  1032. visitor.onSetupFunctionExit ||
  1033. visitor.onRenderFunctionEnter
  1034. ) {
  1035. const setups = new Set()
  1036. /** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property } } node */
  1037. vueVisitor[
  1038. 'Property[value.type=/^(Arrow)?FunctionExpression$/] > :function'
  1039. ] = (node) => {
  1040. /** @type {Property} */
  1041. const prop = node.parent
  1042. if (vueStack && prop.parent === vueStack.node && prop.value === node) {
  1043. const name = getStaticPropertyName(prop)
  1044. if (name === 'setup') {
  1045. callVisitor('onSetupFunctionEnter', node)
  1046. setups.add(node)
  1047. } else if (name === 'render') {
  1048. callVisitor('onRenderFunctionEnter', node)
  1049. }
  1050. }
  1051. callVisitor(
  1052. 'Property[value.type=/^(Arrow)?FunctionExpression$/] > :function',
  1053. node
  1054. )
  1055. }
  1056. if (visitor.onSetupFunctionExit) {
  1057. /** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property } } node */
  1058. vueVisitor[
  1059. 'Property[value.type=/^(Arrow)?FunctionExpression$/] > :function:exit'
  1060. ] = (node) => {
  1061. if (setups.has(node)) {
  1062. callVisitor('onSetupFunctionExit', node)
  1063. setups.delete(node)
  1064. }
  1065. }
  1066. }
  1067. }
  1068. return vueVisitor
  1069. },
  1070. /**
  1071. * Define handlers to traverse the AST nodes in `<script setup>`.
  1072. * Some special events are available to visitor.
  1073. *
  1074. * - `onDefinePropsEnter` ... Event when defineProps is found.
  1075. * - `onDefinePropsExit` ... Event when defineProps visit ends.
  1076. * - `onDefineEmitsEnter` ... Event when defineEmits is found.
  1077. * - `onDefineEmitsExit` ... Event when defineEmits visit ends.
  1078. *
  1079. * @param {RuleContext} context The ESLint rule context object.
  1080. * @param {ScriptSetupVisitor} visitor The visitor to traverse the AST nodes.
  1081. */
  1082. defineScriptSetupVisitor(context, visitor) {
  1083. const scriptSetup = getScriptSetupElement(context)
  1084. if (scriptSetup == null) {
  1085. return {}
  1086. }
  1087. const scriptSetupRange = scriptSetup.range
  1088. /**
  1089. * @param {ESNode} node
  1090. */
  1091. function inScriptSetup(node) {
  1092. return (
  1093. scriptSetupRange[0] <= node.range[0] &&
  1094. node.range[1] <= scriptSetupRange[1]
  1095. )
  1096. }
  1097. /**
  1098. * @param {string} key
  1099. * @param {ESNode} node
  1100. * @param {any[]} args
  1101. */
  1102. function callVisitor(key, node, ...args) {
  1103. if (visitor[key]) {
  1104. if (inScriptSetup(node)) {
  1105. // @ts-expect-error
  1106. visitor[key](node, ...args)
  1107. }
  1108. }
  1109. }
  1110. /** @type {NodeListener} */
  1111. const scriptSetupVisitor = {}
  1112. for (const key in visitor) {
  1113. scriptSetupVisitor[key] = (node) => callVisitor(key, node)
  1114. }
  1115. const hasPropsEvent =
  1116. visitor.onDefinePropsEnter || visitor.onDefinePropsExit
  1117. const hasEmitsEvent =
  1118. visitor.onDefineEmitsEnter || visitor.onDefineEmitsExit
  1119. if (hasPropsEvent || hasEmitsEvent) {
  1120. /** @type {Expression | null} */
  1121. let candidateMacro = null
  1122. /** @param {VariableDeclarator|ExpressionStatement} node */
  1123. scriptSetupVisitor[
  1124. 'Program > VariableDeclaration > VariableDeclarator, Program > ExpressionStatement'
  1125. ] = (node) => {
  1126. if (!candidateMacro) {
  1127. candidateMacro =
  1128. node.type === 'VariableDeclarator' ? node.init : node.expression
  1129. }
  1130. }
  1131. /** @param {VariableDeclarator|ExpressionStatement} node */
  1132. scriptSetupVisitor[
  1133. 'Program > VariableDeclaration > VariableDeclarator, Program > ExpressionStatement:exit'
  1134. ] = (node) => {
  1135. if (
  1136. candidateMacro ===
  1137. (node.type === 'VariableDeclarator' ? node.init : node.expression)
  1138. ) {
  1139. candidateMacro = null
  1140. }
  1141. }
  1142. const definePropsMap = new Map()
  1143. const defineEmitsMap = new Map()
  1144. /**
  1145. * @param {CallExpression} node
  1146. */
  1147. scriptSetupVisitor.CallExpression = (node) => {
  1148. if (
  1149. candidateMacro &&
  1150. inScriptSetup(node) &&
  1151. node.callee.type === 'Identifier'
  1152. ) {
  1153. if (
  1154. hasPropsEvent &&
  1155. (candidateMacro === node ||
  1156. candidateMacro === getWithDefaults(node)) &&
  1157. node.callee.name === 'defineProps'
  1158. ) {
  1159. /** @type {ComponentProp[]} */
  1160. const props = getComponentPropsFromDefineProps(context, node)
  1161. callVisitor('onDefinePropsEnter', node, props)
  1162. definePropsMap.set(node, props)
  1163. } else if (
  1164. hasEmitsEvent &&
  1165. candidateMacro === node &&
  1166. node.callee.name === 'defineEmits'
  1167. ) {
  1168. /** @type {ComponentEmit[]} */
  1169. const emits = getComponentEmitsFromDefineEmits(context, node)
  1170. callVisitor('onDefineEmitsEnter', node, emits)
  1171. defineEmitsMap.set(node, emits)
  1172. }
  1173. }
  1174. callVisitor('CallExpression', node)
  1175. }
  1176. scriptSetupVisitor['CallExpression:exit'] = (node) => {
  1177. callVisitor('CallExpression:exit', node)
  1178. if (definePropsMap.has(node)) {
  1179. callVisitor('onDefinePropsExit', node, definePropsMap.get(node))
  1180. definePropsMap.delete(node)
  1181. }
  1182. if (defineEmitsMap.has(node)) {
  1183. callVisitor('onDefineEmitsExit', node, defineEmitsMap.get(node))
  1184. defineEmitsMap.delete(node)
  1185. }
  1186. }
  1187. }
  1188. return scriptSetupVisitor
  1189. },
  1190. /**
  1191. * Checks whether given defineProps call node has withDefaults.
  1192. * @param {CallExpression} node The node of defineProps
  1193. * @returns {node is CallExpression & { parent: CallExpression }}
  1194. */
  1195. hasWithDefaults,
  1196. /**
  1197. * Gets a map of the expressions defined in withDefaults.
  1198. * @param {CallExpression} node The node of defineProps
  1199. * @returns { { [key: string]: Expression | undefined } }
  1200. */
  1201. getWithDefaultsPropExpressions(node) {
  1202. const map = getWithDefaultsProps(node)
  1203. /** @type {Record<string, Expression | undefined>} */
  1204. const result = {}
  1205. for (const key of Object.keys(map)) {
  1206. const prop = map[key]
  1207. result[key] = prop && prop.value
  1208. }
  1209. return result
  1210. },
  1211. /**
  1212. * Gets a map of the property nodes defined in withDefaults.
  1213. * @param {CallExpression} node The node of defineProps
  1214. * @returns { { [key: string]: Property | undefined } }
  1215. */
  1216. getWithDefaultsProps,
  1217. getVueObjectType,
  1218. /**
  1219. * Get the Vue component definition type from given node
  1220. * Vue.component('xxx', {}) || component('xxx', {})
  1221. * @param {ObjectExpression} node Node to check
  1222. * @returns {'component' | 'mixin' | 'extend' | 'createApp' | 'defineComponent' | null}
  1223. */
  1224. getVueComponentDefinitionType,
  1225. /**
  1226. * Checks whether the given object is an SFC definition.
  1227. * @param {RuleContext} context The ESLint rule context object.
  1228. * @param {ObjectExpression} node Node to check
  1229. * @returns { boolean } `true`, the given object is an SFC definition.
  1230. */
  1231. isSFCObject,
  1232. compositingVisitors,
  1233. /**
  1234. * Check if current file is a Vue instance (new Vue) and call callback
  1235. * @param {RuleContext} context The ESLint rule context object.
  1236. * @param { (node: ObjectExpression, type: VueObjectType) => void } cb Callback function
  1237. */
  1238. executeOnVueInstance(context, cb) {
  1239. return {
  1240. /** @param {ObjectExpression} node */
  1241. 'ObjectExpression:exit'(node) {
  1242. const type = getVueObjectType(context, node)
  1243. if (!type || type !== 'instance') return
  1244. cb(node, type)
  1245. }
  1246. }
  1247. },
  1248. /**
  1249. * Check if current file is a Vue component and call callback
  1250. * @param {RuleContext} context The ESLint rule context object.
  1251. * @param { (node: ObjectExpression, type: VueObjectType) => void } cb Callback function
  1252. */
  1253. executeOnVueComponent(context, cb) {
  1254. return {
  1255. /** @param {ObjectExpression} node */
  1256. 'ObjectExpression:exit'(node) {
  1257. const type = getVueObjectType(context, node)
  1258. if (
  1259. !type ||
  1260. (type !== 'mark' && type !== 'export' && type !== 'definition')
  1261. )
  1262. return
  1263. cb(node, type)
  1264. }
  1265. }
  1266. },
  1267. /**
  1268. * Check call `Vue.component` and call callback.
  1269. * @param {RuleContext} _context The ESLint rule context object.
  1270. * @param { (node: CallExpression) => void } cb Callback function
  1271. */
  1272. executeOnCallVueComponent(_context, cb) {
  1273. return {
  1274. /** @param {Identifier & { parent: MemberExpression & { parent: CallExpression } } } node */
  1275. "CallExpression > MemberExpression > Identifier[name='component']": (
  1276. node
  1277. ) => {
  1278. const callExpr = node.parent.parent
  1279. const callee = callExpr.callee
  1280. if (callee.type === 'MemberExpression') {
  1281. const calleeObject = skipTSAsExpression(callee.object)
  1282. if (
  1283. calleeObject.type === 'Identifier' &&
  1284. // calleeObject.name === 'Vue' && // Any names can be used in Vue.js 3.x. e.g. app.component()
  1285. callee.property === node &&
  1286. callExpr.arguments.length >= 1
  1287. ) {
  1288. cb(callExpr)
  1289. }
  1290. }
  1291. }
  1292. }
  1293. },
  1294. /**
  1295. * Return generator with all properties
  1296. * @param {ObjectExpression} node Node to check
  1297. * @param {Set<GroupName>} groups Name of parent group
  1298. * @returns {IterableIterator<ComponentPropertyData>}
  1299. */
  1300. *iterateProperties(node, groups) {
  1301. for (const item of node.properties) {
  1302. if (item.type !== 'Property') {
  1303. continue
  1304. }
  1305. const name = /** @type {GroupName | null} */ (getStaticPropertyName(item))
  1306. if (!name || !groups.has(name)) continue
  1307. if (item.value.type === 'ArrayExpression') {
  1308. yield* this.iterateArrayExpression(item.value, name)
  1309. } else if (item.value.type === 'ObjectExpression') {
  1310. yield* this.iterateObjectExpression(item.value, name)
  1311. } else if (item.value.type === 'FunctionExpression') {
  1312. yield* this.iterateFunctionExpression(item.value, name)
  1313. } else if (item.value.type === 'ArrowFunctionExpression') {
  1314. yield* this.iterateArrowFunctionExpression(item.value, name)
  1315. }
  1316. }
  1317. },
  1318. /**
  1319. * Return generator with all elements inside ArrayExpression
  1320. * @param {ArrayExpression} node Node to check
  1321. * @param {GroupName} groupName Name of parent group
  1322. * @returns {IterableIterator<ComponentArrayPropertyData>}
  1323. */
  1324. *iterateArrayExpression(node, groupName) {
  1325. for (const item of node.elements) {
  1326. if (
  1327. item &&
  1328. (item.type === 'Literal' || item.type === 'TemplateLiteral')
  1329. ) {
  1330. const name = getStringLiteralValue(item)
  1331. if (name) {
  1332. yield { type: 'array', name, groupName, node: item }
  1333. }
  1334. }
  1335. }
  1336. },
  1337. /**
  1338. * Return generator with all elements inside ObjectExpression
  1339. * @param {ObjectExpression} node Node to check
  1340. * @param {GroupName} groupName Name of parent group
  1341. * @returns {IterableIterator<ComponentObjectPropertyData>}
  1342. */
  1343. *iterateObjectExpression(node, groupName) {
  1344. /** @type {Set<Property> | undefined} */
  1345. let usedGetter
  1346. for (const item of node.properties) {
  1347. if (item.type === 'Property') {
  1348. const key = item.key
  1349. if (
  1350. key.type === 'Identifier' ||
  1351. key.type === 'Literal' ||
  1352. key.type === 'TemplateLiteral'
  1353. ) {
  1354. const name = getStaticPropertyName(item)
  1355. if (name) {
  1356. if (item.kind === 'set') {
  1357. // find getter pair
  1358. if (
  1359. node.properties.some((item2) => {
  1360. if (item2.type === 'Property' && item2.kind === 'get') {
  1361. if (!usedGetter) {
  1362. usedGetter = new Set()
  1363. }
  1364. if (usedGetter.has(item2)) {
  1365. return false
  1366. }
  1367. const getterName = getStaticPropertyName(item2)
  1368. if (getterName === name) {
  1369. usedGetter.add(item2)
  1370. return true
  1371. }
  1372. }
  1373. return false
  1374. })
  1375. ) {
  1376. // has getter pair
  1377. continue
  1378. }
  1379. }
  1380. yield {
  1381. type: 'object',
  1382. name,
  1383. groupName,
  1384. node: key,
  1385. property: item
  1386. }
  1387. }
  1388. }
  1389. }
  1390. }
  1391. },
  1392. /**
  1393. * Return generator with all elements inside FunctionExpression
  1394. * @param {FunctionExpression} node Node to check
  1395. * @param {GroupName} groupName Name of parent group
  1396. * @returns {IterableIterator<ComponentObjectPropertyData>}
  1397. */
  1398. *iterateFunctionExpression(node, groupName) {
  1399. if (node.body.type === 'BlockStatement') {
  1400. for (const item of node.body.body) {
  1401. if (
  1402. item.type === 'ReturnStatement' &&
  1403. item.argument &&
  1404. item.argument.type === 'ObjectExpression'
  1405. ) {
  1406. yield* this.iterateObjectExpression(item.argument, groupName)
  1407. }
  1408. }
  1409. }
  1410. },
  1411. /**
  1412. * Return generator with all elements inside ArrowFunctionExpression
  1413. * @param {ArrowFunctionExpression} node Node to check
  1414. * @param {GroupName} groupName Name of parent group
  1415. * @returns {IterableIterator<ComponentObjectPropertyData>}
  1416. */
  1417. *iterateArrowFunctionExpression(node, groupName) {
  1418. const body = node.body
  1419. if (body.type === 'BlockStatement') {
  1420. for (const item of body.body) {
  1421. if (
  1422. item.type === 'ReturnStatement' &&
  1423. item.argument &&
  1424. item.argument.type === 'ObjectExpression'
  1425. ) {
  1426. yield* this.iterateObjectExpression(item.argument, groupName)
  1427. }
  1428. }
  1429. } else if (body.type === 'ObjectExpression') {
  1430. yield* this.iterateObjectExpression(body, groupName)
  1431. }
  1432. },
  1433. /**
  1434. * Find all functions which do not always return values
  1435. * @param {boolean} treatUndefinedAsUnspecified
  1436. * @param { (node: ArrowFunctionExpression | FunctionExpression | FunctionDeclaration) => void } cb Callback function
  1437. * @returns {RuleListener}
  1438. */
  1439. executeOnFunctionsWithoutReturn(treatUndefinedAsUnspecified, cb) {
  1440. /**
  1441. * @typedef {object} FuncInfo
  1442. * @property {FuncInfo | null} funcInfo
  1443. * @property {CodePath} codePath
  1444. * @property {boolean} hasReturn
  1445. * @property {boolean} hasReturnValue
  1446. * @property {ArrowFunctionExpression | FunctionExpression | FunctionDeclaration} node
  1447. */
  1448. /** @type {FuncInfo | null} */
  1449. let funcInfo = null
  1450. /** @param {CodePathSegment} segment */
  1451. function isReachable(segment) {
  1452. return segment.reachable
  1453. }
  1454. function isValidReturn() {
  1455. if (!funcInfo) {
  1456. return true
  1457. }
  1458. if (
  1459. funcInfo.codePath &&
  1460. funcInfo.codePath.currentSegments.some(isReachable)
  1461. ) {
  1462. return false
  1463. }
  1464. return !treatUndefinedAsUnspecified || funcInfo.hasReturnValue
  1465. }
  1466. return {
  1467. /**
  1468. * @param {CodePath} codePath
  1469. * @param {ESNode} node
  1470. */
  1471. onCodePathStart(codePath, node) {
  1472. if (
  1473. node.type === 'ArrowFunctionExpression' ||
  1474. node.type === 'FunctionExpression' ||
  1475. node.type === 'FunctionDeclaration'
  1476. ) {
  1477. funcInfo = {
  1478. codePath,
  1479. funcInfo,
  1480. hasReturn: false,
  1481. hasReturnValue: false,
  1482. node
  1483. }
  1484. }
  1485. },
  1486. onCodePathEnd() {
  1487. funcInfo = funcInfo && funcInfo.funcInfo
  1488. },
  1489. /** @param {ReturnStatement} node */
  1490. ReturnStatement(node) {
  1491. if (funcInfo) {
  1492. funcInfo.hasReturn = true
  1493. funcInfo.hasReturnValue = Boolean(node.argument)
  1494. }
  1495. },
  1496. /** @param {ArrowFunctionExpression} node */
  1497. 'ArrowFunctionExpression:exit'(node) {
  1498. if (funcInfo && !isValidReturn() && !node.expression) {
  1499. cb(funcInfo.node)
  1500. }
  1501. },
  1502. 'FunctionExpression:exit'() {
  1503. if (funcInfo && !isValidReturn()) {
  1504. cb(funcInfo.node)
  1505. }
  1506. }
  1507. }
  1508. },
  1509. /**
  1510. * Check whether the component is declared in a single line or not.
  1511. * @param {ASTNode} node
  1512. * @returns {boolean}
  1513. */
  1514. isSingleLine(node) {
  1515. return node.loc.start.line === node.loc.end.line
  1516. },
  1517. /**
  1518. * Check whether the templateBody of the program has invalid EOF or not.
  1519. * @param {Program} node The program node to check.
  1520. * @returns {boolean} `true` if it has invalid EOF.
  1521. */
  1522. hasInvalidEOF(node) {
  1523. const body = node.templateBody
  1524. if (body == null || body.errors == null) {
  1525. return false
  1526. }
  1527. return body.errors.some(
  1528. (error) => typeof error.code === 'string' && error.code.startsWith('eof-')
  1529. )
  1530. },
  1531. /**
  1532. * Get the chaining nodes of MemberExpression.
  1533. *
  1534. * @param {ESNode} node The node to parse
  1535. * @return {[ESNode, ...MemberExpression[]]} The chaining nodes
  1536. */
  1537. getMemberChaining(node) {
  1538. /** @type {MemberExpression[]} */
  1539. const nodes = []
  1540. let n = skipChainExpression(node)
  1541. while (n.type === 'MemberExpression') {
  1542. nodes.push(n)
  1543. n = skipChainExpression(n.object)
  1544. }
  1545. return [n, ...nodes.reverse()]
  1546. },
  1547. /**
  1548. * return two string editdistance
  1549. * @param {string} a string a to compare
  1550. * @param {string} b string b to compare
  1551. * @returns {number}
  1552. */
  1553. editDistance(a, b) {
  1554. if (a === b) {
  1555. return 0
  1556. }
  1557. const alen = a.length
  1558. const blen = b.length
  1559. const dp = Array.from({ length: alen + 1 }).map((_) =>
  1560. Array.from({ length: blen + 1 }).fill(0)
  1561. )
  1562. for (let i = 0; i <= alen; i++) {
  1563. dp[i][0] = i
  1564. }
  1565. for (let j = 0; j <= blen; j++) {
  1566. dp[0][j] = j
  1567. }
  1568. for (let i = 1; i <= alen; i++) {
  1569. for (let j = 1; j <= blen; j++) {
  1570. if (a[i - 1] === b[j - 1]) {
  1571. dp[i][j] = dp[i - 1][j - 1]
  1572. } else {
  1573. dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
  1574. }
  1575. }
  1576. }
  1577. return dp[alen][blen]
  1578. },
  1579. /**
  1580. * Checks whether the target node is within the given range.
  1581. * @param { [number, number] } range
  1582. * @param {ASTNode | Token} target
  1583. */
  1584. inRange(range, target) {
  1585. return range[0] <= target.range[0] && target.range[1] <= range[1]
  1586. },
  1587. /**
  1588. * Checks whether the given node is Property.
  1589. */
  1590. isProperty,
  1591. /**
  1592. * Checks whether the given node is AssignmentProperty.
  1593. */
  1594. isAssignmentProperty,
  1595. /**
  1596. * Checks whether the given node is VElement.
  1597. */
  1598. isVElement,
  1599. /**
  1600. * Finds the property with the given name from the given ObjectExpression node.
  1601. */
  1602. findProperty,
  1603. /**
  1604. * Finds the assignment property with the given name from the given ObjectPattern node.
  1605. */
  1606. findAssignmentProperty,
  1607. /**
  1608. * Checks if the given node is a property value.
  1609. * @param {Property} prop
  1610. * @param {Expression} node
  1611. */
  1612. isPropertyChain,
  1613. /**
  1614. * Retrieve `TSAsExpression#expression` value if the given node a `TSAsExpression` node. Otherwise, pass through it.
  1615. */
  1616. skipTSAsExpression,
  1617. /**
  1618. * Retrieve `AssignmentPattern#left` value if the given node a `AssignmentPattern` node. Otherwise, pass through it.
  1619. */
  1620. skipDefaultParamValue,
  1621. /**
  1622. * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
  1623. */
  1624. skipChainExpression,
  1625. /**
  1626. * Checks whether the given node is in a type annotation.
  1627. */
  1628. withinTypeNode,
  1629. findVariableByIdentifier,
  1630. getScope,
  1631. /**
  1632. * Checks whether the given node is in export default.
  1633. * @param {ASTNode} node
  1634. * @returns {boolean}
  1635. */
  1636. isInExportDefault,
  1637. /**
  1638. * Check whether the given node is `this` or variable that stores `this`.
  1639. * @param {ESNode} node The node to check
  1640. * @param {RuleContext} context The rule context to use parser services.
  1641. * @returns {boolean} `true` if the given node is `this`.
  1642. */
  1643. isThis(node, context) {
  1644. if (node.type === 'ThisExpression') {
  1645. return true
  1646. }
  1647. if (node.type !== 'Identifier') {
  1648. return false
  1649. }
  1650. const parent = node.parent
  1651. if (parent.type === 'MemberExpression') {
  1652. if (parent.property === node) {
  1653. return false
  1654. }
  1655. } else if (parent.type === 'Property') {
  1656. if (parent.key === node && !parent.computed) {
  1657. return false
  1658. }
  1659. }
  1660. const variable = findVariable(context.getScope(), node)
  1661. if (variable != null && variable.defs.length === 1) {
  1662. const def = variable.defs[0]
  1663. if (
  1664. def.type === 'Variable' &&
  1665. def.parent.kind === 'const' &&
  1666. def.node.id.type === 'Identifier'
  1667. ) {
  1668. return Boolean(
  1669. def.node && def.node.init && def.node.init.type === 'ThisExpression'
  1670. )
  1671. }
  1672. }
  1673. return false
  1674. },
  1675. /**
  1676. * @param {MemberExpression|Identifier} props
  1677. * @returns { { kind: 'assignment' | 'update' | 'call' , node: ESNode, pathNodes: MemberExpression[] } | null }
  1678. */
  1679. findMutating(props) {
  1680. /** @type {MemberExpression[]} */
  1681. const pathNodes = []
  1682. /** @type {MemberExpression | Identifier | ChainExpression} */
  1683. let node = props
  1684. let target = node.parent
  1685. while (true) {
  1686. if (target.type === 'AssignmentExpression') {
  1687. if (target.left === node) {
  1688. // this.xxx <=|+=|-=>
  1689. return {
  1690. kind: 'assignment',
  1691. node: target,
  1692. pathNodes
  1693. }
  1694. }
  1695. } else if (target.type === 'UpdateExpression') {
  1696. // this.xxx <++|-->
  1697. return {
  1698. kind: 'update',
  1699. node: target,
  1700. pathNodes
  1701. }
  1702. } else if (target.type === 'CallExpression') {
  1703. if (pathNodes.length > 0 && target.callee === node) {
  1704. const mem = pathNodes[pathNodes.length - 1]
  1705. const callName = getStaticPropertyName(mem)
  1706. if (
  1707. callName &&
  1708. /^(?:push|pop|shift|unshift|reverse|splice|sort|copyWithin|fill)$/u.exec(
  1709. callName
  1710. )
  1711. ) {
  1712. // this.xxx.push()
  1713. pathNodes.pop()
  1714. return {
  1715. kind: 'call',
  1716. node: target,
  1717. pathNodes
  1718. }
  1719. }
  1720. }
  1721. } else if (target.type === 'MemberExpression') {
  1722. if (target.object === node) {
  1723. pathNodes.push(target)
  1724. node = target
  1725. target = target.parent
  1726. continue // loop
  1727. }
  1728. } else if (target.type === 'ChainExpression') {
  1729. node = target
  1730. target = target.parent
  1731. continue // loop
  1732. }
  1733. return null
  1734. }
  1735. },
  1736. /**
  1737. * Return generator with the all handler nodes defined in the given watcher property.
  1738. * @param {Property|Expression} property
  1739. * @returns {IterableIterator<Expression>}
  1740. */
  1741. iterateWatchHandlerValues,
  1742. /**
  1743. * Wraps composition API trace map in both 'vue' and '@vue/composition-api' imports
  1744. * @param {import('eslint-utils').TYPES.TraceMap} map
  1745. */
  1746. createCompositionApiTraceMap: (map) => ({
  1747. vue: map,
  1748. '@vue/composition-api': map
  1749. }),
  1750. /**
  1751. * Checks whether or not the tokens of two given nodes are same.
  1752. * @param {ASTNode} left A node 1 to compare.
  1753. * @param {ASTNode} right A node 2 to compare.
  1754. * @param {ParserServices.TokenStore | SourceCode} sourceCode The ESLint source code object.
  1755. * @returns {boolean} the source code for the given node.
  1756. */
  1757. equalTokens(left, right, sourceCode) {
  1758. const tokensL = sourceCode.getTokens(left)
  1759. const tokensR = sourceCode.getTokens(right)
  1760. if (tokensL.length !== tokensR.length) {
  1761. return false
  1762. }
  1763. for (let i = 0; i < tokensL.length; ++i) {
  1764. if (
  1765. tokensL[i].type !== tokensR[i].type ||
  1766. tokensL[i].value !== tokensR[i].value
  1767. ) {
  1768. return false
  1769. }
  1770. }
  1771. return true
  1772. }
  1773. }
  1774. // ------------------------------------------------------------------------------
  1775. // Standard Helpers
  1776. // ------------------------------------------------------------------------------
  1777. /**
  1778. * Checks whether the given value is defined.
  1779. * @template T
  1780. * @param {T | null | undefined} v
  1781. * @returns {v is T}
  1782. */
  1783. function isDef(v) {
  1784. return v != null
  1785. }
  1786. /**
  1787. * Flattens arrays, objects and iterable objects.
  1788. * @template T
  1789. * @param {T | Iterable<T> | null | undefined} v
  1790. * @returns {T[]}
  1791. */
  1792. function flatten(v) {
  1793. /** @type {T[]} */
  1794. const result = []
  1795. if (v) {
  1796. if (isIterable(v)) {
  1797. result.push(...v)
  1798. } else {
  1799. result.push(v)
  1800. }
  1801. }
  1802. return result
  1803. }
  1804. /**
  1805. * @param {*} v
  1806. * @returns {v is Iterable<any>}
  1807. */
  1808. function isIterable(v) {
  1809. return v && Symbol.iterator in v
  1810. }
  1811. // ------------------------------------------------------------------------------
  1812. // Nodejs Helpers
  1813. // ------------------------------------------------------------------------------
  1814. /**
  1815. * @param {String} filename
  1816. */
  1817. function createRequire(filename) {
  1818. const Module = require('module')
  1819. const moduleCreateRequire =
  1820. // Added in v12.2.0
  1821. Module.createRequire ||
  1822. // Added in v10.12.0, but deprecated in v12.2.0.
  1823. Module.createRequireFromPath ||
  1824. // Polyfill - This is not executed on the tests on node@>=10.
  1825. /**
  1826. * @param {string} filename
  1827. */
  1828. function (filename) {
  1829. const mod = new Module(filename)
  1830. mod.filename = filename
  1831. // @ts-ignore
  1832. mod.paths = Module._nodeModulePaths(path.dirname(filename))
  1833. // @ts-ignore
  1834. mod._compile('module.exports = require;', filename)
  1835. return mod.exports
  1836. }
  1837. return moduleCreateRequire(filename)
  1838. }
  1839. // ------------------------------------------------------------------------------
  1840. // Rule Helpers
  1841. // ------------------------------------------------------------------------------
  1842. /**
  1843. * Register the given visitor to parser services.
  1844. * If the parser service of `vue-eslint-parser` was not found,
  1845. * this generates a warning.
  1846. *
  1847. * @param {RuleContext} context The rule context to use parser services.
  1848. * @param {TemplateListener} templateBodyVisitor The visitor to traverse the template body.
  1849. * @param {RuleListener} [scriptVisitor] The visitor to traverse the script.
  1850. * @param { { templateBodyTriggerSelector: "Program" | "Program:exit" } } [options] The options.
  1851. * @returns {RuleListener} The merged visitor.
  1852. */
  1853. function defineTemplateBodyVisitor(
  1854. context,
  1855. templateBodyVisitor,
  1856. scriptVisitor,
  1857. options
  1858. ) {
  1859. if (context.parserServices.defineTemplateBodyVisitor == null) {
  1860. const filename = context.getFilename()
  1861. if (path.extname(filename) === '.vue') {
  1862. context.report({
  1863. loc: { line: 1, column: 0 },
  1864. message:
  1865. 'Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error.'
  1866. })
  1867. }
  1868. return {}
  1869. }
  1870. return context.parserServices.defineTemplateBodyVisitor(
  1871. templateBodyVisitor,
  1872. scriptVisitor,
  1873. options
  1874. )
  1875. }
  1876. /**
  1877. * Register the given visitor to parser services.
  1878. * If the parser service of `vue-eslint-parser` was not found,
  1879. * this generates a warning.
  1880. *
  1881. * @param {RuleContext} context The rule context to use parser services.
  1882. * @param {TemplateListener} documentVisitor The visitor to traverse the document.
  1883. * @param { { triggerSelector: "Program" | "Program:exit" } } [options] The options.
  1884. * @returns {RuleListener} The merged visitor.
  1885. */
  1886. function defineDocumentVisitor(context, documentVisitor, options) {
  1887. if (context.parserServices.defineDocumentVisitor == null) {
  1888. const filename = context.getFilename()
  1889. if (path.extname(filename) === '.vue') {
  1890. context.report({
  1891. loc: { line: 1, column: 0 },
  1892. message:
  1893. 'Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error.'
  1894. })
  1895. }
  1896. return {}
  1897. }
  1898. return context.parserServices.defineDocumentVisitor(documentVisitor, options)
  1899. }
  1900. /**
  1901. * @template T
  1902. * @param {T} visitor
  1903. * @param {...(TemplateListener | RuleListener | NodeListener)} visitors
  1904. * @returns {T}
  1905. */
  1906. function compositingVisitors(visitor, ...visitors) {
  1907. for (const v of visitors) {
  1908. for (const key in v) {
  1909. // @ts-expect-error
  1910. if (visitor[key]) {
  1911. // @ts-expect-error
  1912. const o = visitor[key]
  1913. // @ts-expect-error
  1914. visitor[key] = (...args) => {
  1915. o(...args)
  1916. // @ts-expect-error
  1917. v[key](...args)
  1918. }
  1919. } else {
  1920. // @ts-expect-error
  1921. visitor[key] = v[key]
  1922. }
  1923. }
  1924. }
  1925. return visitor
  1926. }
  1927. // ------------------------------------------------------------------------------
  1928. // AST Helpers
  1929. // ------------------------------------------------------------------------------
  1930. /**
  1931. * Find the variable of a given identifier.
  1932. * @param {RuleContext} context The rule context
  1933. * @param {Identifier} node The variable name to find.
  1934. * @returns {Variable|null} The found variable or null.
  1935. */
  1936. function findVariableByIdentifier(context, node) {
  1937. return findVariable(getScope(context, node), node)
  1938. }
  1939. /**
  1940. * Gets the scope for the current node
  1941. * @param {RuleContext} context The rule context
  1942. * @param {ESNode} currentNode The node to get the scope of
  1943. * @returns { import('eslint').Scope.Scope } The scope information for this node
  1944. */
  1945. function getScope(context, currentNode) {
  1946. // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope.
  1947. const inner = currentNode.type !== 'Program'
  1948. const scopeManager = context.getSourceCode().scopeManager
  1949. /** @type {ESNode | null} */
  1950. let node = currentNode
  1951. for (; node; node = /** @type {ESNode | null} */ (node.parent)) {
  1952. const scope = scopeManager.acquire(node, inner)
  1953. if (scope) {
  1954. if (scope.type === 'function-expression-name') {
  1955. return scope.childScopes[0]
  1956. }
  1957. return scope
  1958. }
  1959. }
  1960. return scopeManager.scopes[0]
  1961. }
  1962. /**
  1963. * Finds the property with the given name from the given ObjectExpression node.
  1964. * @param {ObjectExpression} node
  1965. * @param {string} name
  1966. * @param { (p: Property) => boolean } [filter]
  1967. * @returns { (Property) | null}
  1968. */
  1969. function findProperty(node, name, filter) {
  1970. const predicate = filter
  1971. ? /**
  1972. * @param {Property | SpreadElement} prop
  1973. * @returns {prop is Property}
  1974. */
  1975. (prop) =>
  1976. isProperty(prop) && getStaticPropertyName(prop) === name && filter(prop)
  1977. : /**
  1978. * @param {Property | SpreadElement} prop
  1979. * @returns {prop is Property}
  1980. */
  1981. (prop) => isProperty(prop) && getStaticPropertyName(prop) === name
  1982. return node.properties.find(predicate) || null
  1983. }
  1984. /**
  1985. * Finds the assignment property with the given name from the given ObjectPattern node.
  1986. * @param {ObjectPattern} node
  1987. * @param {string} name
  1988. * @param { (p: AssignmentProperty) => boolean } [filter]
  1989. * @returns { (AssignmentProperty) | null}
  1990. */
  1991. function findAssignmentProperty(node, name, filter) {
  1992. const predicate = filter
  1993. ? /**
  1994. * @param {AssignmentProperty | RestElement} prop
  1995. * @returns {prop is AssignmentProperty}
  1996. */
  1997. (prop) =>
  1998. isAssignmentProperty(prop) &&
  1999. getStaticPropertyName(prop) === name &&
  2000. filter(prop)
  2001. : /**
  2002. * @param {AssignmentProperty | RestElement} prop
  2003. * @returns {prop is AssignmentProperty}
  2004. */
  2005. (prop) =>
  2006. isAssignmentProperty(prop) && getStaticPropertyName(prop) === name
  2007. return node.properties.find(predicate) || null
  2008. }
  2009. /**
  2010. * Checks whether the given node is Property.
  2011. * @param {Property | SpreadElement | AssignmentProperty} node
  2012. * @returns {node is Property}
  2013. */
  2014. function isProperty(node) {
  2015. if (node.type !== 'Property') {
  2016. return false
  2017. }
  2018. return !node.parent || node.parent.type === 'ObjectExpression'
  2019. }
  2020. /**
  2021. * Checks whether the given node is AssignmentProperty.
  2022. * @param {AssignmentProperty | RestElement} node
  2023. * @returns {node is AssignmentProperty}
  2024. */
  2025. function isAssignmentProperty(node) {
  2026. return node.type === 'Property'
  2027. }
  2028. /**
  2029. * Checks whether the given node is VElement.
  2030. * @param {VElement | VExpressionContainer | VText} node
  2031. * @returns {node is VElement}
  2032. */
  2033. function isVElement(node) {
  2034. return node.type === 'VElement'
  2035. }
  2036. /**
  2037. * Checks whether the given node is in export default.
  2038. * @param {ASTNode} node
  2039. * @returns {boolean}
  2040. */
  2041. function isInExportDefault(node) {
  2042. /** @type {ASTNode | null} */
  2043. let parent = node.parent
  2044. while (parent) {
  2045. if (parent.type === 'ExportDefaultDeclaration') {
  2046. return true
  2047. }
  2048. parent = parent.parent
  2049. }
  2050. return false
  2051. }
  2052. /**
  2053. * Retrieve `TSAsExpression#expression` value if the given node a `TSAsExpression` node. Otherwise, pass through it.
  2054. * @template T Node type
  2055. * @param {T | TSAsExpression} node The node to address.
  2056. * @returns {T} The `TSAsExpression#expression` value if the node is a `TSAsExpression` node. Otherwise, the node.
  2057. */
  2058. function skipTSAsExpression(node) {
  2059. if (!node) {
  2060. return node
  2061. }
  2062. // @ts-expect-error
  2063. if (node.type === 'TSAsExpression') {
  2064. // @ts-expect-error
  2065. return skipTSAsExpression(node.expression)
  2066. }
  2067. // @ts-expect-error
  2068. return node
  2069. }
  2070. /**
  2071. * Gets the parent node of the given node. This method returns a value ignoring `X as F`.
  2072. * @param {Expression} node
  2073. * @returns {ASTNode}
  2074. */
  2075. function getParent(node) {
  2076. let parent = node.parent
  2077. while (parent.type === 'TSAsExpression') {
  2078. parent = parent.parent
  2079. }
  2080. return parent
  2081. }
  2082. /**
  2083. * Checks if the given node is a property value.
  2084. * @param {Property} prop
  2085. * @param {Expression} node
  2086. */
  2087. function isPropertyChain(prop, node) {
  2088. let value = node
  2089. while (value.parent.type === 'TSAsExpression') {
  2090. value = value.parent
  2091. }
  2092. return prop === value.parent && prop.value === value
  2093. }
  2094. /**
  2095. * Retrieve `AssignmentPattern#left` value if the given node a `AssignmentPattern` node. Otherwise, pass through it.
  2096. * @template T Node type
  2097. * @param {T | AssignmentPattern} node The node to address.
  2098. * @return {T} The `AssignmentPattern#left` value if the node is a `AssignmentPattern` node. Otherwise, the node.
  2099. */
  2100. function skipDefaultParamValue(node) {
  2101. if (!node) {
  2102. return node
  2103. }
  2104. // @ts-expect-error
  2105. if (node.type === 'AssignmentPattern') {
  2106. // @ts-expect-error
  2107. return skipDefaultParamValue(node.left)
  2108. }
  2109. // @ts-expect-error
  2110. return node
  2111. }
  2112. /**
  2113. * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
  2114. * @template T Node type
  2115. * @param {T | ChainExpression} node The node to address.
  2116. * @returns {T} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node.
  2117. */
  2118. function skipChainExpression(node) {
  2119. if (!node) {
  2120. return node
  2121. }
  2122. // @ts-expect-error
  2123. if (node.type === 'ChainExpression') {
  2124. // @ts-expect-error
  2125. return skipChainExpression(node.expression)
  2126. }
  2127. // @ts-expect-error
  2128. return node
  2129. }
  2130. /**
  2131. * Checks whether the given node is in a type annotation.
  2132. * @param {ESNode} node
  2133. * @returns {boolean}
  2134. */
  2135. function withinTypeNode(node) {
  2136. /** @type {ASTNode | null} */
  2137. let target = node
  2138. while (target) {
  2139. if (isTypeNode(target)) {
  2140. return true
  2141. }
  2142. target = target.parent
  2143. }
  2144. return false
  2145. }
  2146. /**
  2147. * Gets the property name of a given node.
  2148. * @param {Property|AssignmentProperty|MethodDefinition|MemberExpression} node - The node to get.
  2149. * @return {string|null} The property name if static. Otherwise, null.
  2150. */
  2151. function getStaticPropertyName(node) {
  2152. if (node.type === 'Property' || node.type === 'MethodDefinition') {
  2153. if (!node.computed) {
  2154. const key = node.key
  2155. if (key.type === 'Identifier') {
  2156. return key.name
  2157. }
  2158. }
  2159. const key = node.key
  2160. // @ts-expect-error
  2161. return getStringLiteralValue(key)
  2162. } else if (node.type === 'MemberExpression') {
  2163. if (!node.computed) {
  2164. const property = node.property
  2165. if (property.type === 'Identifier') {
  2166. return property.name
  2167. }
  2168. return null
  2169. }
  2170. const property = node.property
  2171. // @ts-expect-error
  2172. return getStringLiteralValue(property)
  2173. }
  2174. return null
  2175. }
  2176. /**
  2177. * Gets the string of a given node.
  2178. * @param {Literal|TemplateLiteral} node - The node to get.
  2179. * @param {boolean} [stringOnly]
  2180. * @return {string|null} The string if static. Otherwise, null.
  2181. */
  2182. function getStringLiteralValue(node, stringOnly) {
  2183. if (node.type === 'Literal') {
  2184. if (node.value == null) {
  2185. if (!stringOnly && node.bigint != null) {
  2186. return node.bigint
  2187. }
  2188. return null
  2189. }
  2190. if (typeof node.value === 'string') {
  2191. return node.value
  2192. }
  2193. if (!stringOnly) {
  2194. return String(node.value)
  2195. }
  2196. return null
  2197. }
  2198. if (node.type === 'TemplateLiteral') {
  2199. if (node.expressions.length === 0 && node.quasis.length === 1) {
  2200. return node.quasis[0].value.cooked
  2201. }
  2202. }
  2203. return null
  2204. }
  2205. /**
  2206. * Gets the VExpressionContainer of a given node.
  2207. * @param {ASTNode} node - The node to get.
  2208. * @return {VExpressionContainer|null}
  2209. */
  2210. function getVExpressionContainer(node) {
  2211. /** @type {ASTNode | null} */
  2212. let n = node
  2213. while (n && n.type !== 'VExpressionContainer') {
  2214. n = n.parent
  2215. }
  2216. return n
  2217. }
  2218. // ------------------------------------------------------------------------------
  2219. // Vue Helpers
  2220. // ------------------------------------------------------------------------------
  2221. /**
  2222. * @param {string} path
  2223. */
  2224. function isVueFile(path) {
  2225. return path.endsWith('.vue') || path.endsWith('.jsx')
  2226. }
  2227. /**
  2228. * Checks whether the current file is uses `<script setup>`
  2229. * @param {RuleContext} context The ESLint rule context object.
  2230. */
  2231. function isScriptSetup(context) {
  2232. return Boolean(getScriptSetupElement(context))
  2233. }
  2234. /**
  2235. * Gets the element of `<script setup>`
  2236. * @param {RuleContext} context The ESLint rule context object.
  2237. * @returns {VElement | null} the element of `<script setup>`
  2238. */
  2239. function getScriptSetupElement(context) {
  2240. const df =
  2241. context.parserServices.getDocumentFragment &&
  2242. context.parserServices.getDocumentFragment()
  2243. if (!df) {
  2244. return null
  2245. }
  2246. const scripts = df.children
  2247. .filter(isVElement)
  2248. .filter((e) => e.name === 'script')
  2249. if (scripts.length === 2) {
  2250. return scripts.find((e) => hasAttribute(e, 'setup')) || null
  2251. } else {
  2252. const script = scripts[0]
  2253. if (script && hasAttribute(script, 'setup')) {
  2254. return script
  2255. }
  2256. }
  2257. return null
  2258. }
  2259. /**
  2260. * Check whether the given node is a Vue component based
  2261. * on the filename and default export type
  2262. * export default {} in .vue || .jsx
  2263. * @param {ESNode} node Node to check
  2264. * @param {string} path File name with extension
  2265. * @returns {boolean}
  2266. */
  2267. function isVueComponentFile(node, path) {
  2268. return (
  2269. isVueFile(path) &&
  2270. node.type === 'ExportDefaultDeclaration' &&
  2271. node.declaration.type === 'ObjectExpression'
  2272. )
  2273. }
  2274. /**
  2275. * Get the Vue component definition type from given node
  2276. * Vue.component('xxx', {}) || component('xxx', {})
  2277. * @param {ObjectExpression} node Node to check
  2278. * @returns {'component' | 'mixin' | 'extend' | 'createApp' | 'defineComponent' | null}
  2279. */
  2280. function getVueComponentDefinitionType(node) {
  2281. const parent = getParent(node)
  2282. if (parent.type === 'CallExpression') {
  2283. const callee = parent.callee
  2284. if (callee.type === 'MemberExpression') {
  2285. const calleeObject = skipTSAsExpression(callee.object)
  2286. if (calleeObject.type === 'Identifier') {
  2287. const propName = getStaticPropertyName(callee)
  2288. if (calleeObject.name === 'Vue') {
  2289. // for Vue.js 2.x
  2290. // Vue.component('xxx', {}) || Vue.mixin({}) || Vue.extend('xxx', {})
  2291. const maybeFullVueComponentForVue2 =
  2292. propName && isObjectArgument(parent)
  2293. return maybeFullVueComponentForVue2 &&
  2294. (propName === 'component' ||
  2295. propName === 'mixin' ||
  2296. propName === 'extend')
  2297. ? propName
  2298. : null
  2299. }
  2300. // for Vue.js 3.x
  2301. // app.component('xxx', {}) || app.mixin({})
  2302. const maybeFullVueComponent = propName && isObjectArgument(parent)
  2303. return maybeFullVueComponent &&
  2304. (propName === 'component' || propName === 'mixin')
  2305. ? propName
  2306. : null
  2307. }
  2308. }
  2309. if (callee.type === 'Identifier') {
  2310. if (callee.name === 'component') {
  2311. // for Vue.js 2.x
  2312. // component('xxx', {})
  2313. const isDestructedVueComponent = isObjectArgument(parent)
  2314. return isDestructedVueComponent ? 'component' : null
  2315. }
  2316. if (callee.name === 'createApp') {
  2317. // for Vue.js 3.x
  2318. // createApp({})
  2319. const isAppVueComponent = isObjectArgument(parent)
  2320. return isAppVueComponent ? 'createApp' : null
  2321. }
  2322. if (callee.name === 'defineComponent') {
  2323. // for Vue.js 3.x
  2324. // defineComponent({})
  2325. const isDestructedVueComponent = isObjectArgument(parent)
  2326. return isDestructedVueComponent ? 'defineComponent' : null
  2327. }
  2328. }
  2329. }
  2330. return null
  2331. /** @param {CallExpression} node */
  2332. function isObjectArgument(node) {
  2333. return (
  2334. node.arguments.length > 0 &&
  2335. skipTSAsExpression(node.arguments.slice(-1)[0]).type ===
  2336. 'ObjectExpression'
  2337. )
  2338. }
  2339. }
  2340. /**
  2341. * Check whether given node is new Vue instance
  2342. * new Vue({})
  2343. * @param {NewExpression} node Node to check
  2344. * @returns {boolean}
  2345. */
  2346. function isVueInstance(node) {
  2347. const callee = node.callee
  2348. return Boolean(
  2349. node.type === 'NewExpression' &&
  2350. callee.type === 'Identifier' &&
  2351. callee.name === 'Vue' &&
  2352. node.arguments.length &&
  2353. skipTSAsExpression(node.arguments[0]).type === 'ObjectExpression'
  2354. )
  2355. }
  2356. /**
  2357. * If the given object is a Vue component or instance, returns the Vue definition type.
  2358. * @param {RuleContext} context The ESLint rule context object.
  2359. * @param {ObjectExpression} node Node to check
  2360. * @returns { VueObjectType | null } The Vue definition type.
  2361. */
  2362. function getVueObjectType(context, node) {
  2363. if (node.type !== 'ObjectExpression') {
  2364. return null
  2365. }
  2366. const parent = getParent(node)
  2367. if (parent.type === 'ExportDefaultDeclaration') {
  2368. // export default {} in .vue || .jsx
  2369. const filePath = context.getFilename()
  2370. if (
  2371. isVueComponentFile(parent, filePath) &&
  2372. skipTSAsExpression(parent.declaration) === node
  2373. ) {
  2374. const scriptSetup = getScriptSetupElement(context)
  2375. if (
  2376. scriptSetup &&
  2377. scriptSetup.range[0] <= parent.range[0] &&
  2378. parent.range[1] <= scriptSetup.range[1]
  2379. ) {
  2380. // `export default` in `<script setup>`
  2381. return null
  2382. }
  2383. return 'export'
  2384. }
  2385. } else if (parent.type === 'CallExpression') {
  2386. // Vue.component('xxx', {}) || component('xxx', {})
  2387. if (
  2388. getVueComponentDefinitionType(node) != null &&
  2389. skipTSAsExpression(parent.arguments.slice(-1)[0]) === node
  2390. ) {
  2391. return 'definition'
  2392. }
  2393. } else if (parent.type === 'NewExpression') {
  2394. // new Vue({})
  2395. if (
  2396. isVueInstance(parent) &&
  2397. skipTSAsExpression(parent.arguments[0]) === node
  2398. ) {
  2399. return 'instance'
  2400. }
  2401. }
  2402. if (
  2403. getComponentComments(context).some(
  2404. (el) => el.loc.end.line === node.loc.start.line - 1
  2405. )
  2406. ) {
  2407. return 'mark'
  2408. }
  2409. return null
  2410. }
  2411. /**
  2412. * Checks whether the given object is an SFC definition.
  2413. * @param {RuleContext} context The ESLint rule context object.
  2414. * @param {ObjectExpression} node Node to check
  2415. * @returns { boolean } `true`, the given object is an SFC definition.
  2416. */
  2417. function isSFCObject(context, node) {
  2418. if (node.type !== 'ObjectExpression') {
  2419. return false
  2420. }
  2421. const filePath = context.getFilename()
  2422. const ext = path.extname(filePath)
  2423. if (ext !== '.vue' && ext) {
  2424. return false
  2425. }
  2426. return isSFC(node)
  2427. /**
  2428. * @param {Expression} node
  2429. * @returns {boolean}
  2430. */
  2431. function isSFC(node) {
  2432. const parent = getParent(node)
  2433. if (parent.type === 'ExportDefaultDeclaration') {
  2434. // export default {}
  2435. if (skipTSAsExpression(parent.declaration) !== node) {
  2436. return false
  2437. }
  2438. const scriptSetup = getScriptSetupElement(context)
  2439. if (
  2440. scriptSetup &&
  2441. scriptSetup.range[0] <= parent.range[0] &&
  2442. parent.range[1] <= scriptSetup.range[1]
  2443. ) {
  2444. // `export default` in `<script setup>`
  2445. return false
  2446. }
  2447. return true
  2448. } else if (parent.type === 'CallExpression') {
  2449. if (parent.arguments.every((arg) => skipTSAsExpression(arg) !== node)) {
  2450. return false
  2451. }
  2452. const { callee } = parent
  2453. if (
  2454. (callee.type === 'Identifier' && callee.name === 'defineComponent') ||
  2455. (callee.type === 'MemberExpression' &&
  2456. callee.object.type === 'Identifier' &&
  2457. callee.object.name === 'Vue' &&
  2458. callee.property.type === 'Identifier' &&
  2459. callee.property.name === 'extend')
  2460. ) {
  2461. return isSFC(parent)
  2462. }
  2463. return false
  2464. } else if (parent.type === 'VariableDeclarator') {
  2465. if (
  2466. skipTSAsExpression(parent.init) !== node ||
  2467. parent.id.type !== 'Identifier'
  2468. ) {
  2469. return false
  2470. }
  2471. const variable = findVariable(context.getScope(), parent.id)
  2472. if (!variable) {
  2473. return false
  2474. }
  2475. return variable.references.some((ref) => isSFC(ref.identifier))
  2476. }
  2477. return false
  2478. }
  2479. }
  2480. /**
  2481. * Gets the component comments of a given context.
  2482. * @param {RuleContext} context The ESLint rule context object.
  2483. * @return {Token[]} The the component comments.
  2484. */
  2485. function getComponentComments(context) {
  2486. let tokens = componentComments.get(context)
  2487. if (tokens) {
  2488. return tokens
  2489. }
  2490. const sourceCode = context.getSourceCode()
  2491. tokens = sourceCode
  2492. .getAllComments()
  2493. .filter((comment) => /@vue\/component/g.test(comment.value))
  2494. componentComments.set(context, tokens)
  2495. return tokens
  2496. }
  2497. /**
  2498. * Return generator with the all handler nodes defined in the given watcher property.
  2499. * @param {Property|Expression} property
  2500. * @returns {IterableIterator<Expression>}
  2501. */
  2502. function* iterateWatchHandlerValues(property) {
  2503. const value = property.type === 'Property' ? property.value : property
  2504. if (value.type === 'ObjectExpression') {
  2505. const handler = findProperty(value, 'handler')
  2506. if (handler) {
  2507. yield handler.value
  2508. }
  2509. } else if (value.type === 'ArrayExpression') {
  2510. for (const element of value.elements.filter(isDef)) {
  2511. if (element.type !== 'SpreadElement') {
  2512. yield* iterateWatchHandlerValues(element)
  2513. }
  2514. }
  2515. } else {
  2516. yield value
  2517. }
  2518. }
  2519. /**
  2520. * Get the attribute which has the given name.
  2521. * @param {VElement} node The start tag node to check.
  2522. * @param {string} name The attribute name to check.
  2523. * @param {string} [value] The attribute value to check.
  2524. * @returns {VAttribute | null} The found attribute.
  2525. */
  2526. function getAttribute(node, name, value) {
  2527. return (
  2528. node.startTag.attributes.find(
  2529. /**
  2530. * @param {VAttribute | VDirective} node
  2531. * @returns {node is VAttribute}
  2532. */
  2533. (node) => {
  2534. return (
  2535. !node.directive &&
  2536. node.key.name === name &&
  2537. (value === undefined ||
  2538. (node.value != null && node.value.value === value))
  2539. )
  2540. }
  2541. ) || null
  2542. )
  2543. }
  2544. /**
  2545. * Get the directive list which has the given name.
  2546. * @param {VElement | VStartTag} node The start tag node to check.
  2547. * @param {string} name The directive name to check.
  2548. * @returns {VDirective[]} The array of `v-slot` directives.
  2549. */
  2550. function getDirectives(node, name) {
  2551. const attributes =
  2552. node.type === 'VElement' ? node.startTag.attributes : node.attributes
  2553. return attributes.filter(
  2554. /**
  2555. * @param {VAttribute | VDirective} node
  2556. * @returns {node is VDirective}
  2557. */
  2558. (node) => {
  2559. return node.directive && node.key.name.name === name
  2560. }
  2561. )
  2562. }
  2563. /**
  2564. * Get the directive which has the given name.
  2565. * @param {VElement} node The start tag node to check.
  2566. * @param {string} name The directive name to check.
  2567. * @param {string} [argument] The directive argument to check.
  2568. * @returns {VDirective | null} The found directive.
  2569. */
  2570. function getDirective(node, name, argument) {
  2571. return (
  2572. node.startTag.attributes.find(
  2573. /**
  2574. * @param {VAttribute | VDirective} node
  2575. * @returns {node is VDirective}
  2576. */
  2577. (node) => {
  2578. return (
  2579. node.directive &&
  2580. node.key.name.name === name &&
  2581. (argument === undefined ||
  2582. (node.key.argument &&
  2583. node.key.argument.type === 'VIdentifier' &&
  2584. node.key.argument.name) === argument)
  2585. )
  2586. }
  2587. ) || null
  2588. )
  2589. }
  2590. /**
  2591. * Check whether the given start tag has specific directive.
  2592. * @param {VElement} node The start tag node to check.
  2593. * @param {string} name The attribute name to check.
  2594. * @param {string} [value] The attribute value to check.
  2595. * @returns {boolean} `true` if the start tag has the attribute.
  2596. */
  2597. function hasAttribute(node, name, value) {
  2598. return Boolean(getAttribute(node, name, value))
  2599. }
  2600. /**
  2601. * Check whether the given start tag has specific directive.
  2602. * @param {VElement} node The start tag node to check.
  2603. * @param {string} name The directive name to check.
  2604. * @param {string} [argument] The directive argument to check.
  2605. * @returns {boolean} `true` if the start tag has the directive.
  2606. */
  2607. function hasDirective(node, name, argument) {
  2608. return Boolean(getDirective(node, name, argument))
  2609. }
  2610. /**
  2611. * Checks whether given defineProps call node has withDefaults.
  2612. * @param {CallExpression} node The node of defineProps
  2613. * @returns {node is CallExpression & { parent: CallExpression }}
  2614. */
  2615. function hasWithDefaults(node) {
  2616. return (
  2617. node.parent &&
  2618. node.parent.type === 'CallExpression' &&
  2619. node.parent.arguments[0] === node &&
  2620. node.parent.callee.type === 'Identifier' &&
  2621. node.parent.callee.name === 'withDefaults'
  2622. )
  2623. }
  2624. /**
  2625. * Get the withDefaults call node from given defineProps call node.
  2626. * @param {CallExpression} node The node of defineProps
  2627. * @returns {CallExpression | null}
  2628. */
  2629. function getWithDefaults(node) {
  2630. return hasWithDefaults(node) ? node.parent : null
  2631. }
  2632. /**
  2633. * Gets a map of the property nodes defined in withDefaults.
  2634. * @param {CallExpression} node The node of defineProps
  2635. * @returns { { [key: string]: Property | undefined } }
  2636. */
  2637. function getWithDefaultsProps(node) {
  2638. if (!hasWithDefaults(node)) {
  2639. return {}
  2640. }
  2641. const param = node.parent.arguments[1]
  2642. if (!param || param.type !== 'ObjectExpression') {
  2643. return {}
  2644. }
  2645. /** @type {Record<string, Property>} */
  2646. const result = {}
  2647. for (const prop of param.properties) {
  2648. if (prop.type !== 'Property') {
  2649. return {}
  2650. }
  2651. const name = getStaticPropertyName(prop)
  2652. if (name != null) {
  2653. result[name] = prop
  2654. }
  2655. }
  2656. return result
  2657. }
  2658. /**
  2659. * Get all props from component options object.
  2660. * @param {ObjectExpression} componentObject Object with component definition
  2661. * @return {(ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp)[]} Array of component props
  2662. */
  2663. function getComponentPropsFromOptions(componentObject) {
  2664. const propsNode = componentObject.properties.find(
  2665. /**
  2666. * @param {ESNode} p
  2667. * @returns {p is (Property & { key: Identifier & {name: 'props'} })}
  2668. */
  2669. (p) => {
  2670. return p.type === 'Property' && getStaticPropertyName(p) === 'props'
  2671. }
  2672. )
  2673. if (!propsNode) {
  2674. return []
  2675. }
  2676. if (
  2677. propsNode.value.type !== 'ObjectExpression' &&
  2678. propsNode.value.type !== 'ArrayExpression'
  2679. ) {
  2680. return [
  2681. {
  2682. type: 'unknown',
  2683. key: null,
  2684. propName: null,
  2685. value: null,
  2686. node: propsNode.value
  2687. }
  2688. ]
  2689. }
  2690. return getComponentPropsFromDefine(propsNode.value)
  2691. }
  2692. /**
  2693. * Get all emits from component options object.
  2694. * @param {ObjectExpression} componentObject Object with component definition
  2695. * @return {(ComponentArrayEmit | ComponentObjectEmit | ComponentUnknownEmit)[]} Array of component emits
  2696. */
  2697. function getComponentEmitsFromOptions(componentObject) {
  2698. const emitsNode = componentObject.properties.find(
  2699. /**
  2700. * @param {ESNode} p
  2701. * @returns {p is (Property & { key: Identifier & {name: 'emits'} })}
  2702. */
  2703. (p) => {
  2704. return p.type === 'Property' && getStaticPropertyName(p) === 'emits'
  2705. }
  2706. )
  2707. if (!emitsNode) {
  2708. return []
  2709. }
  2710. if (
  2711. emitsNode.value.type !== 'ObjectExpression' &&
  2712. emitsNode.value.type !== 'ArrayExpression'
  2713. ) {
  2714. return [
  2715. {
  2716. type: 'unknown',
  2717. key: null,
  2718. emitName: null,
  2719. value: null,
  2720. node: emitsNode.value
  2721. }
  2722. ]
  2723. }
  2724. return getComponentEmitsFromDefine(emitsNode.value)
  2725. }
  2726. /**
  2727. * Get all props from `defineProps` call expression.
  2728. * @param {RuleContext} context The rule context object.
  2729. * @param {CallExpression} node `defineProps` call expression
  2730. * @return {(ComponentArrayProp | ComponentObjectProp | ComponentTypeProp | ComponentUnknownProp)[]} Array of component props
  2731. */
  2732. function getComponentPropsFromDefineProps(context, node) {
  2733. if (node.arguments.length >= 1) {
  2734. const defNode = getObjectOrArray(context, node.arguments[0])
  2735. if (defNode) {
  2736. return getComponentPropsFromDefine(defNode)
  2737. }
  2738. return [
  2739. {
  2740. type: 'unknown',
  2741. key: null,
  2742. propName: null,
  2743. value: null,
  2744. node: node.arguments[0]
  2745. }
  2746. ]
  2747. }
  2748. if (node.typeParameters && node.typeParameters.params.length >= 1) {
  2749. return getComponentPropsFromTypeDefine(
  2750. context,
  2751. node.typeParameters.params[0]
  2752. )
  2753. }
  2754. return [
  2755. {
  2756. type: 'unknown',
  2757. key: null,
  2758. propName: null,
  2759. value: null,
  2760. node: null
  2761. }
  2762. ]
  2763. }
  2764. /**
  2765. * Get all emits from `defineEmits` call expression.
  2766. * @param {RuleContext} context The rule context object.
  2767. * @param {CallExpression} node `defineEmits` call expression
  2768. * @return {(ComponentArrayEmit | ComponentObjectEmit | ComponentTypeEmit | ComponentUnknownEmit)[]} Array of component emits
  2769. */
  2770. function getComponentEmitsFromDefineEmits(context, node) {
  2771. if (node.arguments.length >= 1) {
  2772. const defNode = getObjectOrArray(context, node.arguments[0])
  2773. if (defNode) {
  2774. return getComponentEmitsFromDefine(defNode)
  2775. }
  2776. return [
  2777. {
  2778. type: 'unknown',
  2779. key: null,
  2780. emitName: null,
  2781. value: null,
  2782. node: node.arguments[0]
  2783. }
  2784. ]
  2785. }
  2786. if (node.typeParameters && node.typeParameters.params.length >= 1) {
  2787. return getComponentEmitsFromTypeDefine(
  2788. context,
  2789. node.typeParameters.params[0]
  2790. )
  2791. }
  2792. return [
  2793. {
  2794. type: 'unknown',
  2795. key: null,
  2796. emitName: null,
  2797. value: null,
  2798. node: null
  2799. }
  2800. ]
  2801. }
  2802. /**
  2803. * Get all props by looking at all component's properties
  2804. * @param {ObjectExpression|ArrayExpression} propsNode Object with props definition
  2805. * @return {(ComponentArrayProp | ComponentObjectProp | ComponentUnknownProp)[]} Array of component props
  2806. */
  2807. function getComponentPropsFromDefine(propsNode) {
  2808. if (propsNode.type === 'ObjectExpression') {
  2809. return propsNode.properties.map((prop) => {
  2810. if (!isProperty(prop)) {
  2811. return {
  2812. type: 'unknown',
  2813. key: null,
  2814. propName: null,
  2815. value: null,
  2816. node: prop
  2817. }
  2818. }
  2819. const propName = getStaticPropertyName(prop)
  2820. if (propName != null) {
  2821. return {
  2822. type: 'object',
  2823. key: prop.key,
  2824. propName,
  2825. value: skipTSAsExpression(prop.value),
  2826. node: prop
  2827. }
  2828. }
  2829. return {
  2830. type: 'object',
  2831. key: null,
  2832. propName: null,
  2833. value: skipTSAsExpression(prop.value),
  2834. node: prop
  2835. }
  2836. })
  2837. } else {
  2838. return propsNode.elements.filter(isDef).map((prop) => {
  2839. if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') {
  2840. const propName = getStringLiteralValue(prop)
  2841. if (propName != null) {
  2842. return {
  2843. type: 'array',
  2844. key: prop,
  2845. propName,
  2846. value: null,
  2847. node: prop
  2848. }
  2849. }
  2850. }
  2851. return {
  2852. type: 'array',
  2853. key: null,
  2854. propName: null,
  2855. value: null,
  2856. node: prop
  2857. }
  2858. })
  2859. }
  2860. }
  2861. /**
  2862. * Get all emits by looking at all component's properties
  2863. * @param {ObjectExpression|ArrayExpression} emitsNode Object with emits definition
  2864. * @return {(ComponentArrayEmit | ComponentObjectEmit | ComponentUnknownEmit)[]} Array of component emits.
  2865. */
  2866. function getComponentEmitsFromDefine(emitsNode) {
  2867. if (emitsNode.type === 'ObjectExpression') {
  2868. return emitsNode.properties.map((prop) => {
  2869. if (!isProperty(prop)) {
  2870. return {
  2871. type: 'unknown',
  2872. key: null,
  2873. emitName: null,
  2874. value: null,
  2875. node: prop
  2876. }
  2877. }
  2878. const emitName = getStaticPropertyName(prop)
  2879. if (emitName != null) {
  2880. return {
  2881. type: 'object',
  2882. key: prop.key,
  2883. emitName,
  2884. value: skipTSAsExpression(prop.value),
  2885. node: prop
  2886. }
  2887. }
  2888. return {
  2889. type: 'object',
  2890. key: null,
  2891. emitName: null,
  2892. value: skipTSAsExpression(prop.value),
  2893. node: prop
  2894. }
  2895. })
  2896. } else {
  2897. return emitsNode.elements.filter(isDef).map((emit) => {
  2898. if (emit.type === 'Literal' || emit.type === 'TemplateLiteral') {
  2899. const emitName = getStringLiteralValue(emit)
  2900. if (emitName != null) {
  2901. return {
  2902. type: 'array',
  2903. key: emit,
  2904. emitName,
  2905. value: null,
  2906. node: emit
  2907. }
  2908. }
  2909. }
  2910. return {
  2911. type: 'array',
  2912. key: null,
  2913. emitName: null,
  2914. value: null,
  2915. node: emit
  2916. }
  2917. })
  2918. }
  2919. }
  2920. /**
  2921. * @param {RuleContext} context The rule context object.
  2922. * @param {ESNode} node
  2923. * @returns {ObjectExpression | ArrayExpression | null}
  2924. */
  2925. function getObjectOrArray(context, node) {
  2926. if (node.type === 'ObjectExpression') {
  2927. return node
  2928. }
  2929. if (node.type === 'ArrayExpression') {
  2930. return node
  2931. }
  2932. if (node.type === 'Identifier') {
  2933. const variable = findVariable(context.getScope(), node)
  2934. if (variable != null && variable.defs.length === 1) {
  2935. const def = variable.defs[0]
  2936. if (
  2937. def.type === 'Variable' &&
  2938. def.parent.kind === 'const' &&
  2939. def.node.id.type === 'Identifier' &&
  2940. def.node.init
  2941. ) {
  2942. return getObjectOrArray(context, def.node.init)
  2943. }
  2944. }
  2945. }
  2946. return null
  2947. }