no-unsupported-features.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  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. // Vue.js 2.7.0+
  21. 'script-setup': require('./syntaxes/script-setup'),
  22. 'style-css-vars-injection': require('./syntaxes/style-css-vars-injection'),
  23. // Vue.js 3.0.0+
  24. 'v-model-argument': require('./syntaxes/v-model-argument'),
  25. 'v-model-custom-modifiers': require('./syntaxes/v-model-custom-modifiers'),
  26. 'v-is': require('./syntaxes/v-is'),
  27. // Vue.js 3.1.0+
  28. 'is-attribute-with-vue-prefix': require('./syntaxes/is-attribute-with-vue-prefix'),
  29. // Vue.js 3.2.0+
  30. 'v-memo': require('./syntaxes/v-memo'),
  31. 'v-bind-prop-modifier-shorthand': require('./syntaxes/v-bind-prop-modifier-shorthand'),
  32. 'v-bind-attr-modifier': require('./syntaxes/v-bind-attr-modifier'),
  33. // Vue.js 3.3.0+
  34. 'define-options': require('./syntaxes/define-options'),
  35. 'define-slots': require('./syntaxes/define-slots')
  36. }
  37. const SYNTAX_NAMES = /** @type {(keyof FEATURES)[]} */ (Object.keys(FEATURES))
  38. const cache = new Map()
  39. /**
  40. * Get the `semver.Range` object of a given range text.
  41. * @param {string} x The text expression for a semver range.
  42. * @returns {semver.Range} The range object of a given range text.
  43. * It's null if the `x` is not a valid range text.
  44. */
  45. function getSemverRange(x) {
  46. const s = String(x)
  47. let ret = cache.get(s) || null
  48. if (!ret) {
  49. try {
  50. ret = new semver.Range(s)
  51. } catch (_error) {
  52. // Ignore parsing error.
  53. }
  54. cache.set(s, ret)
  55. }
  56. return ret
  57. }
  58. module.exports = {
  59. meta: {
  60. type: 'suggestion',
  61. docs: {
  62. description:
  63. 'disallow unsupported Vue.js syntax on the specified version',
  64. categories: 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: SYNTAX_NAMES
  79. },
  80. uniqueItems: true
  81. }
  82. },
  83. additionalProperties: false
  84. }
  85. ],
  86. messages: {
  87. // Vue.js 2.5.0+
  88. forbiddenSlotScopeAttribute:
  89. '`slot-scope` are not supported except Vue.js ">=2.5.0 <3.0.0".',
  90. // Vue.js 2.6.0+
  91. forbiddenDynamicDirectiveArguments:
  92. 'Dynamic arguments are not supported until Vue.js "2.6.0".',
  93. forbiddenVSlot: '`v-slot` are not supported until Vue.js "2.6.0".',
  94. // Vue.js 2.7.0+
  95. forbiddenScriptSetup:
  96. '`<script setup>` is not supported until Vue.js "2.7.0".',
  97. forbiddenStyleCssVarsInjection:
  98. 'SFC CSS variable injection is not supported until Vue.js ">=3.0.3 || >=2.7.0 <3.0.0".',
  99. // Vue.js 3.0.0+
  100. forbiddenVModelArgument:
  101. 'Argument on `v-model` is not supported until Vue.js "3.0.0".',
  102. forbiddenVModelCustomModifiers:
  103. 'Custom modifiers on `v-model` are not supported until Vue.js "3.0.0".',
  104. forbiddenVIs: '`v-is` are not supported until Vue.js "3.0.0".',
  105. // Vue.js 3.1.0+
  106. forbiddenIsAttributeWithVuePrefix:
  107. '`is="vue:"` are not supported until Vue.js "3.1.0".',
  108. // Vue.js 3.2.0+
  109. forbiddenVMemo: '`v-memo` are not supported until Vue.js "3.2.0".',
  110. forbiddenVBindPropModifierShorthand:
  111. '`.prop` shorthand are not supported until Vue.js "3.2.0".',
  112. forbiddenVBindAttrModifier:
  113. '`.attr` modifiers on `v-bind` are not supported until Vue.js "3.2.0".',
  114. // Vue.js 3.3.0+
  115. forbiddenDefineOptions:
  116. '`defineOptions()` macros are not supported until Vue.js "3.3.0".',
  117. forbiddenDefineSlots:
  118. '`defineSlots()` macros are not supported until Vue.js "3.3.0".'
  119. }
  120. },
  121. /** @param {RuleContext} context */
  122. create(context) {
  123. const { version, ignores } = Object.assign(
  124. {
  125. version: null,
  126. ignores: []
  127. },
  128. context.options[0] || {}
  129. )
  130. if (!version) {
  131. // version is not set.
  132. return {}
  133. }
  134. const versionRange = getSemverRange(version)
  135. /**
  136. * Check whether a given case object is full-supported on the configured node version.
  137. * @param {SyntaxRule} aCase The case object to check.
  138. * @returns {boolean} `true` if it's supporting.
  139. */
  140. function isNotSupportingVersion(aCase) {
  141. return !semver.subset(versionRange, getSemverRange(aCase.supported))
  142. }
  143. /** @type {TemplateListener} */
  144. let templateBodyVisitor = {}
  145. /** @type {RuleListener} */
  146. let scriptVisitor = {}
  147. for (const syntaxName of SYNTAX_NAMES) {
  148. /** @type {SyntaxRule} */
  149. const syntax = FEATURES[syntaxName]
  150. if (ignores.includes(syntaxName) || !isNotSupportingVersion(syntax)) {
  151. continue
  152. }
  153. if (syntax.createTemplateBodyVisitor) {
  154. const visitor = syntax.createTemplateBodyVisitor(context)
  155. templateBodyVisitor = utils.compositingVisitors(
  156. templateBodyVisitor,
  157. visitor
  158. )
  159. }
  160. if (syntax.createScriptVisitor) {
  161. const visitor = syntax.createScriptVisitor(context)
  162. scriptVisitor = utils.compositingVisitors(scriptVisitor, visitor)
  163. }
  164. }
  165. return utils.defineTemplateBodyVisitor(
  166. context,
  167. templateBodyVisitor,
  168. scriptVisitor
  169. )
  170. }
  171. }