index.js 85 KB

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