no-unsupported-features.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const { Range } = require('semver')
  7. const utils = require('../utils')
  8. const FEATURES = {
  9. // Vue.js 2.5.0+
  10. 'slot-scope-attribute': require('./syntaxes/slot-scope-attribute'),
  11. // Vue.js 2.6.0+
  12. 'dynamic-directive-arguments': require('./syntaxes/dynamic-directive-arguments'),
  13. 'v-slot': require('./syntaxes/v-slot'),
  14. // >=2.6.0-beta.1 <=2.6.0-beta.3
  15. 'v-bind-prop-modifier-shorthand': require('./syntaxes/v-bind-prop-modifier-shorthand')
  16. }
  17. const cache = new Map()
  18. /**
  19. * Get the `semver.Range` object of a given range text.
  20. * @param {string} x The text expression for a semver range.
  21. * @returns {Range|null} The range object of a given range text.
  22. * It's null if the `x` is not a valid range text.
  23. */
  24. function getSemverRange (x) {
  25. const s = String(x)
  26. let ret = cache.get(s) || null
  27. if (!ret) {
  28. try {
  29. ret = new Range(s)
  30. } catch (_error) {
  31. // Ignore parsing error.
  32. }
  33. cache.set(s, ret)
  34. }
  35. return ret
  36. }
  37. /**
  38. * Merge two visitors.
  39. * @param {Visitor} x The visitor which is assigned.
  40. * @param {Visitor} y The visitor which is assigning.
  41. * @returns {Visitor} `x`.
  42. */
  43. function merge (x, y) {
  44. for (const key of Object.keys(y)) {
  45. if (typeof x[key] === 'function') {
  46. if (x[key]._handlers == null) {
  47. const fs = [x[key], y[key]]
  48. x[key] = node => fs.forEach(h => h(node))
  49. x[key]._handlers = fs
  50. } else {
  51. x[key]._handlers.push(y[key])
  52. }
  53. } else {
  54. x[key] = y[key]
  55. }
  56. }
  57. return x
  58. }
  59. module.exports = {
  60. meta: {
  61. type: 'suggestion',
  62. docs: {
  63. description: 'disallow unsupported Vue.js syntax on the specified version',
  64. category: undefined,
  65. url: 'https://eslint.vuejs.org/rules/no-unsupported-features.html'
  66. },
  67. fixable: 'code',
  68. schema: [
  69. {
  70. type: 'object',
  71. properties: {
  72. version: {
  73. type: 'string'
  74. },
  75. ignores: {
  76. type: 'array',
  77. items: {
  78. enum: Object.keys(FEATURES)
  79. },
  80. uniqueItems: true
  81. }
  82. },
  83. additionalProperties: false
  84. }
  85. ],
  86. messages: {
  87. // Vue.js 2.5.0+
  88. forbiddenSlotScopeAttribute: '`slot-scope` are not supported until Vue.js "2.5.0".',
  89. // Vue.js 2.6.0+
  90. forbiddenDynamicDirectiveArguments: 'Dynamic arguments are not supported until Vue.js "2.6.0".',
  91. forbiddenVSlot: '`v-slot` are not supported until Vue.js "2.6.0".',
  92. // >=2.6.0-beta.1 <=2.6.0-beta.3
  93. forbiddenVBindPropModifierShorthand: '`.prop` shorthand are not supported except Vue.js ">=2.6.0-beta.1 <=2.6.0-beta.3".'
  94. }
  95. },
  96. create (context) {
  97. const { version, ignores } = Object.assign(
  98. {
  99. version: null,
  100. ignores: []
  101. },
  102. context.options[0] || {}
  103. )
  104. if (!version) {
  105. // version is not set.
  106. return {}
  107. }
  108. const versionRange = getSemverRange(version)
  109. /**
  110. * Check whether a given case object is full-supported on the configured node version.
  111. * @param {{supported:string}} aCase The case object to check.
  112. * @returns {boolean} `true` if it's supporting.
  113. */
  114. function isNotSupportingVersion (aCase) {
  115. if (typeof aCase.supported === 'function') {
  116. return !aCase.supported(versionRange)
  117. }
  118. return versionRange.intersects(getSemverRange(`<${aCase.supported}`))
  119. }
  120. const templateBodyVisitor = Object.keys(FEATURES)
  121. .filter(syntaxName => !ignores.includes(syntaxName))
  122. .filter(syntaxName => isNotSupportingVersion(FEATURES[syntaxName]))
  123. .reduce((result, syntaxName) => {
  124. const visitor = FEATURES[syntaxName].createTemplateBodyVisitor(context)
  125. if (visitor) {
  126. merge(result, visitor)
  127. }
  128. return result
  129. }, {})
  130. return utils.defineTemplateBodyVisitor(context, templateBodyVisitor)
  131. }
  132. }