ts-ast.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. const { getScope } = require('../scope')
  2. const { findVariable } = require('@eslint-community/eslint-utils')
  3. const { inferRuntimeTypeFromTypeNode } = require('./ts-types')
  4. /**
  5. * @typedef {import('@typescript-eslint/types').TSESTree.TypeNode} TSESTreeTypeNode
  6. * @typedef {import('@typescript-eslint/types').TSESTree.TSInterfaceBody} TSESTreeTSInterfaceBody
  7. * @typedef {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} TSESTreeTSTypeLiteral
  8. * @typedef {import('@typescript-eslint/types').TSESTree.TSFunctionType} TSESTreeTSFunctionType
  9. * @typedef {import('@typescript-eslint/types').TSESTree.Parameter} TSESTreeParameter
  10. * @typedef {import('@typescript-eslint/types').TSESTree.Node} TSESTreeNode
  11. *
  12. */
  13. /**
  14. * @typedef {import('../index').ComponentTypeProp} ComponentTypeProp
  15. * @typedef {import('../index').ComponentUnknownProp} ComponentUnknownProp
  16. * @typedef {import('../index').ComponentTypeEmit} ComponentTypeEmit
  17. * @typedef {import('../index').ComponentUnknownEmit} ComponentUnknownEmit
  18. * @typedef {import('../index').ComponentTypeSlot} ComponentTypeSlot
  19. * @typedef {import('../index').ComponentUnknownSlot} ComponentUnknownSlot
  20. */
  21. const noop = Function.prototype
  22. module.exports = {
  23. isTypeNode,
  24. flattenTypeNodes,
  25. isTSInterfaceBody,
  26. isTSTypeLiteral,
  27. isTSTypeLiteralOrTSFunctionType,
  28. extractRuntimeProps,
  29. extractRuntimeEmits,
  30. extractRuntimeSlots
  31. }
  32. /**
  33. * @param {ASTNode} node
  34. * @returns {node is TypeNode}
  35. */
  36. function isTypeNode(node) {
  37. if (
  38. node.type === 'TSAbstractKeyword' ||
  39. node.type === 'TSAnyKeyword' ||
  40. node.type === 'TSAsyncKeyword' ||
  41. node.type === 'TSArrayType' ||
  42. node.type === 'TSBigIntKeyword' ||
  43. node.type === 'TSBooleanKeyword' ||
  44. node.type === 'TSConditionalType' ||
  45. node.type === 'TSConstructorType' ||
  46. node.type === 'TSDeclareKeyword' ||
  47. node.type === 'TSExportKeyword' ||
  48. node.type === 'TSFunctionType' ||
  49. node.type === 'TSImportType' ||
  50. node.type === 'TSIndexedAccessType' ||
  51. node.type === 'TSInferType' ||
  52. node.type === 'TSIntersectionType' ||
  53. node.type === 'TSIntrinsicKeyword' ||
  54. node.type === 'TSLiteralType' ||
  55. node.type === 'TSMappedType' ||
  56. node.type === 'TSNamedTupleMember' ||
  57. node.type === 'TSNeverKeyword' ||
  58. node.type === 'TSNullKeyword' ||
  59. node.type === 'TSNumberKeyword' ||
  60. node.type === 'TSObjectKeyword' ||
  61. node.type === 'TSOptionalType' ||
  62. node.type === 'TSQualifiedName' ||
  63. node.type === 'TSPrivateKeyword' ||
  64. node.type === 'TSProtectedKeyword' ||
  65. node.type === 'TSPublicKeyword' ||
  66. node.type === 'TSReadonlyKeyword' ||
  67. node.type === 'TSRestType' ||
  68. node.type === 'TSStaticKeyword' ||
  69. node.type === 'TSStringKeyword' ||
  70. node.type === 'TSSymbolKeyword' ||
  71. node.type === 'TSTemplateLiteralType' ||
  72. node.type === 'TSThisType' ||
  73. node.type === 'TSTupleType' ||
  74. node.type === 'TSTypeLiteral' ||
  75. node.type === 'TSTypeOperator' ||
  76. node.type === 'TSTypePredicate' ||
  77. node.type === 'TSTypeQuery' ||
  78. node.type === 'TSTypeReference' ||
  79. node.type === 'TSUndefinedKeyword' ||
  80. node.type === 'TSUnionType' ||
  81. node.type === 'TSUnknownKeyword' ||
  82. node.type === 'TSVoidKeyword'
  83. ) {
  84. /** @type {TypeNode['type']} for type check */
  85. const type = node.type
  86. noop(type)
  87. return true
  88. }
  89. /** @type {Exclude<ASTNode['type'], TypeNode['type']>} for type check */
  90. const type = node.type
  91. noop(type)
  92. return false
  93. }
  94. /**
  95. * @param {TSESTreeTypeNode|TSESTreeTSInterfaceBody} node
  96. * @returns {node is TSESTreeTSInterfaceBody}
  97. */
  98. function isTSInterfaceBody(node) {
  99. return node.type === 'TSInterfaceBody'
  100. }
  101. /**
  102. * @param {TSESTreeTypeNode} node
  103. * @returns {node is TSESTreeTSTypeLiteral}
  104. */
  105. function isTSTypeLiteral(node) {
  106. return node.type === 'TSTypeLiteral'
  107. }
  108. /**
  109. * @param {TSESTreeTypeNode} node
  110. * @returns {node is TSESTreeTSFunctionType}
  111. */
  112. function isTSFunctionType(node) {
  113. return node.type === 'TSFunctionType'
  114. }
  115. /**
  116. * @param {TSESTreeTypeNode} node
  117. * @returns {node is TSESTreeTSTypeLiteral | TSESTreeTSFunctionType}
  118. */
  119. function isTSTypeLiteralOrTSFunctionType(node) {
  120. return isTSTypeLiteral(node) || isTSFunctionType(node)
  121. }
  122. /**
  123. * @see https://github.com/vuejs/vue-next/blob/253ca2729d808fc051215876aa4af986e4caa43c/packages/compiler-sfc/src/compileScript.ts#L1512
  124. * @param {RuleContext} context The ESLint rule context object.
  125. * @param {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody} node
  126. * @returns {IterableIterator<ComponentTypeProp | ComponentUnknownProp>}
  127. */
  128. function* extractRuntimeProps(context, node) {
  129. const members = node.type === 'TSTypeLiteral' ? node.members : node.body
  130. for (const member of members) {
  131. if (
  132. member.type === 'TSPropertySignature' ||
  133. member.type === 'TSMethodSignature'
  134. ) {
  135. if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
  136. yield {
  137. type: 'unknown',
  138. propName: null,
  139. node: /** @type {Expression} */ (member.key)
  140. }
  141. continue
  142. }
  143. /** @type {string[]|undefined} */
  144. let types
  145. if (member.type === 'TSMethodSignature') {
  146. types = ['Function']
  147. } else if (member.typeAnnotation) {
  148. types = inferRuntimeType(context, member.typeAnnotation.typeAnnotation)
  149. }
  150. yield {
  151. type: 'type',
  152. key: /** @type {Identifier | Literal} */ (member.key),
  153. propName:
  154. member.key.type === 'Identifier'
  155. ? member.key.name
  156. : `${member.key.value}`,
  157. node: /** @type {TSPropertySignature | TSMethodSignature} */ (member),
  158. required: !member.optional,
  159. types: types || [`null`]
  160. }
  161. }
  162. }
  163. }
  164. /**
  165. * @param {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody | TSESTreeTSFunctionType} node
  166. * @returns {IterableIterator<ComponentTypeEmit | ComponentUnknownEmit>}
  167. */
  168. function* extractRuntimeEmits(node) {
  169. if (node.type === 'TSFunctionType') {
  170. yield* extractEventNames(
  171. node.params[0],
  172. /** @type {TSFunctionType} */ (node)
  173. )
  174. return
  175. }
  176. const members = node.type === 'TSTypeLiteral' ? node.members : node.body
  177. for (const member of members) {
  178. if (member.type === 'TSCallSignatureDeclaration') {
  179. yield* extractEventNames(
  180. member.params[0],
  181. /** @type {TSCallSignatureDeclaration} */ (member)
  182. )
  183. } else if (
  184. member.type === 'TSPropertySignature' ||
  185. member.type === 'TSMethodSignature'
  186. ) {
  187. if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
  188. yield {
  189. type: 'unknown',
  190. emitName: null,
  191. node: /** @type {Expression} */ (member.key)
  192. }
  193. continue
  194. }
  195. yield {
  196. type: 'type',
  197. key: /** @type {Identifier | Literal} */ (member.key),
  198. emitName:
  199. member.key.type === 'Identifier'
  200. ? member.key.name
  201. : `${member.key.value}`,
  202. node: /** @type {TSPropertySignature | TSMethodSignature} */ (member)
  203. }
  204. }
  205. }
  206. }
  207. /**
  208. * @param {TSESTreeTSTypeLiteral | TSESTreeTSInterfaceBody} node
  209. * @returns {IterableIterator<ComponentTypeSlot | ComponentUnknownSlot>}
  210. */
  211. function* extractRuntimeSlots(node) {
  212. const members = node.type === 'TSTypeLiteral' ? node.members : node.body
  213. for (const member of members) {
  214. if (
  215. member.type === 'TSPropertySignature' ||
  216. member.type === 'TSMethodSignature'
  217. ) {
  218. if (member.key.type !== 'Identifier' && member.key.type !== 'Literal') {
  219. yield {
  220. type: 'unknown',
  221. slotName: null,
  222. node: /** @type {Expression} */ (member.key)
  223. }
  224. continue
  225. }
  226. yield {
  227. type: 'type',
  228. key: /** @type {Identifier | Literal} */ (member.key),
  229. slotName:
  230. member.key.type === 'Identifier'
  231. ? member.key.name
  232. : `${member.key.value}`,
  233. node: /** @type {TSPropertySignature | TSMethodSignature} */ (member)
  234. }
  235. }
  236. }
  237. }
  238. /**
  239. * @param {TSESTreeParameter} eventName
  240. * @param {TSCallSignatureDeclaration | TSFunctionType} member
  241. * @returns {IterableIterator<ComponentTypeEmit>}
  242. */
  243. function* extractEventNames(eventName, member) {
  244. if (
  245. eventName &&
  246. eventName.type === 'Identifier' &&
  247. eventName.typeAnnotation &&
  248. eventName.typeAnnotation.type === 'TSTypeAnnotation'
  249. ) {
  250. const typeNode = eventName.typeAnnotation.typeAnnotation
  251. if (
  252. typeNode.type === 'TSLiteralType' &&
  253. typeNode.literal.type === 'Literal'
  254. ) {
  255. const emitName = String(typeNode.literal.value)
  256. yield {
  257. type: 'type',
  258. key: /** @type {TSLiteralType} */ (typeNode),
  259. emitName,
  260. node: member
  261. }
  262. } else if (typeNode.type === 'TSUnionType') {
  263. for (const t of typeNode.types) {
  264. if (t.type === 'TSLiteralType' && t.literal.type === 'Literal') {
  265. const emitName = String(t.literal.value)
  266. yield {
  267. type: 'type',
  268. key: /** @type {TSLiteralType} */ (t),
  269. emitName,
  270. node: member
  271. }
  272. }
  273. }
  274. }
  275. }
  276. }
  277. /**
  278. * @param {RuleContext} context The ESLint rule context object.
  279. * @param {TSESTreeTypeNode} node
  280. * @returns {(TSESTreeTypeNode|TSESTreeTSInterfaceBody)[]}
  281. */
  282. function flattenTypeNodes(context, node) {
  283. /**
  284. * @typedef {object} TraversedData
  285. * @property {Set<TSESTreeTypeNode|TSESTreeTSInterfaceBody>} nodes
  286. * @property {boolean} finished
  287. */
  288. /** @type {Map<TSESTreeTypeNode,TraversedData>} */
  289. const traversed = new Map()
  290. return [...flattenImpl(node)]
  291. /**
  292. * @param {TSESTreeTypeNode} node
  293. * @returns {Iterable<TSESTreeTypeNode|TSESTreeTSInterfaceBody>}
  294. */
  295. function* flattenImpl(node) {
  296. if (node.type === 'TSUnionType' || node.type === 'TSIntersectionType') {
  297. for (const typeNode of node.types) {
  298. yield* flattenImpl(typeNode)
  299. }
  300. return
  301. }
  302. if (
  303. node.type === 'TSTypeReference' &&
  304. node.typeName.type === 'Identifier'
  305. ) {
  306. const refName = node.typeName.name
  307. const variable = findVariable(
  308. getScope(context, /** @type {any} */ (node)),
  309. refName
  310. )
  311. if (variable && variable.defs.length === 1) {
  312. const defNode = /** @type {TSESTreeNode} */ (variable.defs[0].node)
  313. if (defNode.type === 'TSInterfaceDeclaration') {
  314. yield defNode.body
  315. return
  316. } else if (defNode.type === 'TSTypeAliasDeclaration') {
  317. const typeAnnotation = defNode.typeAnnotation
  318. let traversedData = traversed.get(typeAnnotation)
  319. if (traversedData) {
  320. const copy = [...traversedData.nodes]
  321. yield* copy
  322. if (!traversedData.finished) {
  323. // Include the node because it will probably be referenced recursively.
  324. yield typeAnnotation
  325. }
  326. return
  327. }
  328. traversedData = { nodes: new Set(), finished: false }
  329. traversed.set(typeAnnotation, traversedData)
  330. for (const e of flattenImpl(typeAnnotation)) {
  331. traversedData.nodes.add(e)
  332. }
  333. traversedData.finished = true
  334. yield* traversedData.nodes
  335. return
  336. }
  337. }
  338. }
  339. yield node
  340. }
  341. }
  342. /**
  343. * @param {RuleContext} context The ESLint rule context object.
  344. * @param {TSESTreeTypeNode} node
  345. * @param {Set<TSESTreeTypeNode>} [checked]
  346. * @returns {string[]}
  347. */
  348. function inferRuntimeType(context, node, checked = new Set()) {
  349. switch (node.type) {
  350. case 'TSStringKeyword':
  351. case 'TSTemplateLiteralType': {
  352. return ['String']
  353. }
  354. case 'TSNumberKeyword': {
  355. return ['Number']
  356. }
  357. case 'TSBooleanKeyword': {
  358. return ['Boolean']
  359. }
  360. case 'TSObjectKeyword': {
  361. return ['Object']
  362. }
  363. case 'TSTypeLiteral': {
  364. return inferTypeLiteralType(node)
  365. }
  366. case 'TSFunctionType': {
  367. return ['Function']
  368. }
  369. case 'TSArrayType':
  370. case 'TSTupleType': {
  371. return ['Array']
  372. }
  373. case 'TSSymbolKeyword': {
  374. return ['Symbol']
  375. }
  376. case 'TSLiteralType': {
  377. if (node.literal.type === 'Literal') {
  378. switch (typeof node.literal.value) {
  379. case 'boolean': {
  380. return ['Boolean']
  381. }
  382. case 'string': {
  383. return ['String']
  384. }
  385. case 'number':
  386. case 'bigint': {
  387. return ['Number']
  388. }
  389. }
  390. if (node.literal.value instanceof RegExp) {
  391. return ['RegExp']
  392. }
  393. }
  394. return inferRuntimeTypeFromTypeNode(
  395. context,
  396. /** @type {TypeNode} */ (node)
  397. )
  398. }
  399. case 'TSTypeReference': {
  400. if (node.typeName.type === 'Identifier') {
  401. const variable = findVariable(
  402. getScope(context, /** @type {any} */ (node)),
  403. node.typeName.name
  404. )
  405. if (variable && variable.defs.length === 1) {
  406. const defNode = /** @type {TSESTreeNode} */ (variable.defs[0].node)
  407. if (defNode.type === 'TSInterfaceDeclaration') {
  408. return [`Object`]
  409. }
  410. if (defNode.type === 'TSTypeAliasDeclaration') {
  411. const typeAnnotation = defNode.typeAnnotation
  412. if (!checked.has(typeAnnotation)) {
  413. checked.add(typeAnnotation)
  414. return inferRuntimeType(context, typeAnnotation, checked)
  415. }
  416. }
  417. if (defNode.type === 'TSEnumDeclaration') {
  418. return inferEnumType(context, defNode)
  419. }
  420. }
  421. for (const name of [
  422. node.typeName.name,
  423. ...(node.typeName.name.startsWith('Readonly')
  424. ? [node.typeName.name.slice(8)]
  425. : [])
  426. ]) {
  427. switch (name) {
  428. case 'Array':
  429. case 'Function':
  430. case 'Object':
  431. case 'Set':
  432. case 'Map':
  433. case 'WeakSet':
  434. case 'WeakMap':
  435. case 'Date': {
  436. return [name]
  437. }
  438. }
  439. }
  440. switch (node.typeName.name) {
  441. case 'Record':
  442. case 'Partial':
  443. case 'Readonly':
  444. case 'Pick':
  445. case 'Omit':
  446. case 'Required':
  447. case 'InstanceType': {
  448. return ['Object']
  449. }
  450. case 'Uppercase':
  451. case 'Lowercase':
  452. case 'Capitalize':
  453. case 'Uncapitalize': {
  454. return ['String']
  455. }
  456. case 'Parameters':
  457. case 'ConstructorParameters': {
  458. return ['Array']
  459. }
  460. case 'NonNullable': {
  461. const typeArguments =
  462. 'typeArguments' in node
  463. ? node.typeArguments
  464. : /** @type {any} typescript-eslint v5 */ (node).typeParameters
  465. if (typeArguments && typeArguments.params[0]) {
  466. return inferRuntimeType(
  467. context,
  468. typeArguments.params[0],
  469. checked
  470. ).filter((t) => t !== 'null')
  471. }
  472. break
  473. }
  474. case 'Extract': {
  475. const typeArguments =
  476. 'typeArguments' in node
  477. ? node.typeArguments
  478. : /** @type {any} typescript-eslint v5 */ (node).typeParameters
  479. if (typeArguments && typeArguments.params[1]) {
  480. return inferRuntimeType(context, typeArguments.params[1], checked)
  481. }
  482. break
  483. }
  484. case 'Exclude':
  485. case 'OmitThisParameter': {
  486. const typeArguments =
  487. 'typeArguments' in node
  488. ? node.typeArguments
  489. : /** @type {any} typescript-eslint v5 */ (node).typeParameters
  490. if (typeArguments && typeArguments.params[0]) {
  491. return inferRuntimeType(context, typeArguments.params[0], checked)
  492. }
  493. break
  494. }
  495. }
  496. }
  497. return inferRuntimeTypeFromTypeNode(
  498. context,
  499. /** @type {TypeNode} */ (node)
  500. )
  501. }
  502. case 'TSUnionType':
  503. case 'TSIntersectionType': {
  504. return inferUnionType(node)
  505. }
  506. default: {
  507. return inferRuntimeTypeFromTypeNode(
  508. context,
  509. /** @type {TypeNode} */ (node)
  510. )
  511. }
  512. }
  513. /**
  514. * @param {import('@typescript-eslint/types').TSESTree.TSUnionType|import('@typescript-eslint/types').TSESTree.TSIntersectionType} node
  515. * @returns {string[]}
  516. */
  517. function inferUnionType(node) {
  518. const types = new Set()
  519. for (const t of node.types) {
  520. for (const tt of inferRuntimeType(context, t, checked)) {
  521. types.add(tt)
  522. }
  523. }
  524. return [...types]
  525. }
  526. }
  527. /**
  528. * @param {import('@typescript-eslint/types').TSESTree.TSTypeLiteral} node
  529. * @returns {string[]}
  530. */
  531. function inferTypeLiteralType(node) {
  532. const types = new Set()
  533. for (const m of node.members) {
  534. switch (m.type) {
  535. case 'TSCallSignatureDeclaration':
  536. case 'TSConstructSignatureDeclaration': {
  537. types.add('Function')
  538. break
  539. }
  540. default: {
  541. types.add('Object')
  542. }
  543. }
  544. }
  545. return types.size > 0 ? [...types] : ['Object']
  546. }
  547. /**
  548. * @param {RuleContext} context The ESLint rule context object.
  549. * @param {import('@typescript-eslint/types').TSESTree.TSEnumDeclaration} node
  550. * @returns {string[]}
  551. */
  552. function inferEnumType(context, node) {
  553. const types = new Set()
  554. for (const m of node.members) {
  555. if (m.initializer) {
  556. if (m.initializer.type === 'Literal') {
  557. switch (typeof m.initializer.value) {
  558. case 'string': {
  559. types.add('String')
  560. break
  561. }
  562. case 'number':
  563. case 'bigint': {
  564. // Now it's a syntax error.
  565. types.add('Number')
  566. break
  567. }
  568. case 'boolean': {
  569. // Now it's a syntax error.
  570. types.add('Boolean')
  571. break
  572. }
  573. }
  574. } else {
  575. for (const type of inferRuntimeTypeFromTypeNode(
  576. context,
  577. /** @type {Expression} */ (m.initializer)
  578. )) {
  579. types.add(type)
  580. }
  581. }
  582. }
  583. }
  584. return types.size > 0 ? [...types] : ['Number']
  585. }