no-unsupported-features.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const semver = require('semver')
  7. const utils = require('../utils')
  8. /**
  9. * @typedef {object} SyntaxRule
  10. * @property {string} supported
  11. * @property { (context: RuleContext) => TemplateListener } [createTemplateBodyVisitor]
  12. * @property { (context: RuleContext) => RuleListener } [createScriptVisitor]
  13. */
  14. const FEATURES = {
  15. // Vue.js 2.5.0+
  16. 'slot-scope-attribute': require('./syntaxes/slot-scope-attribute'),
  17. // Vue.js 2.6.0+
  18. 'dynamic-directive-arguments': require('./syntaxes/dynamic-directive-arguments'),
  19. 'v-slot': require('./syntaxes/v-slot'),
  20. // >=2.6.0-beta.1 <=2.6.0-beta.3
  21. 'v-bind-prop-modifier-shorthand': require('./syntaxes/v-bind-prop-modifier-shorthand'),
  22. // Vue.js 3.0.0+
  23. 'v-model-argument': require('./syntaxes/v-model-argument'),
  24. 'v-model-custom-modifiers': require('./syntaxes/v-model-custom-modifiers'),
  25. 'v-is': require('./syntaxes/v-is')
  26. }
  27. const SYNTAX_NAMES = /** @type {(keyof FEATURES)[]} */ (Object.keys(FEATURES))
  28. const cache = new Map()
  29. /**
  30. * Get the `semver.Range` object of a given range text.
  31. * @param {string} x The text expression for a semver range.
  32. * @returns {semver.Range} The range object of a given range text.
  33. * It's null if the `x` is not a valid range text.
  34. */
  35. function getSemverRange(x) {
  36. const s = String(x)
  37. let ret = cache.get(s) || null
  38. if (!ret) {
  39. try {
  40. ret = new semver.Range(s)
  41. } catch (_error) {
  42. // Ignore parsing error.
  43. }
  44. cache.set(s, ret)
  45. }
  46. return ret
  47. }
  48. module.exports = {
  49. meta: {
  50. type: 'suggestion',
  51. docs: {
  52. description:
  53. 'disallow unsupported Vue.js syntax on the specified version',
  54. categories: undefined,
  55. url: 'https://eslint.vuejs.org/rules/no-unsupported-features.html'
  56. },
  57. fixable: 'code',
  58. schema: [
  59. {
  60. type: 'object',
  61. properties: {
  62. version: {
  63. type: 'string'
  64. },
  65. ignores: {
  66. type: 'array',
  67. items: {
  68. enum: SYNTAX_NAMES
  69. },
  70. uniqueItems: true
  71. }
  72. },
  73. additionalProperties: false
  74. }
  75. ],
  76. messages: {
  77. // Vue.js 2.5.0+
  78. forbiddenSlotScopeAttribute:
  79. '`slot-scope` are not supported except Vue.js ">=2.5.0 <3.0.0".',
  80. // Vue.js 2.6.0+
  81. forbiddenDynamicDirectiveArguments:
  82. 'Dynamic arguments are not supported until Vue.js "2.6.0".',
  83. forbiddenVSlot: '`v-slot` are not supported until Vue.js "2.6.0".',
  84. // >=2.6.0-beta.1 <=2.6.0-beta.3
  85. forbiddenVBindPropModifierShorthand:
  86. '`.prop` shorthand are not supported except Vue.js ">=2.6.0-beta.1 <=2.6.0-beta.3".',
  87. // Vue.js 3.0.0+
  88. forbiddenVModelArgument:
  89. 'Argument on `v-model` is not supported until Vue.js "3.0.0".',
  90. forbiddenVModelCustomModifiers:
  91. 'Custom modifiers on `v-model` are not supported until Vue.js "3.0.0".',
  92. forbiddenVIs: '`v-is` are not supported until Vue.js "3.0.0".'
  93. }
  94. },
  95. /** @param {RuleContext} context */
  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 {SyntaxRule} aCase The case object to check.
  112. * @returns {boolean} `true` if it's supporting.
  113. */
  114. function isNotSupportingVersion(aCase) {
  115. return !semver.subset(versionRange, getSemverRange(aCase.supported))
  116. }
  117. /** @type {TemplateListener} */
  118. let templateBodyVisitor = {}
  119. /** @type {RuleListener} */
  120. let scriptVisitor = {}
  121. for (const syntaxName of SYNTAX_NAMES) {
  122. /** @type {SyntaxRule} */
  123. const syntax = FEATURES[syntaxName]
  124. if (ignores.includes(syntaxName) || !isNotSupportingVersion(syntax)) {
  125. continue
  126. }
  127. if (syntax.createTemplateBodyVisitor) {
  128. const visitor = syntax.createTemplateBodyVisitor(context)
  129. templateBodyVisitor = utils.compositingVisitors(
  130. templateBodyVisitor,
  131. visitor
  132. )
  133. }
  134. if (syntax.createScriptVisitor) {
  135. const visitor = syntax.createScriptVisitor(context)
  136. scriptVisitor = utils.compositingVisitors(scriptVisitor, visitor)
  137. }
  138. }
  139. return utils.defineTemplateBodyVisitor(
  140. context,
  141. templateBodyVisitor,
  142. scriptVisitor
  143. )
  144. }
  145. }