no-restricted-class.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /**
  2. * @fileoverview Forbid certain classes from being used
  3. * @author Tao Bojlen
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const utils = require('../utils')
  10. // ------------------------------------------------------------------------------
  11. // Helpers
  12. // ------------------------------------------------------------------------------
  13. /**
  14. * Report a forbidden class
  15. * @param {string} className
  16. * @param {*} node
  17. * @param {RuleContext} context
  18. * @param {Set<string>} forbiddenClasses
  19. */
  20. const reportForbiddenClass = (className, node, context, forbiddenClasses) => {
  21. if (forbiddenClasses.has(className)) {
  22. const loc = node.value ? node.value.loc : node.loc
  23. context.report({
  24. node,
  25. loc,
  26. messageId: 'forbiddenClass',
  27. data: {
  28. class: className
  29. }
  30. })
  31. }
  32. }
  33. /**
  34. * @param {Expression} node
  35. * @param {boolean} [textOnly]
  36. * @returns {IterableIterator<{ className:string, reportNode: ESNode }>}
  37. */
  38. function* extractClassNames(node, textOnly) {
  39. if (node.type === 'Literal') {
  40. yield* `${node.value}`
  41. .split(/\s+/)
  42. .map((className) => ({ className, reportNode: node }))
  43. return
  44. }
  45. if (node.type === 'TemplateLiteral') {
  46. for (const templateElement of node.quasis) {
  47. yield* templateElement.value.cooked
  48. .split(/\s+/)
  49. .map((className) => ({ className, reportNode: templateElement }))
  50. }
  51. for (const expr of node.expressions) {
  52. yield* extractClassNames(expr, true)
  53. }
  54. return
  55. }
  56. if (node.type === 'BinaryExpression') {
  57. if (node.operator !== '+') {
  58. return
  59. }
  60. yield* extractClassNames(node.left, true)
  61. yield* extractClassNames(node.right, true)
  62. return
  63. }
  64. if (textOnly) {
  65. return
  66. }
  67. if (node.type === 'ObjectExpression') {
  68. for (const prop of node.properties) {
  69. if (prop.type !== 'Property') {
  70. continue
  71. }
  72. const classNames = utils.getStaticPropertyName(prop)
  73. if (!classNames) {
  74. continue
  75. }
  76. yield* classNames
  77. .split(/\s+/)
  78. .map((className) => ({ className, reportNode: prop.key }))
  79. }
  80. return
  81. }
  82. if (node.type === 'ArrayExpression') {
  83. for (const element of node.elements) {
  84. if (element == null) {
  85. continue
  86. }
  87. if (element.type === 'SpreadElement') {
  88. continue
  89. }
  90. yield* extractClassNames(element)
  91. }
  92. return
  93. }
  94. }
  95. // ------------------------------------------------------------------------------
  96. // Rule Definition
  97. // ------------------------------------------------------------------------------
  98. module.exports = {
  99. meta: {
  100. type: 'problem',
  101. docs: {
  102. description: 'disallow specific classes in Vue components',
  103. url: 'https://eslint.vuejs.org/rules/no-restricted-class.html',
  104. categories: undefined
  105. },
  106. fixable: null,
  107. messages: {
  108. forbiddenClass: "'{{class}}' class is not allowed."
  109. },
  110. schema: {
  111. type: 'array',
  112. items: {
  113. type: 'string'
  114. }
  115. }
  116. },
  117. /** @param {RuleContext} context */
  118. create(context) {
  119. const forbiddenClasses = new Set(context.options || [])
  120. return utils.defineTemplateBodyVisitor(context, {
  121. /**
  122. * @param {VAttribute & { value: VLiteral } } node
  123. */
  124. 'VAttribute[directive=false][key.name="class"]'(node) {
  125. node.value.value
  126. .split(/\s+/)
  127. .forEach((className) =>
  128. reportForbiddenClass(className, node, context, forbiddenClasses)
  129. )
  130. },
  131. /** @param {VExpressionContainer} node */
  132. "VAttribute[directive=true][key.name.name='bind'][key.argument.name='class'] > VExpressionContainer.value"(
  133. node
  134. ) {
  135. if (!node.expression) {
  136. return
  137. }
  138. for (const { className, reportNode } of extractClassNames(
  139. /** @type {Expression} */ (node.expression)
  140. )) {
  141. reportForbiddenClass(className, reportNode, context, forbiddenClasses)
  142. }
  143. }
  144. })
  145. }
  146. }