index.js 91 KB

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