index.js 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022
  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 {object} ComponentArrayPropDetectName
  15. * @property {'array'} type
  16. * @property {Literal | TemplateLiteral} key
  17. * @property {string} propName
  18. * @property {null} value
  19. * @property {Expression | SpreadElement} node
  20. *
  21. * @typedef {object} ComponentArrayPropUnknownName
  22. * @property {'array'} type
  23. * @property {null} key
  24. * @property {null} propName
  25. * @property {null} value
  26. * @property {Expression | SpreadElement} node
  27. *
  28. * @typedef {ComponentArrayPropDetectName | ComponentArrayPropUnknownName} ComponentArrayProp
  29. *
  30. * @typedef {object} ComponentObjectPropDetectName
  31. * @property {'object'} type
  32. * @property {Expression} key
  33. * @property {string} propName
  34. * @property {Expression} value
  35. * @property {Property} node
  36. *
  37. * @typedef {object} ComponentObjectPropUnknownName
  38. * @property {'object'} type
  39. * @property {null} key
  40. * @property {null} propName
  41. * @property {Expression} value
  42. * @property {Property} node
  43. *
  44. * @typedef {ComponentObjectPropDetectName | ComponentObjectPropUnknownName} ComponentObjectProp
  45. */
  46. /**
  47. * @typedef {object} ComponentArrayEmitDetectName
  48. * @property {'array'} type
  49. * @property {Literal | TemplateLiteral} key
  50. * @property {string} emitName
  51. * @property {null} value
  52. * @property {Expression | SpreadElement} node
  53. *
  54. * @typedef {object} ComponentArrayEmitUnknownName
  55. * @property {'array'} type
  56. * @property {null} key
  57. * @property {null} emitName
  58. * @property {null} value
  59. * @property {Expression | SpreadElement} node
  60. *
  61. * @typedef {ComponentArrayEmitDetectName | ComponentArrayEmitUnknownName} ComponentArrayEmit
  62. *
  63. * @typedef {object} ComponentObjectEmitDetectName
  64. * @property {'object'} type
  65. * @property {Expression} key
  66. * @property {string} emitName
  67. * @property {Expression} value
  68. * @property {Property} node
  69. *
  70. * @typedef {object} ComponentObjectEmitUnknownName
  71. * @property {'object'} type
  72. * @property {null} key
  73. * @property {null} emitName
  74. * @property {Expression} value
  75. * @property {Property} node
  76. *
  77. * @typedef {ComponentObjectEmitDetectName | ComponentObjectEmitUnknownName} ComponentObjectEmit
  78. */
  79. /**
  80. * @typedef { {key: string | null, value: BlockStatement | null} } ComponentComputedProperty
  81. */
  82. /**
  83. * @typedef { 'props' | 'data' | 'computed' | 'setup' | 'watch' | 'methods' } GroupName
  84. * @typedef { { type: 'array', name: string, groupName: GroupName, node: Literal | TemplateLiteral } } ComponentArrayPropertyData
  85. * @typedef { { type: 'object', name: string, groupName: GroupName, node: Identifier | Literal | TemplateLiteral, property: Property } } ComponentObjectPropertyData
  86. * @typedef { ComponentArrayPropertyData | ComponentObjectPropertyData } ComponentPropertyData
  87. */
  88. /**
  89. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').VueObjectType} VueObjectType
  90. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').VueObjectData} VueObjectData
  91. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').VueVisitor} VueVisitor
  92. */
  93. // ------------------------------------------------------------------------------
  94. // Helpers
  95. // ------------------------------------------------------------------------------
  96. const HTML_ELEMENT_NAMES = new Set(require('./html-elements.json'))
  97. const SVG_ELEMENT_NAMES = new Set(require('./svg-elements.json'))
  98. const VOID_ELEMENT_NAMES = new Set(require('./void-elements.json'))
  99. const path = require('path')
  100. const vueEslintParser = require('vue-eslint-parser')
  101. const { findVariable } = require('eslint-utils')
  102. /**
  103. * @type { WeakMap<RuleContext, Token[]> }
  104. */
  105. const componentComments = new WeakMap()
  106. /** @type { Map<string, RuleModule> | null } */
  107. let ruleMap = null
  108. /**
  109. * Get the core rule implementation from the rule name
  110. * @param {string} name
  111. * @returns {RuleModule}
  112. */
  113. function getCoreRule(name) {
  114. const map = ruleMap || (ruleMap = new (require('eslint').Linter)().getRules())
  115. return map.get(name) || require(`eslint/lib/rules/${name}`)
  116. }
  117. /**
  118. * Wrap the rule context object to override methods which access to tokens (such as getTokenAfter).
  119. * @param {RuleContext} context The rule context object.
  120. * @param {ParserServices.TokenStore} tokenStore The token store object for template.
  121. * @returns {RuleContext}
  122. */
  123. function wrapContextToOverrideTokenMethods(context, tokenStore) {
  124. const eslintSourceCode = context.getSourceCode()
  125. /** @type {Token[] | null} */
  126. let tokensAndComments = null
  127. function getTokensAndComments() {
  128. if (tokensAndComments) {
  129. return tokensAndComments
  130. }
  131. const templateBody = eslintSourceCode.ast.templateBody
  132. tokensAndComments = templateBody
  133. ? tokenStore.getTokens(templateBody, {
  134. includeComments: true
  135. })
  136. : []
  137. return tokensAndComments
  138. }
  139. const sourceCode = new Proxy(Object.assign({}, eslintSourceCode), {
  140. get(_object, key) {
  141. if (key === 'tokensAndComments') {
  142. return getTokensAndComments()
  143. }
  144. // @ts-expect-error
  145. return key in tokenStore ? tokenStore[key] : eslintSourceCode[key]
  146. }
  147. })
  148. return {
  149. // @ts-expect-error
  150. __proto__: context,
  151. getSourceCode() {
  152. return sourceCode
  153. }
  154. }
  155. }
  156. /**
  157. * Wrap the rule context object to override report method to skip the dynamic argument.
  158. * @param {RuleContext} context The rule context object.
  159. * @returns {RuleContext}
  160. */
  161. function wrapContextToOverrideReportMethodToSkipDynamicArgument(context) {
  162. const sourceCode = context.getSourceCode()
  163. const templateBody = sourceCode.ast.templateBody
  164. if (!templateBody) {
  165. return context
  166. }
  167. /** @type {Range[]} */
  168. const directiveKeyRanges = []
  169. const traverseNodes = vueEslintParser.AST.traverseNodes
  170. traverseNodes(templateBody, {
  171. enterNode(node, parent) {
  172. if (
  173. parent &&
  174. parent.type === 'VDirectiveKey' &&
  175. node.type === 'VExpressionContainer'
  176. ) {
  177. directiveKeyRanges.push(node.range)
  178. }
  179. },
  180. leaveNode() {}
  181. })
  182. return {
  183. // @ts-expect-error
  184. __proto__: context,
  185. report(descriptor, ...args) {
  186. let range = null
  187. if (descriptor.loc) {
  188. const startLoc = descriptor.loc.start || descriptor.loc
  189. const endLoc = descriptor.loc.end || startLoc
  190. range = [
  191. sourceCode.getIndexFromLoc(startLoc),
  192. sourceCode.getIndexFromLoc(endLoc)
  193. ]
  194. } else if (descriptor.node) {
  195. range = descriptor.node.range
  196. }
  197. if (range) {
  198. for (const directiveKeyRange of directiveKeyRanges) {
  199. if (
  200. range[0] < directiveKeyRange[1] &&
  201. directiveKeyRange[0] < range[1]
  202. ) {
  203. return
  204. }
  205. }
  206. }
  207. context.report(descriptor, ...args)
  208. }
  209. }
  210. }
  211. // ------------------------------------------------------------------------------
  212. // Exports
  213. // ------------------------------------------------------------------------------
  214. module.exports = {
  215. /**
  216. * Register the given visitor to parser services.
  217. * If the parser service of `vue-eslint-parser` was not found,
  218. * this generates a warning.
  219. *
  220. * @param {RuleContext} context The rule context to use parser services.
  221. * @param {TemplateListener} templateBodyVisitor The visitor to traverse the template body.
  222. * @param {RuleListener} [scriptVisitor] The visitor to traverse the script.
  223. * @returns {RuleListener} The merged visitor.
  224. */
  225. defineTemplateBodyVisitor,
  226. /**
  227. * Wrap a given core rule to apply it to Vue.js template.
  228. * @param {string} coreRuleName The name of the core rule implementation to wrap.
  229. * @param {Object} [options] The option of this rule.
  230. * @param {string[]} [options.categories] The categories of this rule.
  231. * @param {boolean} [options.skipDynamicArguments] If `true`, skip validation within dynamic arguments.
  232. * @param {boolean} [options.skipDynamicArgumentsReport] If `true`, skip report within dynamic arguments.
  233. * @param { (context: RuleContext, options: { coreHandlers: RuleListener }) => TemplateListener } [options.create] If define, extend core rule.
  234. * @returns {RuleModule} The wrapped rule implementation.
  235. */
  236. wrapCoreRule(coreRuleName, options) {
  237. const coreRule = getCoreRule(coreRuleName)
  238. const {
  239. categories,
  240. skipDynamicArguments,
  241. skipDynamicArgumentsReport,
  242. create
  243. } = options || {}
  244. return {
  245. create(context) {
  246. const tokenStore =
  247. context.parserServices.getTemplateBodyTokenStore &&
  248. context.parserServices.getTemplateBodyTokenStore()
  249. // The `context.getSourceCode()` cannot access the tokens of templates.
  250. // So override the methods which access to tokens by the `tokenStore`.
  251. if (tokenStore) {
  252. context = wrapContextToOverrideTokenMethods(context, tokenStore)
  253. }
  254. if (skipDynamicArgumentsReport) {
  255. context = wrapContextToOverrideReportMethodToSkipDynamicArgument(
  256. context
  257. )
  258. }
  259. // Move `Program` handlers to `VElement[parent.type!='VElement']`
  260. const coreHandlers = coreRule.create(context)
  261. const handlers = /** @type {TemplateListener} */ (Object.assign(
  262. {},
  263. coreHandlers
  264. ))
  265. if (handlers.Program) {
  266. handlers["VElement[parent.type!='VElement']"] = handlers.Program
  267. delete handlers.Program
  268. }
  269. if (handlers['Program:exit']) {
  270. handlers["VElement[parent.type!='VElement']:exit"] =
  271. handlers['Program:exit']
  272. delete handlers['Program:exit']
  273. }
  274. if (skipDynamicArguments) {
  275. let withinDynamicArguments = false
  276. for (const name of Object.keys(handlers)) {
  277. const original = handlers[name]
  278. /** @param {any[]} args */
  279. handlers[name] = (...args) => {
  280. if (withinDynamicArguments) return
  281. // @ts-expect-error
  282. original(...args)
  283. }
  284. }
  285. handlers['VDirectiveKey > VExpressionContainer'] = () => {
  286. withinDynamicArguments = true
  287. }
  288. handlers['VDirectiveKey > VExpressionContainer:exit'] = () => {
  289. withinDynamicArguments = false
  290. }
  291. }
  292. if (create) {
  293. compositingVisitors(handlers, create(context, { coreHandlers }))
  294. }
  295. // Apply the handlers to templates.
  296. return defineTemplateBodyVisitor(context, handlers)
  297. },
  298. meta: Object.assign({}, coreRule.meta, {
  299. docs: Object.assign({}, coreRule.meta.docs, {
  300. category: null,
  301. categories,
  302. url: `https://eslint.vuejs.org/rules/${path.basename(
  303. coreRule.meta.docs.url || ''
  304. )}.html`,
  305. extensionRule: true,
  306. coreRuleUrl: coreRule.meta.docs.url
  307. })
  308. })
  309. }
  310. },
  311. /**
  312. * Checks whether the given value is defined.
  313. * @template T
  314. * @param {T | null | undefined} v
  315. * @returns {v is T}
  316. */
  317. isDef,
  318. /**
  319. * Get the previous sibling element of the given element.
  320. * @param {VElement} node The element node to get the previous sibling element.
  321. * @returns {VElement|null} The previous sibling element.
  322. */
  323. prevSibling(node) {
  324. let prevElement = null
  325. for (const siblingNode of (node.parent && node.parent.children) || []) {
  326. if (siblingNode === node) {
  327. return prevElement
  328. }
  329. if (siblingNode.type === 'VElement') {
  330. prevElement = siblingNode
  331. }
  332. }
  333. return null
  334. },
  335. /**
  336. * Check whether the given start tag has specific directive.
  337. * @param {VElement} node The start tag node to check.
  338. * @param {string} name The attribute name to check.
  339. * @param {string} [value] The attribute value to check.
  340. * @returns {boolean} `true` if the start tag has the attribute.
  341. */
  342. hasAttribute(node, name, value) {
  343. return Boolean(this.getAttribute(node, name, value))
  344. },
  345. /**
  346. * Check whether the given start tag has specific directive.
  347. * @param {VElement} node The start tag node to check.
  348. * @param {string} name The directive name to check.
  349. * @param {string} [argument] The directive argument to check.
  350. * @returns {boolean} `true` if the start tag has the directive.
  351. */
  352. hasDirective(node, name, argument) {
  353. return Boolean(this.getDirective(node, name, argument))
  354. },
  355. /**
  356. * Check whether the given directive attribute has their empty value (`=""`).
  357. * @param {VDirective} node The directive attribute node to check.
  358. * @param {RuleContext} context The rule context to use parser services.
  359. * @returns {boolean} `true` if the directive attribute has their empty value (`=""`).
  360. */
  361. isEmptyValueDirective(node, context) {
  362. if (node.value == null) {
  363. return false
  364. }
  365. if (node.value.expression != null) {
  366. return false
  367. }
  368. let valueText = context.getSourceCode().getText(node.value)
  369. if (
  370. (valueText[0] === '"' || valueText[0] === "'") &&
  371. valueText[0] === valueText[valueText.length - 1]
  372. ) {
  373. // quoted
  374. valueText = valueText.slice(1, -1)
  375. }
  376. if (!valueText) {
  377. // empty
  378. return true
  379. }
  380. return false
  381. },
  382. /**
  383. * Check whether the given directive attribute has their empty expression value (e.g. `=" "`, `="/* &ast;/"`).
  384. * @param {VDirective} node The directive attribute node to check.
  385. * @param {RuleContext} context The rule context to use parser services.
  386. * @returns {boolean} `true` if the directive attribute has their empty expression value.
  387. */
  388. isEmptyExpressionValueDirective(node, context) {
  389. if (node.value == null) {
  390. return false
  391. }
  392. if (node.value.expression != null) {
  393. return false
  394. }
  395. const valueNode = node.value
  396. const tokenStore = context.parserServices.getTemplateBodyTokenStore()
  397. let quote1 = null
  398. let quote2 = null
  399. // `node.value` may be only comments, so cannot get the correct tokens with `tokenStore.getTokens(node.value)`.
  400. for (const token of tokenStore.getTokens(node)) {
  401. if (token.range[1] <= valueNode.range[0]) {
  402. continue
  403. }
  404. if (valueNode.range[1] <= token.range[0]) {
  405. // empty
  406. return true
  407. }
  408. if (
  409. !quote1 &&
  410. token.type === 'Punctuator' &&
  411. (token.value === '"' || token.value === "'")
  412. ) {
  413. quote1 = token
  414. continue
  415. }
  416. if (
  417. !quote2 &&
  418. quote1 &&
  419. token.type === 'Punctuator' &&
  420. token.value === quote1.value
  421. ) {
  422. quote2 = token
  423. continue
  424. }
  425. // not empty
  426. return false
  427. }
  428. // empty
  429. return true
  430. },
  431. /**
  432. * Get the attribute which has the given name.
  433. * @param {VElement} node The start tag node to check.
  434. * @param {string} name The attribute name to check.
  435. * @param {string} [value] The attribute value to check.
  436. * @returns {VAttribute | null} The found attribute.
  437. */
  438. getAttribute(node, name, value) {
  439. return (
  440. node.startTag.attributes.find(
  441. /**
  442. * @param {VAttribute | VDirective} node
  443. * @returns {node is VAttribute}
  444. */
  445. (node) => {
  446. return (
  447. !node.directive &&
  448. node.key.name === name &&
  449. (value === undefined ||
  450. (node.value != null && node.value.value === value))
  451. )
  452. }
  453. ) || null
  454. )
  455. },
  456. /**
  457. * Get the directive list which has the given name.
  458. * @param {VElement | VStartTag} node The start tag node to check.
  459. * @param {string} name The directive name to check.
  460. * @returns {VDirective[]} The array of `v-slot` directives.
  461. */
  462. getDirectives(node, name) {
  463. const attributes =
  464. node.type === 'VElement' ? node.startTag.attributes : node.attributes
  465. return attributes.filter(
  466. /**
  467. * @param {VAttribute | VDirective} node
  468. * @returns {node is VDirective}
  469. */
  470. (node) => {
  471. return node.directive && node.key.name.name === name
  472. }
  473. )
  474. },
  475. /**
  476. * Get the directive which has the given name.
  477. * @param {VElement} node The start tag node to check.
  478. * @param {string} name The directive name to check.
  479. * @param {string} [argument] The directive argument to check.
  480. * @returns {VDirective | null} The found directive.
  481. */
  482. getDirective(node, name, argument) {
  483. return (
  484. node.startTag.attributes.find(
  485. /**
  486. * @param {VAttribute | VDirective} node
  487. * @returns {node is VDirective}
  488. */
  489. (node) => {
  490. return (
  491. node.directive &&
  492. node.key.name.name === name &&
  493. (argument === undefined ||
  494. (node.key.argument &&
  495. node.key.argument.type === 'VIdentifier' &&
  496. node.key.argument.name) === argument)
  497. )
  498. }
  499. ) || null
  500. )
  501. },
  502. /**
  503. * Returns the list of all registered components
  504. * @param {ObjectExpression} componentObject
  505. * @returns { { node: Property, name: string }[] } Array of ASTNodes
  506. */
  507. getRegisteredComponents(componentObject) {
  508. const componentsNode = componentObject.properties.find(
  509. /**
  510. * @param {ESNode} p
  511. * @returns {p is (Property & { key: Identifier & {name: 'components'}, value: ObjectExpression })}
  512. */
  513. (p) => {
  514. return (
  515. p.type === 'Property' &&
  516. p.key.type === 'Identifier' &&
  517. p.key.name === 'components' &&
  518. p.value.type === 'ObjectExpression'
  519. )
  520. }
  521. )
  522. if (!componentsNode) {
  523. return []
  524. }
  525. return componentsNode.value.properties
  526. .filter(isProperty)
  527. .map((node) => {
  528. const name = getStaticPropertyName(node)
  529. return name ? { node, name } : null
  530. })
  531. .filter(isDef)
  532. },
  533. /**
  534. * Check whether the previous sibling element has `if` or `else-if` directive.
  535. * @param {VElement} node The element node to check.
  536. * @returns {boolean} `true` if the previous sibling element has `if` or `else-if` directive.
  537. */
  538. prevElementHasIf(node) {
  539. const prev = this.prevSibling(node)
  540. return (
  541. prev != null &&
  542. prev.startTag.attributes.some(
  543. (a) =>
  544. a.directive &&
  545. (a.key.name.name === 'if' || a.key.name.name === 'else-if')
  546. )
  547. )
  548. },
  549. /**
  550. * Check whether the given node is a custom component or not.
  551. * @param {VElement} node The start tag node to check.
  552. * @returns {boolean} `true` if the node is a custom component.
  553. */
  554. isCustomComponent(node) {
  555. return (
  556. (this.isHtmlElementNode(node) &&
  557. !this.isHtmlWellKnownElementName(node.rawName)) ||
  558. (this.isSvgElementNode(node) &&
  559. !this.isSvgWellKnownElementName(node.rawName)) ||
  560. this.hasAttribute(node, 'is') ||
  561. this.hasDirective(node, 'bind', 'is') ||
  562. this.hasDirective(node, 'is')
  563. )
  564. },
  565. /**
  566. * Check whether the given node is a HTML element or not.
  567. * @param {VElement} node The node to check.
  568. * @returns {boolean} `true` if the node is a HTML element.
  569. */
  570. isHtmlElementNode(node) {
  571. return node.namespace === vueEslintParser.AST.NS.HTML
  572. },
  573. /**
  574. * Check whether the given node is a SVG element or not.
  575. * @param {VElement} node The node to check.
  576. * @returns {boolean} `true` if the name is a SVG element.
  577. */
  578. isSvgElementNode(node) {
  579. return node.namespace === vueEslintParser.AST.NS.SVG
  580. },
  581. /**
  582. * Check whether the given name is a MathML element or not.
  583. * @param {VElement} node The node to check.
  584. * @returns {boolean} `true` if the node is a MathML element.
  585. */
  586. isMathMLElementNode(node) {
  587. return node.namespace === vueEslintParser.AST.NS.MathML
  588. },
  589. /**
  590. * Check whether the given name is an well-known element or not.
  591. * @param {string} name The name to check.
  592. * @returns {boolean} `true` if the name is an well-known element name.
  593. */
  594. isHtmlWellKnownElementName(name) {
  595. return HTML_ELEMENT_NAMES.has(name)
  596. },
  597. /**
  598. * Check whether the given name is an well-known SVG element or not.
  599. * @param {string} name The name to check.
  600. * @returns {boolean} `true` if the name is an well-known SVG element name.
  601. */
  602. isSvgWellKnownElementName(name) {
  603. return SVG_ELEMENT_NAMES.has(name)
  604. },
  605. /**
  606. * Check whether the given name is a void element name or not.
  607. * @param {string} name The name to check.
  608. * @returns {boolean} `true` if the name is a void element name.
  609. */
  610. isHtmlVoidElementName(name) {
  611. return VOID_ELEMENT_NAMES.has(name)
  612. },
  613. /**
  614. * Gets the property name of a given node.
  615. * @param {Property|AssignmentProperty|MethodDefinition|MemberExpression} node - The node to get.
  616. * @return {string|null} The property name if static. Otherwise, null.
  617. */
  618. getStaticPropertyName,
  619. /**
  620. * Gets the string of a given node.
  621. * @param {Literal|TemplateLiteral} node - The node to get.
  622. * @return {string|null} The string if static. Otherwise, null.
  623. */
  624. getStringLiteralValue,
  625. /**
  626. * Get all props by looking at all component's properties
  627. * @param {ObjectExpression} componentObject Object with component definition
  628. * @return {(ComponentArrayProp | ComponentObjectProp)[]} Array of component props in format: [{key?: String, value?: ASTNode, node: ASTNod}]
  629. */
  630. getComponentProps(componentObject) {
  631. const propsNode = componentObject.properties.find(
  632. /**
  633. * @param {ESNode} p
  634. * @returns {p is (Property & { key: Identifier & {name: 'props'}, value: ObjectExpression | ArrayExpression })}
  635. */
  636. (p) => {
  637. return (
  638. p.type === 'Property' &&
  639. p.key.type === 'Identifier' &&
  640. p.key.name === 'props' &&
  641. (p.value.type === 'ObjectExpression' ||
  642. p.value.type === 'ArrayExpression')
  643. )
  644. }
  645. )
  646. if (!propsNode) {
  647. return []
  648. }
  649. if (propsNode.value.type === 'ObjectExpression') {
  650. return propsNode.value.properties.filter(isProperty).map((prop) => {
  651. const propName = getStaticPropertyName(prop)
  652. if (propName != null) {
  653. return {
  654. type: 'object',
  655. key: prop.key,
  656. propName,
  657. value: skipTSAsExpression(prop.value),
  658. node: prop
  659. }
  660. }
  661. return {
  662. type: 'object',
  663. key: null,
  664. propName: null,
  665. value: skipTSAsExpression(prop.value),
  666. node: prop
  667. }
  668. })
  669. } else {
  670. return propsNode.value.elements.filter(isDef).map((prop) => {
  671. if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') {
  672. const propName = getStringLiteralValue(prop)
  673. if (propName != null) {
  674. return {
  675. type: 'array',
  676. key: prop,
  677. propName,
  678. value: null,
  679. node: prop
  680. }
  681. }
  682. }
  683. return {
  684. type: 'array',
  685. key: null,
  686. propName: null,
  687. value: null,
  688. node: prop
  689. }
  690. })
  691. }
  692. },
  693. /**
  694. * Get all emits by looking at all component's properties
  695. * @param {ObjectExpression} componentObject Object with component definition
  696. * @return {(ComponentArrayEmit | ComponentObjectEmit)[]} Array of component emits in format: [{key?: String, value?: ASTNode, node: ASTNod}]
  697. */
  698. getComponentEmits(componentObject) {
  699. const emitsNode = componentObject.properties.find(
  700. /**
  701. * @param {ESNode} p
  702. * @returns {p is (Property & { key: Identifier & {name: 'emits'}, value: ObjectExpression | ArrayExpression })}
  703. */
  704. (p) => {
  705. return (
  706. p.type === 'Property' &&
  707. p.key.type === 'Identifier' &&
  708. p.key.name === 'emits' &&
  709. (p.value.type === 'ObjectExpression' ||
  710. p.value.type === 'ArrayExpression')
  711. )
  712. }
  713. )
  714. if (!emitsNode) {
  715. return []
  716. }
  717. if (emitsNode.value.type === 'ObjectExpression') {
  718. return emitsNode.value.properties.filter(isProperty).map((prop) => {
  719. const emitName = getStaticPropertyName(prop)
  720. if (emitName != null) {
  721. return {
  722. type: 'object',
  723. key: prop.key,
  724. emitName,
  725. value: skipTSAsExpression(prop.value),
  726. node: prop
  727. }
  728. }
  729. return {
  730. type: 'object',
  731. key: null,
  732. emitName: null,
  733. value: skipTSAsExpression(prop.value),
  734. node: prop
  735. }
  736. })
  737. } else {
  738. return emitsNode.value.elements.filter(isDef).map((prop) => {
  739. if (prop.type === 'Literal' || prop.type === 'TemplateLiteral') {
  740. const emitName = getStringLiteralValue(prop)
  741. if (emitName != null) {
  742. return {
  743. type: 'array',
  744. key: prop,
  745. emitName,
  746. value: null,
  747. node: prop
  748. }
  749. }
  750. }
  751. return {
  752. type: 'array',
  753. key: null,
  754. emitName: null,
  755. value: null,
  756. node: prop
  757. }
  758. })
  759. }
  760. },
  761. /**
  762. * Get all computed properties by looking at all component's properties
  763. * @param {ObjectExpression} componentObject Object with component definition
  764. * @return {ComponentComputedProperty[]} Array of computed properties in format: [{key: String, value: ASTNode}]
  765. */
  766. getComputedProperties(componentObject) {
  767. const computedPropertiesNode = componentObject.properties.find(
  768. /**
  769. * @param {ESNode} p
  770. * @returns {p is (Property & { key: Identifier & {name: 'computed'}, value: ObjectExpression })}
  771. */
  772. (p) => {
  773. return (
  774. p.type === 'Property' &&
  775. p.key.type === 'Identifier' &&
  776. p.key.name === 'computed' &&
  777. p.value.type === 'ObjectExpression'
  778. )
  779. }
  780. )
  781. if (!computedPropertiesNode) {
  782. return []
  783. }
  784. return computedPropertiesNode.value.properties
  785. .filter(isProperty)
  786. .map((cp) => {
  787. const key = getStaticPropertyName(cp)
  788. /** @type {Expression} */
  789. const propValue = skipTSAsExpression(cp.value)
  790. /** @type {BlockStatement | null} */
  791. let value = null
  792. if (propValue.type === 'FunctionExpression') {
  793. value = propValue.body
  794. } else if (propValue.type === 'ObjectExpression') {
  795. const get = propValue.properties.find(
  796. /**
  797. * @param {ESNode} p
  798. * @returns { p is (Property & { value: FunctionExpression }) }
  799. */
  800. (p) =>
  801. p.type === 'Property' &&
  802. p.key.type === 'Identifier' &&
  803. p.key.name === 'get' &&
  804. p.value.type === 'FunctionExpression'
  805. )
  806. value = get ? get.value.body : null
  807. }
  808. return { key, value }
  809. })
  810. },
  811. isVueFile,
  812. /**
  813. * Check if current file is a Vue instance or component and call callback
  814. * @param {RuleContext} context The ESLint rule context object.
  815. * @param { (node: ObjectExpression, type: VueObjectType) => void } cb Callback function
  816. */
  817. executeOnVue(context, cb) {
  818. return compositingVisitors(
  819. this.executeOnVueComponent(context, cb),
  820. this.executeOnVueInstance(context, cb)
  821. )
  822. },
  823. /**
  824. * Define handlers to traverse the Vue Objects.
  825. * Some special events are available to visitor.
  826. *
  827. * - `onVueObjectEnter` ... Event when Vue Object is found.
  828. * - `onVueObjectExit` ... Event when Vue Object visit ends.
  829. * - `onSetupFunctionEnter` ... Event when setup function found.
  830. * - `onRenderFunctionEnter` ... Event when render function found.
  831. *
  832. * @param {RuleContext} context The ESLint rule context object.
  833. * @param {VueVisitor} visitor The visitor to traverse the Vue Objects.
  834. */
  835. defineVueVisitor(context, visitor) {
  836. /** @type {VueObjectData | null} */
  837. let vueStack = null
  838. /**
  839. * @param {string} key
  840. * @param {ESNode} node
  841. */
  842. function callVisitor(key, node) {
  843. if (visitor[key] && vueStack) {
  844. // @ts-expect-error
  845. visitor[key](node, vueStack)
  846. }
  847. }
  848. /** @type {NodeListener} */
  849. const vueVisitor = {}
  850. for (const key in visitor) {
  851. vueVisitor[key] = (node) => callVisitor(key, node)
  852. }
  853. /**
  854. * @param {ObjectExpression} node
  855. */
  856. vueVisitor.ObjectExpression = (node) => {
  857. const type = getVueObjectType(context, node)
  858. if (type) {
  859. vueStack = {
  860. node,
  861. type,
  862. parent: vueStack,
  863. get functional() {
  864. const functional = node.properties.find(
  865. /**
  866. * @param {Property | SpreadElement} p
  867. * @returns {p is Property}
  868. */
  869. (p) =>
  870. p.type === 'Property' &&
  871. getStaticPropertyName(p) === 'functional'
  872. )
  873. if (!functional) {
  874. return false
  875. }
  876. if (
  877. functional.value.type === 'Literal' &&
  878. functional.value.value === false
  879. ) {
  880. return false
  881. }
  882. return true
  883. }
  884. }
  885. callVisitor('onVueObjectEnter', node)
  886. }
  887. callVisitor('ObjectExpression', node)
  888. }
  889. vueVisitor['ObjectExpression:exit'] = (node) => {
  890. callVisitor('ObjectExpression:exit', node)
  891. if (vueStack && vueStack.node === node) {
  892. callVisitor('onVueObjectExit', node)
  893. vueStack = vueStack.parent
  894. }
  895. }
  896. if (visitor.onSetupFunctionEnter || visitor.onRenderFunctionEnter) {
  897. /** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property } } node */
  898. vueVisitor[
  899. 'Property[value.type=/^(Arrow)?FunctionExpression$/] > :function'
  900. ] = (node) => {
  901. /** @type {Property} */
  902. const prop = node.parent
  903. if (vueStack && prop.parent === vueStack.node && prop.value === node) {
  904. const name = getStaticPropertyName(prop)
  905. if (name === 'setup') {
  906. callVisitor('onSetupFunctionEnter', node)
  907. } else if (name === 'render') {
  908. callVisitor('onRenderFunctionEnter', node)
  909. }
  910. }
  911. callVisitor(
  912. 'Property[value.type=/^(Arrow)?FunctionExpression$/] > :function',
  913. node
  914. )
  915. }
  916. }
  917. return vueVisitor
  918. },
  919. getVueObjectType,
  920. compositingVisitors,
  921. /**
  922. * Check if current file is a Vue instance (new Vue) and call callback
  923. * @param {RuleContext} context The ESLint rule context object.
  924. * @param { (node: ObjectExpression, type: VueObjectType) => void } cb Callback function
  925. */
  926. executeOnVueInstance(context, cb) {
  927. return {
  928. /** @param {ObjectExpression} node */
  929. 'ObjectExpression:exit'(node) {
  930. const type = getVueObjectType(context, node)
  931. if (!type || type !== 'instance') return
  932. cb(node, type)
  933. }
  934. }
  935. },
  936. /**
  937. * Check if current file is a Vue component and call callback
  938. * @param {RuleContext} context The ESLint rule context object.
  939. * @param { (node: ObjectExpression, type: VueObjectType) => void } cb Callback function
  940. */
  941. executeOnVueComponent(context, cb) {
  942. return {
  943. /** @param {ObjectExpression} node */
  944. 'ObjectExpression:exit'(node) {
  945. const type = getVueObjectType(context, node)
  946. if (
  947. !type ||
  948. (type !== 'mark' && type !== 'export' && type !== 'definition')
  949. )
  950. return
  951. cb(node, type)
  952. }
  953. }
  954. },
  955. /**
  956. * Check call `Vue.component` and call callback.
  957. * @param {RuleContext} _context The ESLint rule context object.
  958. * @param { (node: CallExpression) => void } cb Callback function
  959. */
  960. executeOnCallVueComponent(_context, cb) {
  961. return {
  962. /** @param {Identifier & { parent: MemberExpression & { parent: CallExpression } } } node */
  963. "CallExpression > MemberExpression > Identifier[name='component']": (
  964. node
  965. ) => {
  966. const callExpr = node.parent.parent
  967. const callee = callExpr.callee
  968. if (callee.type === 'MemberExpression') {
  969. const calleeObject = skipTSAsExpression(callee.object)
  970. if (
  971. calleeObject.type === 'Identifier' &&
  972. // calleeObject.name === 'Vue' && // Any names can be used in Vue.js 3.x. e.g. app.component()
  973. callee.property === node &&
  974. callExpr.arguments.length >= 1
  975. ) {
  976. cb(callExpr)
  977. }
  978. }
  979. }
  980. }
  981. },
  982. /**
  983. * Return generator with all properties
  984. * @param {ObjectExpression} node Node to check
  985. * @param {Set<GroupName>} groups Name of parent group
  986. * @returns {IterableIterator<ComponentPropertyData>}
  987. */
  988. *iterateProperties(node, groups) {
  989. for (const item of node.properties) {
  990. if (item.type !== 'Property') {
  991. continue
  992. }
  993. const name = /** @type {GroupName | null} */ (getStaticPropertyName(item))
  994. if (!name || !groups.has(name)) continue
  995. if (item.value.type === 'ArrayExpression') {
  996. yield* this.iterateArrayExpression(item.value, name)
  997. } else if (item.value.type === 'ObjectExpression') {
  998. yield* this.iterateObjectExpression(item.value, name)
  999. } else if (item.value.type === 'FunctionExpression') {
  1000. yield* this.iterateFunctionExpression(item.value, name)
  1001. } else if (item.value.type === 'ArrowFunctionExpression') {
  1002. yield* this.iterateArrowFunctionExpression(item.value, name)
  1003. }
  1004. }
  1005. },
  1006. /**
  1007. * Return generator with all elements inside ArrayExpression
  1008. * @param {ArrayExpression} node Node to check
  1009. * @param {GroupName} groupName Name of parent group
  1010. * @returns {IterableIterator<ComponentArrayPropertyData>}
  1011. */
  1012. *iterateArrayExpression(node, groupName) {
  1013. for (const item of node.elements) {
  1014. if (
  1015. item &&
  1016. (item.type === 'Literal' || item.type === 'TemplateLiteral')
  1017. ) {
  1018. const name = getStringLiteralValue(item)
  1019. if (name) {
  1020. yield { type: 'array', name, groupName, node: item }
  1021. }
  1022. }
  1023. }
  1024. },
  1025. /**
  1026. * Return generator with all elements inside ObjectExpression
  1027. * @param {ObjectExpression} node Node to check
  1028. * @param {GroupName} groupName Name of parent group
  1029. * @returns {IterableIterator<ComponentObjectPropertyData>}
  1030. */
  1031. *iterateObjectExpression(node, groupName) {
  1032. /** @type {Set<Property> | undefined} */
  1033. let usedGetter
  1034. for (const item of node.properties) {
  1035. if (item.type === 'Property') {
  1036. const key = item.key
  1037. if (
  1038. key.type === 'Identifier' ||
  1039. key.type === 'Literal' ||
  1040. key.type === 'TemplateLiteral'
  1041. ) {
  1042. const name = getStaticPropertyName(item)
  1043. if (name) {
  1044. if (item.kind === 'set') {
  1045. // find getter pair
  1046. if (
  1047. node.properties.some((item2) => {
  1048. if (item2.type === 'Property' && item2.kind === 'get') {
  1049. if (!usedGetter) {
  1050. usedGetter = new Set()
  1051. }
  1052. if (usedGetter.has(item2)) {
  1053. return false
  1054. }
  1055. const getterName = getStaticPropertyName(item2)
  1056. if (getterName === name) {
  1057. usedGetter.add(item2)
  1058. return true
  1059. }
  1060. }
  1061. return false
  1062. })
  1063. ) {
  1064. // has getter pair
  1065. continue
  1066. }
  1067. }
  1068. yield {
  1069. type: 'object',
  1070. name,
  1071. groupName,
  1072. node: key,
  1073. property: item
  1074. }
  1075. }
  1076. }
  1077. }
  1078. }
  1079. },
  1080. /**
  1081. * Return generator with all elements inside FunctionExpression
  1082. * @param {FunctionExpression} node Node to check
  1083. * @param {GroupName} groupName Name of parent group
  1084. * @returns {IterableIterator<ComponentObjectPropertyData>}
  1085. */
  1086. *iterateFunctionExpression(node, groupName) {
  1087. if (node.body.type === 'BlockStatement') {
  1088. for (const item of node.body.body) {
  1089. if (
  1090. item.type === 'ReturnStatement' &&
  1091. item.argument &&
  1092. item.argument.type === 'ObjectExpression'
  1093. ) {
  1094. yield* this.iterateObjectExpression(item.argument, groupName)
  1095. }
  1096. }
  1097. }
  1098. },
  1099. /**
  1100. * Return generator with all elements inside ArrowFunctionExpression
  1101. * @param {ArrowFunctionExpression} node Node to check
  1102. * @param {GroupName} groupName Name of parent group
  1103. * @returns {IterableIterator<ComponentObjectPropertyData>}
  1104. */
  1105. *iterateArrowFunctionExpression(node, groupName) {
  1106. const body = node.body
  1107. if (body.type === 'BlockStatement') {
  1108. for (const item of body.body) {
  1109. if (
  1110. item.type === 'ReturnStatement' &&
  1111. item.argument &&
  1112. item.argument.type === 'ObjectExpression'
  1113. ) {
  1114. yield* this.iterateObjectExpression(item.argument, groupName)
  1115. }
  1116. }
  1117. } else if (body.type === 'ObjectExpression') {
  1118. yield* this.iterateObjectExpression(body, groupName)
  1119. }
  1120. },
  1121. /**
  1122. * Find all functions which do not always return values
  1123. * @param {boolean} treatUndefinedAsUnspecified
  1124. * @param { (node: ESNode) => void } cb Callback function
  1125. * @returns {RuleListener}
  1126. */
  1127. executeOnFunctionsWithoutReturn(treatUndefinedAsUnspecified, cb) {
  1128. /**
  1129. * @typedef {object} FuncInfo
  1130. * @property {FuncInfo} funcInfo
  1131. * @property {CodePath} codePath
  1132. * @property {boolean} hasReturn
  1133. * @property {boolean} hasReturnValue
  1134. * @property {ESNode} node
  1135. */
  1136. /** @type {FuncInfo} */
  1137. let funcInfo
  1138. /** @param {CodePathSegment} segment */
  1139. function isReachable(segment) {
  1140. return segment.reachable
  1141. }
  1142. function isValidReturn() {
  1143. if (
  1144. funcInfo.codePath &&
  1145. funcInfo.codePath.currentSegments.some(isReachable)
  1146. ) {
  1147. return false
  1148. }
  1149. return !treatUndefinedAsUnspecified || funcInfo.hasReturnValue
  1150. }
  1151. return {
  1152. /**
  1153. * @param {CodePath} codePath
  1154. * @param {ESNode} node
  1155. */
  1156. onCodePathStart(codePath, node) {
  1157. funcInfo = {
  1158. codePath,
  1159. funcInfo,
  1160. hasReturn: false,
  1161. hasReturnValue: false,
  1162. node
  1163. }
  1164. },
  1165. onCodePathEnd() {
  1166. funcInfo = funcInfo.funcInfo
  1167. },
  1168. /** @param {ReturnStatement} node */
  1169. ReturnStatement(node) {
  1170. funcInfo.hasReturn = true
  1171. funcInfo.hasReturnValue = Boolean(node.argument)
  1172. },
  1173. /** @param {ArrowFunctionExpression} node */
  1174. 'ArrowFunctionExpression:exit'(node) {
  1175. if (!isValidReturn() && !node.expression) {
  1176. cb(funcInfo.node)
  1177. }
  1178. },
  1179. 'FunctionExpression:exit'() {
  1180. if (!isValidReturn()) {
  1181. cb(funcInfo.node)
  1182. }
  1183. }
  1184. }
  1185. },
  1186. /**
  1187. * Check whether the component is declared in a single line or not.
  1188. * @param {ASTNode} node
  1189. * @returns {boolean}
  1190. */
  1191. isSingleLine(node) {
  1192. return node.loc.start.line === node.loc.end.line
  1193. },
  1194. /**
  1195. * Check whether the templateBody of the program has invalid EOF or not.
  1196. * @param {Program} node The program node to check.
  1197. * @returns {boolean} `true` if it has invalid EOF.
  1198. */
  1199. hasInvalidEOF(node) {
  1200. const body = node.templateBody
  1201. if (body == null || body.errors == null) {
  1202. return false
  1203. }
  1204. return body.errors.some(
  1205. (error) => typeof error.code === 'string' && error.code.startsWith('eof-')
  1206. )
  1207. },
  1208. /**
  1209. * Get the chaining nodes of MemberExpression.
  1210. *
  1211. * @param {ESNode} node The node to parse
  1212. * @return {[ESNode, ...MemberExpression[]]} The chaining nodes
  1213. */
  1214. getMemberChaining(node) {
  1215. /** @type {MemberExpression[]} */
  1216. const nodes = []
  1217. let n = skipChainExpression(node)
  1218. while (n.type === 'MemberExpression') {
  1219. nodes.push(n)
  1220. n = skipChainExpression(n.object)
  1221. }
  1222. return [n, ...nodes.reverse()]
  1223. },
  1224. /**
  1225. * return two string editdistance
  1226. * @param {string} a string a to compare
  1227. * @param {string} b string b to compare
  1228. * @returns {number}
  1229. */
  1230. editDistance(a, b) {
  1231. if (a === b) {
  1232. return 0
  1233. }
  1234. const alen = a.length
  1235. const blen = b.length
  1236. const dp = Array.from({ length: alen + 1 }).map((_) =>
  1237. Array.from({ length: blen + 1 }).fill(0)
  1238. )
  1239. for (let i = 0; i <= alen; i++) {
  1240. dp[i][0] = i
  1241. }
  1242. for (let j = 0; j <= blen; j++) {
  1243. dp[0][j] = j
  1244. }
  1245. for (let i = 1; i <= alen; i++) {
  1246. for (let j = 1; j <= blen; j++) {
  1247. if (a[i - 1] === b[j - 1]) {
  1248. dp[i][j] = dp[i - 1][j - 1]
  1249. } else {
  1250. dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
  1251. }
  1252. }
  1253. }
  1254. return dp[alen][blen]
  1255. },
  1256. /**
  1257. * Checks whether the given node is Property.
  1258. */
  1259. isProperty,
  1260. /**
  1261. * Checks whether the given node is AssignmentProperty.
  1262. */
  1263. isAssignmentProperty,
  1264. /**
  1265. * Checks whether the given node is VElement.
  1266. */
  1267. isVElement,
  1268. /**
  1269. * Finds the property with the given name from the given ObjectExpression node.
  1270. */
  1271. findProperty,
  1272. /**
  1273. * Finds the assignment property with the given name from the given ObjectPattern node.
  1274. */
  1275. findAssignmentProperty,
  1276. /**
  1277. * Checks if the given node is a property value.
  1278. * @param {Property} prop
  1279. * @param {Expression} node
  1280. */
  1281. isPropertyChain,
  1282. /**
  1283. * Retrieve `TSAsExpression#expression` value if the given node a `TSAsExpression` node. Otherwise, pass through it.
  1284. */
  1285. skipTSAsExpression,
  1286. /**
  1287. * Retrieve `AssignmentPattern#left` value if the given node a `AssignmentPattern` node. Otherwise, pass through it.
  1288. */
  1289. skipDefaultParamValue,
  1290. /**
  1291. * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
  1292. */
  1293. skipChainExpression,
  1294. /**
  1295. * Check whether the given node is `this` or variable that stores `this`.
  1296. * @param {ESNode} node The node to check
  1297. * @param {RuleContext} context The rule context to use parser services.
  1298. * @returns {boolean} `true` if the given node is `this`.
  1299. */
  1300. isThis(node, context) {
  1301. if (node.type === 'ThisExpression') {
  1302. return true
  1303. }
  1304. if (node.type !== 'Identifier') {
  1305. return false
  1306. }
  1307. const parent = node.parent
  1308. if (parent.type === 'MemberExpression') {
  1309. if (parent.property === node) {
  1310. return false
  1311. }
  1312. } else if (parent.type === 'Property') {
  1313. if (parent.key === node && !parent.computed) {
  1314. return false
  1315. }
  1316. }
  1317. const variable = findVariable(context.getScope(), node)
  1318. if (variable != null && variable.defs.length === 1) {
  1319. const def = variable.defs[0]
  1320. if (
  1321. def.type === 'Variable' &&
  1322. def.parent.kind === 'const' &&
  1323. def.node.id.type === 'Identifier'
  1324. ) {
  1325. return Boolean(
  1326. def.node && def.node.init && def.node.init.type === 'ThisExpression'
  1327. )
  1328. }
  1329. }
  1330. return false
  1331. },
  1332. /**
  1333. * @param {MemberExpression|Identifier} props
  1334. * @returns { { kind: 'assignment' | 'update' | 'call' , node: ESNode, pathNodes: MemberExpression[] } | null }
  1335. */
  1336. findMutating(props) {
  1337. /** @type {MemberExpression[]} */
  1338. const pathNodes = []
  1339. /** @type {MemberExpression | Identifier | ChainExpression} */
  1340. let node = props
  1341. let target = node.parent
  1342. while (true) {
  1343. if (target.type === 'AssignmentExpression') {
  1344. if (target.left === node) {
  1345. // this.xxx <=|+=|-=>
  1346. return {
  1347. kind: 'assignment',
  1348. node: target,
  1349. pathNodes
  1350. }
  1351. }
  1352. } else if (target.type === 'UpdateExpression') {
  1353. // this.xxx <++|-->
  1354. return {
  1355. kind: 'update',
  1356. node: target,
  1357. pathNodes
  1358. }
  1359. } else if (target.type === 'CallExpression') {
  1360. if (pathNodes.length > 0 && target.callee === node) {
  1361. const mem = pathNodes[pathNodes.length - 1]
  1362. const callName = getStaticPropertyName(mem)
  1363. if (
  1364. callName &&
  1365. /^push|pop|shift|unshift|reverse|splice|sort|copyWithin|fill$/u.exec(
  1366. callName
  1367. )
  1368. ) {
  1369. // this.xxx.push()
  1370. pathNodes.pop()
  1371. return {
  1372. kind: 'call',
  1373. node: target,
  1374. pathNodes
  1375. }
  1376. }
  1377. }
  1378. } else if (target.type === 'MemberExpression') {
  1379. if (target.object === node) {
  1380. pathNodes.push(target)
  1381. node = target
  1382. target = target.parent
  1383. continue // loop
  1384. }
  1385. } else if (target.type === 'ChainExpression') {
  1386. node = target
  1387. target = target.parent
  1388. continue // loop
  1389. }
  1390. return null
  1391. }
  1392. },
  1393. /**
  1394. * Return generator with the all handler nodes defined in the given watcher property.
  1395. * @param {Property|Expression} property
  1396. * @returns {IterableIterator<Expression>}
  1397. */
  1398. iterateWatchHandlerValues,
  1399. /**
  1400. * Wraps composition API trace map in both 'vue' and '@vue/composition-api' imports
  1401. * @param {import('eslint-utils').TYPES.TraceMap} map
  1402. */
  1403. createCompositionApiTraceMap: (map) => ({
  1404. vue: map,
  1405. '@vue/composition-api': map
  1406. }),
  1407. /**
  1408. * Checks whether or not the tokens of two given nodes are same.
  1409. * @param {ASTNode} left A node 1 to compare.
  1410. * @param {ASTNode} right A node 2 to compare.
  1411. * @param {ParserServices.TokenStore | SourceCode} sourceCode The ESLint source code object.
  1412. * @returns {boolean} the source code for the given node.
  1413. */
  1414. equalTokens(left, right, sourceCode) {
  1415. const tokensL = sourceCode.getTokens(left)
  1416. const tokensR = sourceCode.getTokens(right)
  1417. if (tokensL.length !== tokensR.length) {
  1418. return false
  1419. }
  1420. for (let i = 0; i < tokensL.length; ++i) {
  1421. if (
  1422. tokensL[i].type !== tokensR[i].type ||
  1423. tokensL[i].value !== tokensR[i].value
  1424. ) {
  1425. return false
  1426. }
  1427. }
  1428. return true
  1429. }
  1430. }
  1431. // ------------------------------------------------------------------------------
  1432. // Standard Helpers
  1433. // ------------------------------------------------------------------------------
  1434. /**
  1435. * Checks whether the given value is defined.
  1436. * @template T
  1437. * @param {T | null | undefined} v
  1438. * @returns {v is T}
  1439. */
  1440. function isDef(v) {
  1441. return v != null
  1442. }
  1443. // ------------------------------------------------------------------------------
  1444. // Rule Helpers
  1445. // ------------------------------------------------------------------------------
  1446. /**
  1447. * Register the given visitor to parser services.
  1448. * If the parser service of `vue-eslint-parser` was not found,
  1449. * this generates a warning.
  1450. *
  1451. * @param {RuleContext} context The rule context to use parser services.
  1452. * @param {TemplateListener} templateBodyVisitor The visitor to traverse the template body.
  1453. * @param {RuleListener} [scriptVisitor] The visitor to traverse the script.
  1454. * @returns {RuleListener} The merged visitor.
  1455. */
  1456. function defineTemplateBodyVisitor(
  1457. context,
  1458. templateBodyVisitor,
  1459. scriptVisitor
  1460. ) {
  1461. if (context.parserServices.defineTemplateBodyVisitor == null) {
  1462. const filename = context.getFilename()
  1463. if (path.extname(filename) === '.vue') {
  1464. context.report({
  1465. loc: { line: 1, column: 0 },
  1466. message:
  1467. 'Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error.'
  1468. })
  1469. }
  1470. return {}
  1471. }
  1472. return context.parserServices.defineTemplateBodyVisitor(
  1473. templateBodyVisitor,
  1474. scriptVisitor
  1475. )
  1476. }
  1477. /**
  1478. * @template T
  1479. * @param {T} visitor
  1480. * @param {...(TemplateListener | RuleListener | NodeListener)} visitors
  1481. * @returns {T}
  1482. */
  1483. function compositingVisitors(visitor, ...visitors) {
  1484. for (const v of visitors) {
  1485. for (const key in v) {
  1486. // @ts-expect-error
  1487. if (visitor[key]) {
  1488. // @ts-expect-error
  1489. const o = visitor[key]
  1490. // @ts-expect-error
  1491. visitor[key] = (...args) => {
  1492. o(...args)
  1493. // @ts-expect-error
  1494. v[key](...args)
  1495. }
  1496. } else {
  1497. // @ts-expect-error
  1498. visitor[key] = v[key]
  1499. }
  1500. }
  1501. }
  1502. return visitor
  1503. }
  1504. // ------------------------------------------------------------------------------
  1505. // AST Helpers
  1506. // ------------------------------------------------------------------------------
  1507. /**
  1508. * Finds the property with the given name from the given ObjectExpression node.
  1509. * @param {ObjectExpression} node
  1510. * @param {string} name
  1511. * @param { (p: Property) => boolean } [filter]
  1512. * @returns { (Property) | null}
  1513. */
  1514. function findProperty(node, name, filter) {
  1515. const predicate = filter
  1516. ? /**
  1517. * @param {Property | SpreadElement} prop
  1518. * @returns {prop is Property}
  1519. */
  1520. (prop) =>
  1521. isProperty(prop) && getStaticPropertyName(prop) === name && filter(prop)
  1522. : /**
  1523. * @param {Property | SpreadElement} prop
  1524. * @returns {prop is Property}
  1525. */
  1526. (prop) => isProperty(prop) && getStaticPropertyName(prop) === name
  1527. return node.properties.find(predicate) || null
  1528. }
  1529. /**
  1530. * Finds the assignment property with the given name from the given ObjectPattern node.
  1531. * @param {ObjectPattern} node
  1532. * @param {string} name
  1533. * @param { (p: AssignmentProperty) => boolean } [filter]
  1534. * @returns { (AssignmentProperty) | null}
  1535. */
  1536. function findAssignmentProperty(node, name, filter) {
  1537. const predicate = filter
  1538. ? /**
  1539. * @param {AssignmentProperty | RestElement} prop
  1540. * @returns {prop is AssignmentProperty}
  1541. */
  1542. (prop) =>
  1543. isAssignmentProperty(prop) &&
  1544. getStaticPropertyName(prop) === name &&
  1545. filter(prop)
  1546. : /**
  1547. * @param {AssignmentProperty | RestElement} prop
  1548. * @returns {prop is AssignmentProperty}
  1549. */
  1550. (prop) =>
  1551. isAssignmentProperty(prop) && getStaticPropertyName(prop) === name
  1552. return node.properties.find(predicate) || null
  1553. }
  1554. /**
  1555. * Checks whether the given node is Property.
  1556. * @param {Property | SpreadElement} node
  1557. * @returns {node is Property}
  1558. */
  1559. function isProperty(node) {
  1560. return node.type === 'Property'
  1561. }
  1562. /**
  1563. * Checks whether the given node is AssignmentProperty.
  1564. * @param {AssignmentProperty | RestElement} node
  1565. * @returns {node is AssignmentProperty}
  1566. */
  1567. function isAssignmentProperty(node) {
  1568. return node.type === 'Property'
  1569. }
  1570. /**
  1571. * Checks whether the given node is VElement.
  1572. * @param {VElement | VExpressionContainer | VText} node
  1573. * @returns {node is VElement}
  1574. */
  1575. function isVElement(node) {
  1576. return node.type === 'VElement'
  1577. }
  1578. /**
  1579. * Retrieve `TSAsExpression#expression` value if the given node a `TSAsExpression` node. Otherwise, pass through it.
  1580. * @template T Node type
  1581. * @param {T | TSAsExpression} node The node to address.
  1582. * @returns {T} The `TSAsExpression#expression` value if the node is a `TSAsExpression` node. Otherwise, the node.
  1583. */
  1584. function skipTSAsExpression(node) {
  1585. if (!node) {
  1586. return node
  1587. }
  1588. // @ts-expect-error
  1589. if (node.type === 'TSAsExpression') {
  1590. // @ts-expect-error
  1591. return skipTSAsExpression(node.expression)
  1592. }
  1593. // @ts-expect-error
  1594. return node
  1595. }
  1596. /**
  1597. * Gets the parent node of the given node. This method returns a value ignoring `X as F`.
  1598. * @param {Expression} node
  1599. * @returns {ASTNode}
  1600. */
  1601. function getParent(node) {
  1602. let parent = node.parent
  1603. while (parent.type === 'TSAsExpression') {
  1604. parent = parent.parent
  1605. }
  1606. return parent
  1607. }
  1608. /**
  1609. * Checks if the given node is a property value.
  1610. * @param {Property} prop
  1611. * @param {Expression} node
  1612. */
  1613. function isPropertyChain(prop, node) {
  1614. let value = node
  1615. while (value.parent.type === 'TSAsExpression') {
  1616. value = value.parent
  1617. }
  1618. return prop === value.parent && prop.value === value
  1619. }
  1620. /**
  1621. * Retrieve `AssignmentPattern#left` value if the given node a `AssignmentPattern` node. Otherwise, pass through it.
  1622. * @template T Node type
  1623. * @param {T | AssignmentPattern} node The node to address.
  1624. * @return {T} The `AssignmentPattern#left` value if the node is a `AssignmentPattern` node. Otherwise, the node.
  1625. */
  1626. function skipDefaultParamValue(node) {
  1627. if (!node) {
  1628. return node
  1629. }
  1630. // @ts-expect-error
  1631. if (node.type === 'AssignmentPattern') {
  1632. // @ts-expect-error
  1633. return skipDefaultParamValue(node.left)
  1634. }
  1635. // @ts-expect-error
  1636. return node
  1637. }
  1638. /**
  1639. * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
  1640. * @template T Node type
  1641. * @param {T | ChainExpression} node The node to address.
  1642. * @returns {T} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node.
  1643. */
  1644. function skipChainExpression(node) {
  1645. if (!node) {
  1646. return node
  1647. }
  1648. // @ts-expect-error
  1649. if (node.type === 'ChainExpression') {
  1650. // @ts-expect-error
  1651. return skipChainExpression(node.expression)
  1652. }
  1653. // @ts-expect-error
  1654. return node
  1655. }
  1656. /**
  1657. * Gets the property name of a given node.
  1658. * @param {Property|AssignmentProperty|MethodDefinition|MemberExpression} node - The node to get.
  1659. * @return {string|null} The property name if static. Otherwise, null.
  1660. */
  1661. function getStaticPropertyName(node) {
  1662. if (node.type === 'Property' || node.type === 'MethodDefinition') {
  1663. const key = node.key
  1664. if (!node.computed) {
  1665. if (key.type === 'Identifier') {
  1666. return key.name
  1667. }
  1668. }
  1669. // @ts-expect-error
  1670. return getStringLiteralValue(key)
  1671. } else if (node.type === 'MemberExpression') {
  1672. const property = node.property
  1673. if (!node.computed) {
  1674. if (property.type === 'Identifier') {
  1675. return property.name
  1676. }
  1677. return null
  1678. }
  1679. // @ts-expect-error
  1680. return getStringLiteralValue(property)
  1681. }
  1682. return null
  1683. }
  1684. /**
  1685. * Gets the string of a given node.
  1686. * @param {Literal|TemplateLiteral} node - The node to get.
  1687. * @param {boolean} [stringOnly]
  1688. * @return {string|null} The string if static. Otherwise, null.
  1689. */
  1690. function getStringLiteralValue(node, stringOnly) {
  1691. if (node.type === 'Literal') {
  1692. if (node.value == null) {
  1693. if (!stringOnly && node.bigint != null) {
  1694. return node.bigint
  1695. }
  1696. return null
  1697. }
  1698. if (typeof node.value === 'string') {
  1699. return node.value
  1700. }
  1701. if (!stringOnly) {
  1702. return String(node.value)
  1703. }
  1704. return null
  1705. }
  1706. if (node.type === 'TemplateLiteral') {
  1707. if (node.expressions.length === 0 && node.quasis.length === 1) {
  1708. return node.quasis[0].value.cooked
  1709. }
  1710. }
  1711. return null
  1712. }
  1713. // ------------------------------------------------------------------------------
  1714. // Vue Helpers
  1715. // ------------------------------------------------------------------------------
  1716. /**
  1717. * @param {string} path
  1718. */
  1719. function isVueFile(path) {
  1720. return path.endsWith('.vue') || path.endsWith('.jsx')
  1721. }
  1722. /**
  1723. * Check whether the given node is a Vue component based
  1724. * on the filename and default export type
  1725. * export default {} in .vue || .jsx
  1726. * @param {ESNode} node Node to check
  1727. * @param {string} path File name with extension
  1728. * @returns {boolean}
  1729. */
  1730. function isVueComponentFile(node, path) {
  1731. return (
  1732. isVueFile(path) &&
  1733. node.type === 'ExportDefaultDeclaration' &&
  1734. node.declaration.type === 'ObjectExpression'
  1735. )
  1736. }
  1737. /**
  1738. * Check whether given node is Vue component
  1739. * Vue.component('xxx', {}) || component('xxx', {})
  1740. * @param {ESNode} node Node to check
  1741. * @returns {boolean}
  1742. */
  1743. function isVueComponent(node) {
  1744. if (node.type === 'CallExpression') {
  1745. const callee = node.callee
  1746. if (callee.type === 'MemberExpression') {
  1747. const calleeObject = skipTSAsExpression(callee.object)
  1748. if (calleeObject.type === 'Identifier') {
  1749. const propName = getStaticPropertyName(callee)
  1750. if (calleeObject.name === 'Vue') {
  1751. // for Vue.js 2.x
  1752. // Vue.component('xxx', {}) || Vue.mixin({}) || Vue.extend('xxx', {})
  1753. const isFullVueComponentForVue2 =
  1754. propName &&
  1755. ['component', 'mixin', 'extend'].includes(propName) &&
  1756. isObjectArgument(node)
  1757. return Boolean(isFullVueComponentForVue2)
  1758. }
  1759. // for Vue.js 3.x
  1760. // app.component('xxx', {}) || app.mixin({})
  1761. const isFullVueComponent =
  1762. propName &&
  1763. ['component', 'mixin'].includes(propName) &&
  1764. isObjectArgument(node)
  1765. return Boolean(isFullVueComponent)
  1766. }
  1767. }
  1768. if (callee.type === 'Identifier') {
  1769. if (callee.name === 'component') {
  1770. // for Vue.js 2.x
  1771. // component('xxx', {})
  1772. const isDestructedVueComponent = isObjectArgument(node)
  1773. return isDestructedVueComponent
  1774. }
  1775. if (callee.name === 'createApp') {
  1776. // for Vue.js 3.x
  1777. // createApp({})
  1778. const isAppVueComponent = isObjectArgument(node)
  1779. return isAppVueComponent
  1780. }
  1781. if (callee.name === 'defineComponent') {
  1782. // for Vue.js 3.x
  1783. // defineComponent({})
  1784. const isDestructedVueComponent = isObjectArgument(node)
  1785. return isDestructedVueComponent
  1786. }
  1787. }
  1788. }
  1789. return false
  1790. /** @param {CallExpression} node */
  1791. function isObjectArgument(node) {
  1792. return (
  1793. node.arguments.length > 0 &&
  1794. skipTSAsExpression(node.arguments.slice(-1)[0]).type ===
  1795. 'ObjectExpression'
  1796. )
  1797. }
  1798. }
  1799. /**
  1800. * Check whether given node is new Vue instance
  1801. * new Vue({})
  1802. * @param {NewExpression} node Node to check
  1803. * @returns {boolean}
  1804. */
  1805. function isVueInstance(node) {
  1806. const callee = node.callee
  1807. return Boolean(
  1808. node.type === 'NewExpression' &&
  1809. callee.type === 'Identifier' &&
  1810. callee.name === 'Vue' &&
  1811. node.arguments.length &&
  1812. skipTSAsExpression(node.arguments[0]).type === 'ObjectExpression'
  1813. )
  1814. }
  1815. /**
  1816. * If the given object is a Vue component or instance, returns the Vue definition type.
  1817. * @param {RuleContext} context The ESLint rule context object.
  1818. * @param {ObjectExpression} node Node to check
  1819. * @returns { VueObjectType | null } The Vue definition type.
  1820. */
  1821. function getVueObjectType(context, node) {
  1822. if (node.type !== 'ObjectExpression') {
  1823. return null
  1824. }
  1825. const parent = getParent(node)
  1826. if (parent.type === 'ExportDefaultDeclaration') {
  1827. // export default {} in .vue || .jsx
  1828. const filePath = context.getFilename()
  1829. if (
  1830. isVueComponentFile(parent, filePath) &&
  1831. skipTSAsExpression(parent.declaration) === node
  1832. ) {
  1833. return 'export'
  1834. }
  1835. } else if (parent.type === 'CallExpression') {
  1836. // Vue.component('xxx', {}) || component('xxx', {})
  1837. if (
  1838. isVueComponent(parent) &&
  1839. skipTSAsExpression(parent.arguments.slice(-1)[0]) === node
  1840. ) {
  1841. return 'definition'
  1842. }
  1843. } else if (parent.type === 'NewExpression') {
  1844. // new Vue({})
  1845. if (
  1846. isVueInstance(parent) &&
  1847. skipTSAsExpression(parent.arguments[0]) === node
  1848. ) {
  1849. return 'instance'
  1850. }
  1851. }
  1852. if (
  1853. getComponentComments(context).some(
  1854. (el) => el.loc.end.line === node.loc.start.line - 1
  1855. )
  1856. ) {
  1857. return 'mark'
  1858. }
  1859. return null
  1860. }
  1861. /**
  1862. * Gets the component comments of a given context.
  1863. * @param {RuleContext} context The ESLint rule context object.
  1864. * @return {Token[]} The the component comments.
  1865. */
  1866. function getComponentComments(context) {
  1867. let tokens = componentComments.get(context)
  1868. if (tokens) {
  1869. return tokens
  1870. }
  1871. const sourceCode = context.getSourceCode()
  1872. tokens = sourceCode
  1873. .getAllComments()
  1874. .filter((comment) => /@vue\/component/g.test(comment.value))
  1875. componentComments.set(context, tokens)
  1876. return tokens
  1877. }
  1878. /**
  1879. * Return generator with the all handler nodes defined in the given watcher property.
  1880. * @param {Property|Expression} property
  1881. * @returns {IterableIterator<Expression>}
  1882. */
  1883. function* iterateWatchHandlerValues(property) {
  1884. const value = property.type === 'Property' ? property.value : property
  1885. if (value.type === 'ObjectExpression') {
  1886. const handler = findProperty(value, 'handler')
  1887. if (handler) {
  1888. yield handler.value
  1889. }
  1890. } else if (value.type === 'ArrayExpression') {
  1891. for (const element of value.elements.filter(isDef)) {
  1892. if (element.type !== 'SpreadElement') {
  1893. yield* iterateWatchHandlerValues(element)
  1894. }
  1895. }
  1896. } else {
  1897. yield value
  1898. }
  1899. }