no-restricted-static-attribute.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. const regexp = require('../utils/regexp')
  8. /**
  9. * @typedef {object} ParsedOption
  10. * @property { (key: VAttribute) => boolean } test
  11. * @property {boolean} [useValue]
  12. * @property {boolean} [useElement]
  13. * @property {string} [message]
  14. */
  15. /**
  16. * @param {string} str
  17. * @returns {(str: string) => boolean}
  18. */
  19. function buildMatcher(str) {
  20. if (regexp.isRegExp(str)) {
  21. const re = regexp.toRegExp(str)
  22. return (s) => {
  23. re.lastIndex = 0
  24. return re.test(s)
  25. }
  26. }
  27. return (s) => s === str
  28. }
  29. /**
  30. * @param {any} option
  31. * @returns {ParsedOption}
  32. */
  33. function parseOption(option) {
  34. if (typeof option === 'string') {
  35. const matcher = buildMatcher(option)
  36. return {
  37. test({ key }) {
  38. return matcher(key.rawName)
  39. }
  40. }
  41. }
  42. const parsed = parseOption(option.key)
  43. if (option.value) {
  44. const keyTest = parsed.test
  45. if (option.value === true) {
  46. parsed.test = (node) => {
  47. if (!keyTest(node)) {
  48. return false
  49. }
  50. return node.value == null || node.value.value === node.key.rawName
  51. }
  52. } else {
  53. const valueMatcher = buildMatcher(option.value)
  54. parsed.test = (node) => {
  55. if (!keyTest(node)) {
  56. return false
  57. }
  58. return node.value != null && valueMatcher(node.value.value)
  59. }
  60. }
  61. parsed.useValue = true
  62. }
  63. if (option.element) {
  64. const argTest = parsed.test
  65. const tagMatcher = buildMatcher(option.element)
  66. parsed.test = (node) => {
  67. if (!argTest(node)) {
  68. return false
  69. }
  70. const element = node.parent.parent
  71. return tagMatcher(element.rawName)
  72. }
  73. parsed.useElement = true
  74. }
  75. parsed.message = option.message
  76. return parsed
  77. }
  78. module.exports = {
  79. meta: {
  80. type: 'suggestion',
  81. docs: {
  82. description: 'disallow specific attribute',
  83. categories: undefined,
  84. url: 'https://eslint.vuejs.org/rules/no-restricted-static-attribute.html'
  85. },
  86. fixable: null,
  87. schema: {
  88. type: 'array',
  89. items: {
  90. oneOf: [
  91. { type: 'string' },
  92. {
  93. type: 'object',
  94. properties: {
  95. key: { type: 'string' },
  96. value: { anyOf: [{ type: 'string' }, { enum: [true] }] },
  97. element: { type: 'string' },
  98. message: { type: 'string', minLength: 1 }
  99. },
  100. required: ['key'],
  101. additionalProperties: false
  102. }
  103. ]
  104. },
  105. uniqueItems: true,
  106. minItems: 0
  107. },
  108. messages: {
  109. // eslint-disable-next-line eslint-plugin/report-message-format
  110. restrictedAttr: '{{message}}'
  111. }
  112. },
  113. /** @param {RuleContext} context */
  114. create(context) {
  115. if (!context.options.length) {
  116. return {}
  117. }
  118. /** @type {ParsedOption[]} */
  119. const options = context.options.map(parseOption)
  120. return utils.defineTemplateBodyVisitor(context, {
  121. /**
  122. * @param {VAttribute} node
  123. */
  124. 'VAttribute[directive=false]'(node) {
  125. for (const option of options) {
  126. if (option.test(node)) {
  127. const message = option.message || defaultMessage(node, option)
  128. context.report({
  129. node,
  130. messageId: 'restrictedAttr',
  131. data: { message }
  132. })
  133. return
  134. }
  135. }
  136. }
  137. })
  138. /**
  139. * @param {VAttribute} node
  140. * @param {ParsedOption} option
  141. */
  142. function defaultMessage(node, option) {
  143. const key = node.key.rawName
  144. const value = !option.useValue
  145. ? ''
  146. : node.value == null
  147. ? '` set to `true'
  148. : `="${node.value.value}"`
  149. let on = ''
  150. if (option.useElement) {
  151. on = ` on \`<${node.parent.parent.rawName}>\``
  152. }
  153. return `Using \`${key + value}\`${on} is not allowed.`
  154. }
  155. }
  156. }