ts-ast-utils.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. const { findVariable } = require('eslint-utils')
  2. /**
  3. * @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TypeNode
  4. * @typedef {import('@typescript-eslint/types').TSESTree.TSInterfaceBody} TSInterfaceBody
  5. * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} TSTypeLiteral
  6. * @typedef {import('@typescript-eslint/types').TSESTree.Parameter} TSESTreeParameter
  7. * @typedef {import('@typescript-eslint/types').TSESTree.Node} Node
  8. *
  9. */
  10. /**
  11. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeProp} ComponentTypeProp
  12. * @typedef {import('../../typings/eslint-plugin-vue/util-types/utils').ComponentTypeEmit} ComponentTypeEmit
  13. */
  14. module.exports = {
  15. isTypeNode,
  16. getComponentPropsFromTypeDefine,
  17. getComponentEmitsFromTypeDefine
  18. }
  19. /**
  20. * @param {Node | ASTNode} node
  21. * @returns {node is TypeNode}
  22. */
  23. function isTypeNode(node) {
  24. return (
  25. node.type === 'TSAnyKeyword' ||
  26. node.type === 'TSArrayType' ||
  27. node.type === 'TSBigIntKeyword' ||
  28. node.type === 'TSBooleanKeyword' ||
  29. node.type === 'TSConditionalType' ||
  30. node.type === 'TSConstructorType' ||
  31. node.type === 'TSFunctionType' ||
  32. node.type === 'TSImportType' ||
  33. node.type === 'TSIndexedAccessType' ||
  34. node.type === 'TSInferType' ||
  35. node.type === 'TSIntersectionType' ||
  36. node.type === 'TSIntrinsicKeyword' ||
  37. node.type === 'TSLiteralType' ||
  38. node.type === 'TSMappedType' ||
  39. node.type === 'TSNamedTupleMember' ||
  40. node.type === 'TSNeverKeyword' ||
  41. node.type === 'TSNullKeyword' ||
  42. node.type === 'TSNumberKeyword' ||
  43. node.type === 'TSObjectKeyword' ||
  44. node.type === 'TSOptionalType' ||
  45. node.type === 'TSRestType' ||
  46. node.type === 'TSStringKeyword' ||
  47. node.type === 'TSSymbolKeyword' ||
  48. node.type === 'TSTemplateLiteralType' ||
  49. node.type === 'TSThisType' ||
  50. node.type === 'TSTupleType' ||
  51. node.type === 'TSTypeLiteral' ||
  52. node.type === 'TSTypeOperator' ||
  53. node.type === 'TSTypePredicate' ||
  54. node.type === 'TSTypeQuery' ||
  55. node.type === 'TSTypeReference' ||
  56. node.type === 'TSUndefinedKeyword' ||
  57. node.type === 'TSUnionType' ||
  58. node.type === 'TSUnknownKeyword' ||
  59. node.type === 'TSVoidKeyword'
  60. )
  61. }
  62. /**
  63. * @param {TypeNode} node
  64. * @returns {node is TSTypeLiteral}
  65. */
  66. function isTSTypeLiteral(node) {
  67. return node.type === 'TSTypeLiteral'
  68. }
  69. /**
  70. * @param {TypeNode} node
  71. * @returns {node is TSFunctionType}
  72. */
  73. function isTSFunctionType(node) {
  74. return node.type === 'TSFunctionType'
  75. }
  76. /**
  77. * Get all props by looking at all component's properties
  78. * @param {RuleContext} context The ESLint rule context object.
  79. * @param {TypeNode} propsNode Type with props definition
  80. * @return {ComponentTypeProp[]} Array of component props
  81. */
  82. function getComponentPropsFromTypeDefine(context, propsNode) {
  83. /** @type {TSInterfaceBody | TSTypeLiteral|null} */
  84. const defNode = resolveQualifiedType(context, propsNode, isTSTypeLiteral)
  85. if (!defNode) {
  86. return []
  87. }
  88. return [...extractRuntimeProps(context, defNode)]
  89. }
  90. /**
  91. * Get all emits by looking at all component's properties
  92. * @param {RuleContext} context The ESLint rule context object.
  93. * @param {TypeNode} emitsNode Type with emits definition
  94. * @return {ComponentTypeEmit[]} Array of component emits
  95. */
  96. function getComponentEmitsFromTypeDefine(context, emitsNode) {
  97. /** @type {TSInterfaceBody | TSTypeLiteral | TSFunctionType | null} */
  98. const defNode = resolveQualifiedType(
  99. context,
  100. emitsNode,
  101. (n) => isTSTypeLiteral(n) || isTSFunctionType(n)
  102. )
  103. if (!defNode) {
  104. return []
  105. }
  106. return [...extractRuntimeEmits(defNode)]
  107. }
  108. /**
  109. * @see https://github.com/vuejs/vue-next/blob/253ca2729d808fc051215876aa4af986e4caa43c/packages/compiler-sfc/src/compileScript.ts#L1512
  110. * @param {RuleContext} context The ESLint rule context object.
  111. * @param {TSTypeLiteral | TSInterfaceBody} node
  112. * @returns {IterableIterator<ComponentTypeProp>}
  113. */
  114. function* extractRuntimeProps(context, node) {
  115. const members = node.type === 'TSTypeLiteral' ? node.members : node.body
  116. for (const m of members) {
  117. if (
  118. (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
  119. (m.key.type === 'Identifier' || m.key.type === 'Literal')
  120. ) {
  121. let type
  122. if (m.type === 'TSMethodSignature') {
  123. type = ['Function']
  124. } else if (m.typeAnnotation) {
  125. type = inferRuntimeType(context, m.typeAnnotation.typeAnnotation)
  126. }
  127. yield {
  128. type: 'type',
  129. key: /** @type {Identifier | Literal} */ (m.key),
  130. propName: m.key.type === 'Identifier' ? m.key.name : `${m.key.value}`,
  131. value: null,
  132. node: /** @type {TSPropertySignature | TSMethodSignature} */ (m),
  133. required: !m.optional,
  134. types: type || [`null`]
  135. }
  136. }
  137. }
  138. }
  139. /**
  140. * @see https://github.com/vuejs/vue-next/blob/348c3b01e56383ffa70b180d1376fdf4ac12e274/packages/compiler-sfc/src/compileScript.ts#L1632
  141. * @param {TSTypeLiteral | TSInterfaceBody | TSFunctionType} node
  142. * @returns {IterableIterator<ComponentTypeEmit>}
  143. */
  144. function* extractRuntimeEmits(node) {
  145. if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {
  146. const members = node.type === 'TSTypeLiteral' ? node.members : node.body
  147. for (const t of members) {
  148. if (t.type === 'TSCallSignatureDeclaration') {
  149. yield* extractEventNames(
  150. t.params[0],
  151. /** @type {TSCallSignatureDeclaration} */ (t)
  152. )
  153. }
  154. }
  155. return
  156. } else {
  157. yield* extractEventNames(node.params[0], node)
  158. }
  159. /**
  160. * @param {TSESTreeParameter} eventName
  161. * @param {TSCallSignatureDeclaration | TSFunctionType} member
  162. * @returns {IterableIterator<ComponentTypeEmit>}
  163. */
  164. function* extractEventNames(eventName, member) {
  165. if (
  166. eventName &&
  167. eventName.type === 'Identifier' &&
  168. eventName.typeAnnotation &&
  169. eventName.typeAnnotation.type === 'TSTypeAnnotation'
  170. ) {
  171. const typeNode = eventName.typeAnnotation.typeAnnotation
  172. if (
  173. typeNode.type === 'TSLiteralType' &&
  174. typeNode.literal.type === 'Literal'
  175. ) {
  176. const emitName = String(typeNode.literal.value)
  177. yield {
  178. type: 'type',
  179. key: /** @type {TSLiteralType} */ (typeNode),
  180. emitName,
  181. value: null,
  182. node: member
  183. }
  184. } else if (typeNode.type === 'TSUnionType') {
  185. for (const t of typeNode.types) {
  186. if (t.type === 'TSLiteralType' && t.literal.type === 'Literal') {
  187. const emitName = String(t.literal.value)
  188. yield {
  189. type: 'type',
  190. key: /** @type {TSLiteralType} */ (t),
  191. emitName,
  192. value: null,
  193. node: member
  194. }
  195. }
  196. }
  197. }
  198. }
  199. }
  200. }
  201. /**
  202. * @see https://github.com/vuejs/vue-next/blob/253ca2729d808fc051215876aa4af986e4caa43c/packages/compiler-sfc/src/compileScript.ts#L425
  203. *
  204. * @param {RuleContext} context The ESLint rule context object.
  205. * @param {TypeNode} node
  206. * @param {(n: TypeNode)=> boolean } qualifier
  207. */
  208. function resolveQualifiedType(context, node, qualifier) {
  209. if (qualifier(node)) {
  210. return node
  211. }
  212. if (node.type === 'TSTypeReference' && node.typeName.type === 'Identifier') {
  213. const refName = node.typeName.name
  214. const variable = findVariable(context.getScope(), refName)
  215. if (variable && variable.defs.length === 1) {
  216. const def = variable.defs[0]
  217. if (def.node.type === 'TSInterfaceDeclaration') {
  218. return /** @type {any} */ (def.node).body
  219. }
  220. if (def.node.type === 'TSTypeAliasDeclaration') {
  221. const typeAnnotation = /** @type {any} */ (def.node).typeAnnotation
  222. return qualifier(typeAnnotation) ? typeAnnotation : null
  223. }
  224. }
  225. }
  226. }
  227. /**
  228. * @param {RuleContext} context The ESLint rule context object.
  229. * @param {TypeNode} node
  230. * @param {Set<TypeNode>} [checked]
  231. * @returns {string[]}
  232. */
  233. function inferRuntimeType(context, node, checked = new Set()) {
  234. switch (node.type) {
  235. case 'TSStringKeyword':
  236. return ['String']
  237. case 'TSNumberKeyword':
  238. return ['Number']
  239. case 'TSBooleanKeyword':
  240. return ['Boolean']
  241. case 'TSObjectKeyword':
  242. return ['Object']
  243. case 'TSTypeLiteral':
  244. return ['Object']
  245. case 'TSFunctionType':
  246. return ['Function']
  247. case 'TSArrayType':
  248. case 'TSTupleType':
  249. return ['Array']
  250. case 'TSLiteralType':
  251. switch (node.literal.type) {
  252. //@ts-ignore ?
  253. case 'StringLiteral':
  254. return ['String']
  255. //@ts-ignore ?
  256. case 'BooleanLiteral':
  257. return ['Boolean']
  258. //@ts-ignore ?
  259. case 'NumericLiteral':
  260. //@ts-ignore ?
  261. // eslint-disable-next-line no-fallthrough
  262. case 'BigIntLiteral':
  263. return ['Number']
  264. default:
  265. return [`null`]
  266. }
  267. case 'TSTypeReference':
  268. if (node.typeName.type === 'Identifier') {
  269. const variable = findVariable(context.getScope(), node.typeName.name)
  270. if (variable && variable.defs.length === 1) {
  271. const def = variable.defs[0]
  272. if (def.node.type === 'TSInterfaceDeclaration') {
  273. return [`Object`]
  274. }
  275. if (def.node.type === 'TSTypeAliasDeclaration') {
  276. const typeAnnotation = /** @type {any} */ (def.node).typeAnnotation
  277. if (!checked.has(typeAnnotation)) {
  278. checked.add(typeAnnotation)
  279. return inferRuntimeType(context, typeAnnotation, checked)
  280. }
  281. }
  282. }
  283. switch (node.typeName.name) {
  284. case 'Array':
  285. case 'Function':
  286. case 'Object':
  287. case 'Set':
  288. case 'Map':
  289. case 'WeakSet':
  290. case 'WeakMap':
  291. case 'Date':
  292. return [node.typeName.name]
  293. case 'Record':
  294. case 'Partial':
  295. case 'Readonly':
  296. case 'Pick':
  297. case 'Omit':
  298. case 'Exclude':
  299. case 'Extract':
  300. case 'Required':
  301. case 'InstanceType':
  302. return ['Object']
  303. }
  304. }
  305. return [`null`]
  306. case 'TSUnionType':
  307. const set = new Set()
  308. for (const t of node.types) {
  309. for (const tt of inferRuntimeType(context, t, checked)) {
  310. set.add(tt)
  311. }
  312. }
  313. return [...set]
  314. case 'TSIntersectionType':
  315. return ['Object']
  316. default:
  317. return [`null`] // no runtime check
  318. }
  319. }