enforce-style-attribute.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. /**
  2. * @author Mussin Benarbia
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const { isVElement } = require('../utils')
  7. /**
  8. * check whether a tag has the `scoped` attribute
  9. * @param {VElement} componentBlock
  10. */
  11. function isScoped(componentBlock) {
  12. return componentBlock.startTag.attributes.some(
  13. (attribute) => !attribute.directive && attribute.key.name === 'scoped'
  14. )
  15. }
  16. /**
  17. * check whether a tag has the `module` attribute
  18. * @param {VElement} componentBlock
  19. */
  20. function isModule(componentBlock) {
  21. return componentBlock.startTag.attributes.some(
  22. (attribute) => !attribute.directive && attribute.key.name === 'module'
  23. )
  24. }
  25. /**
  26. * check if a tag doesn't have either the `scoped` nor `module` attribute
  27. * @param {VElement} componentBlock
  28. */
  29. function isPlain(componentBlock) {
  30. return !isScoped(componentBlock) && !isModule(componentBlock)
  31. }
  32. /** @param {RuleContext} context */
  33. function getUserDefinedAllowedAttrs(context) {
  34. if (context.options[0] && context.options[0].allow) {
  35. return context.options[0].allow
  36. }
  37. return []
  38. }
  39. const defaultAllowedAttrs = ['scoped']
  40. module.exports = {
  41. meta: {
  42. type: 'suggestion',
  43. docs: {
  44. description:
  45. 'enforce or forbid the use of the `scoped` and `module` attributes in SFC top level style tags',
  46. categories: undefined,
  47. url: 'https://eslint.vuejs.org/rules/enforce-style-attribute.html'
  48. },
  49. fixable: null,
  50. schema: [
  51. {
  52. type: 'object',
  53. properties: {
  54. allow: {
  55. type: 'array',
  56. minItems: 1,
  57. uniqueItems: true,
  58. items: {
  59. type: 'string',
  60. enum: ['plain', 'scoped', 'module']
  61. }
  62. }
  63. },
  64. additionalProperties: false
  65. }
  66. ],
  67. messages: {
  68. notAllowedScoped:
  69. 'The scoped attribute is not allowed. Allowed: {{ allowedAttrsString }}.',
  70. notAllowedModule:
  71. 'The module attribute is not allowed. Allowed: {{ allowedAttrsString }}.',
  72. notAllowedPlain:
  73. 'Plain <style> tags are not allowed. Allowed: {{ allowedAttrsString }}.'
  74. }
  75. },
  76. /** @param {RuleContext} context */
  77. create(context) {
  78. const sourceCode = context.getSourceCode()
  79. if (!sourceCode.parserServices.getDocumentFragment) {
  80. return {}
  81. }
  82. const documentFragment = sourceCode.parserServices.getDocumentFragment()
  83. if (!documentFragment) {
  84. return {}
  85. }
  86. const topLevelStyleTags = documentFragment.children.filter(
  87. /** @returns {element is VElement} */
  88. (element) => isVElement(element) && element.rawName === 'style'
  89. )
  90. if (topLevelStyleTags.length === 0) {
  91. return {}
  92. }
  93. const userDefinedAllowedAttrs = getUserDefinedAllowedAttrs(context)
  94. const allowedAttrs =
  95. userDefinedAllowedAttrs.length > 0
  96. ? userDefinedAllowedAttrs
  97. : defaultAllowedAttrs
  98. const allowsPlain = allowedAttrs.includes('plain')
  99. const allowsScoped = allowedAttrs.includes('scoped')
  100. const allowsModule = allowedAttrs.includes('module')
  101. const allowedAttrsString = [...allowedAttrs].sort().join(', ')
  102. return {
  103. Program() {
  104. for (const styleTag of topLevelStyleTags) {
  105. if (!allowsPlain && isPlain(styleTag)) {
  106. context.report({
  107. node: styleTag,
  108. messageId: 'notAllowedPlain',
  109. data: {
  110. allowedAttrsString
  111. }
  112. })
  113. return
  114. }
  115. if (!allowsScoped && isScoped(styleTag)) {
  116. context.report({
  117. node: styleTag,
  118. messageId: 'notAllowedScoped',
  119. data: {
  120. allowedAttrsString
  121. }
  122. })
  123. return
  124. }
  125. if (!allowsModule && isModule(styleTag)) {
  126. context.report({
  127. node: styleTag,
  128. messageId: 'notAllowedModule',
  129. data: {
  130. allowedAttrsString
  131. }
  132. })
  133. return
  134. }
  135. }
  136. }
  137. }
  138. }
  139. }