no-restricted-block.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  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 { (block: VElement) => boolean } test
  11. * @property {string} [message]
  12. */
  13. /**
  14. * @param {string} str
  15. * @returns {(str: string) => boolean}
  16. */
  17. function buildMatcher(str) {
  18. if (regexp.isRegExp(str)) {
  19. const re = regexp.toRegExp(str)
  20. return (s) => {
  21. re.lastIndex = 0
  22. return re.test(s)
  23. }
  24. }
  25. return (s) => s === str
  26. }
  27. /**
  28. * @param {any} option
  29. * @returns {ParsedOption}
  30. */
  31. function parseOption(option) {
  32. if (typeof option === 'string') {
  33. const matcher = buildMatcher(option)
  34. return {
  35. test(block) {
  36. return matcher(block.rawName)
  37. }
  38. }
  39. }
  40. const parsed = parseOption(option.element)
  41. parsed.message = option.message
  42. return parsed
  43. }
  44. module.exports = {
  45. meta: {
  46. type: 'suggestion',
  47. docs: {
  48. description: 'disallow specific block',
  49. categories: undefined,
  50. url: 'https://eslint.vuejs.org/rules/no-restricted-block.html'
  51. },
  52. fixable: null,
  53. schema: {
  54. type: 'array',
  55. items: {
  56. oneOf: [
  57. { type: 'string' },
  58. {
  59. type: 'object',
  60. properties: {
  61. element: { type: 'string' },
  62. message: { type: 'string', minLength: 1 }
  63. },
  64. required: ['element'],
  65. additionalProperties: false
  66. }
  67. ]
  68. },
  69. uniqueItems: true,
  70. minItems: 0
  71. },
  72. messages: {
  73. // eslint-disable-next-line eslint-plugin/report-message-format
  74. restrictedBlock: '{{message}}'
  75. }
  76. },
  77. /** @param {RuleContext} context */
  78. create(context) {
  79. /** @type {ParsedOption[]} */
  80. const options = context.options.map(parseOption)
  81. const documentFragment =
  82. context.parserServices.getDocumentFragment &&
  83. context.parserServices.getDocumentFragment()
  84. function getTopLevelHTMLElements() {
  85. if (documentFragment) {
  86. return documentFragment.children.filter(utils.isVElement)
  87. }
  88. return []
  89. }
  90. return {
  91. /** @param {Program} node */
  92. Program(node) {
  93. if (utils.hasInvalidEOF(node)) {
  94. return
  95. }
  96. for (const block of getTopLevelHTMLElements()) {
  97. for (const option of options) {
  98. if (option.test(block)) {
  99. const message = option.message || defaultMessage(block)
  100. context.report({
  101. node: block.startTag,
  102. messageId: 'restrictedBlock',
  103. data: { message }
  104. })
  105. break
  106. }
  107. }
  108. }
  109. }
  110. }
  111. /**
  112. * @param {VElement} block
  113. */
  114. function defaultMessage(block) {
  115. return `Using \`<${block.rawName}>\` is not allowed.`
  116. }
  117. }
  118. }