no-boolean-default.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /**
  2. * @fileoverview Prevents boolean defaults from being set
  3. * @author Hiroki Osame
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. /**
  8. * @typedef {import('../utils').ComponentProp} ComponentProp
  9. * @typedef {import('../utils').ComponentObjectProp} ComponentObjectProp
  10. */
  11. /**
  12. * @param {Expression|undefined} node
  13. */
  14. function isBooleanIdentifier(node) {
  15. return Boolean(node && node.type === 'Identifier' && node.name === 'Boolean')
  16. }
  17. /**
  18. * Detects whether given prop node is a Boolean
  19. * @param {ComponentObjectProp} prop
  20. * @return {Boolean}
  21. */
  22. function isBooleanProp(prop) {
  23. const value = utils.skipTSAsExpression(prop.value)
  24. return (
  25. isBooleanIdentifier(value) ||
  26. (value.type === 'ObjectExpression' &&
  27. isBooleanIdentifier(utils.findProperty(value, 'type')?.value))
  28. )
  29. }
  30. /**
  31. * @param {ObjectExpression} propDefValue
  32. */
  33. function getDefaultNode(propDefValue) {
  34. return utils.findProperty(propDefValue, 'default')
  35. }
  36. module.exports = {
  37. meta: {
  38. type: 'suggestion',
  39. docs: {
  40. description: 'disallow boolean defaults',
  41. categories: undefined,
  42. url: 'https://eslint.vuejs.org/rules/no-boolean-default.html'
  43. },
  44. fixable: null,
  45. schema: [
  46. {
  47. enum: ['default-false', 'no-default']
  48. }
  49. ],
  50. messages: {
  51. noBooleanDefault:
  52. 'Boolean prop should not set a default (Vue defaults it to false).',
  53. defaultFalse: 'Boolean prop should only be defaulted to false.'
  54. }
  55. },
  56. /** @param {RuleContext} context */
  57. create(context) {
  58. const booleanType = context.options[0] || 'no-default'
  59. /**
  60. * @param {ComponentProp} prop
  61. * @param {(propName: string) => Expression[]} otherDefaultProvider
  62. */
  63. function processProp(prop, otherDefaultProvider) {
  64. if (prop.type === 'object') {
  65. if (!isBooleanProp(prop)) {
  66. return
  67. }
  68. if (prop.value.type === 'ObjectExpression') {
  69. const defaultNode = getDefaultNode(prop.value)
  70. if (defaultNode) {
  71. verifyDefaultExpression(defaultNode.value)
  72. }
  73. }
  74. if (prop.propName != null) {
  75. for (const defaultNode of otherDefaultProvider(prop.propName)) {
  76. verifyDefaultExpression(defaultNode)
  77. }
  78. }
  79. } else if (prop.type === 'type') {
  80. if (prop.types.length !== 1 || prop.types[0] !== 'Boolean') {
  81. return
  82. }
  83. for (const defaultNode of otherDefaultProvider(prop.propName)) {
  84. verifyDefaultExpression(defaultNode)
  85. }
  86. }
  87. }
  88. /**
  89. * @param {ComponentProp[]} props
  90. * @param {(propName: string) => Expression[]} otherDefaultProvider
  91. */
  92. function processProps(props, otherDefaultProvider) {
  93. for (const prop of props) {
  94. processProp(prop, otherDefaultProvider)
  95. }
  96. }
  97. /**
  98. * @param {Expression} defaultNode
  99. */
  100. function verifyDefaultExpression(defaultNode) {
  101. switch (booleanType) {
  102. case 'no-default': {
  103. context.report({
  104. node: defaultNode,
  105. messageId: 'noBooleanDefault'
  106. })
  107. break
  108. }
  109. case 'default-false': {
  110. if (defaultNode.type !== 'Literal' || defaultNode.value !== false) {
  111. context.report({
  112. node: defaultNode,
  113. messageId: 'defaultFalse'
  114. })
  115. }
  116. break
  117. }
  118. }
  119. }
  120. return utils.compositingVisitors(
  121. utils.executeOnVueComponent(context, (obj) => {
  122. processProps(utils.getComponentPropsFromOptions(obj), () => [])
  123. }),
  124. utils.defineScriptSetupVisitor(context, {
  125. onDefinePropsEnter(node, props) {
  126. const defaultsByWithDefaults =
  127. utils.getWithDefaultsPropExpressions(node)
  128. const defaultsByAssignmentPatterns =
  129. utils.getDefaultPropExpressionsForPropsDestructure(node)
  130. processProps(props, (propName) =>
  131. [
  132. defaultsByWithDefaults[propName],
  133. defaultsByAssignmentPatterns[propName]?.expression
  134. ].filter(utils.isDef)
  135. )
  136. }
  137. })
  138. )
  139. }
  140. }