component-definition-name-casing.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. /**
  2. * @fileoverview enforce specific casing for component definition name
  3. * @author Armano
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. const casing = require('../utils/casing')
  8. const allowedCaseOptions = ['PascalCase', 'kebab-case']
  9. /**
  10. * @param {Expression | SpreadElement} node
  11. * @returns {node is (Literal | TemplateLiteral)}
  12. */
  13. function canConvert(node) {
  14. return (
  15. node.type === 'Literal' ||
  16. (node.type === 'TemplateLiteral' &&
  17. node.expressions.length === 0 &&
  18. node.quasis.length === 1)
  19. )
  20. }
  21. module.exports = {
  22. meta: {
  23. type: 'suggestion',
  24. docs: {
  25. description: 'enforce specific casing for component definition name',
  26. categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
  27. url: 'https://eslint.vuejs.org/rules/component-definition-name-casing.html'
  28. },
  29. fixable: 'code',
  30. schema: [
  31. {
  32. enum: allowedCaseOptions
  33. }
  34. ],
  35. messages: {
  36. incorrectCase: 'Property name "{{value}}" is not {{caseType}}.'
  37. }
  38. },
  39. /** @param {RuleContext} context */
  40. create(context) {
  41. const options = context.options[0]
  42. const caseType = allowedCaseOptions.includes(options)
  43. ? options
  44. : 'PascalCase'
  45. /**
  46. * @param {Literal | TemplateLiteral} node
  47. */
  48. function convertName(node) {
  49. /** @type {string} */
  50. let nodeValue
  51. /** @type {Range} */
  52. let range
  53. if (node.type === 'TemplateLiteral') {
  54. const quasis = node.quasis[0]
  55. nodeValue = quasis.value.cooked
  56. range = quasis.range
  57. } else {
  58. nodeValue = `${node.value}`
  59. range = node.range
  60. }
  61. if (!casing.getChecker(caseType)(nodeValue)) {
  62. context.report({
  63. node,
  64. messageId: 'incorrectCase',
  65. data: {
  66. value: nodeValue,
  67. caseType
  68. },
  69. fix: (fixer) =>
  70. fixer.replaceTextRange(
  71. [range[0] + 1, range[1] - 1],
  72. casing.getExactConverter(caseType)(nodeValue)
  73. )
  74. })
  75. }
  76. }
  77. return utils.compositingVisitors(
  78. utils.executeOnCallVueComponent(context, (node) => {
  79. if (node.arguments.length === 2) {
  80. const argument = node.arguments[0]
  81. if (canConvert(argument)) {
  82. convertName(argument)
  83. }
  84. }
  85. }),
  86. utils.executeOnVue(context, (obj) => {
  87. const node = utils.findProperty(obj, 'name')
  88. if (!node) return
  89. if (!canConvert(node.value)) return
  90. convertName(node.value)
  91. }),
  92. utils.defineScriptSetupVisitor(context, {
  93. onDefineOptionsEnter(node) {
  94. if (node.arguments.length === 0) return
  95. const define = node.arguments[0]
  96. if (define.type !== 'ObjectExpression') return
  97. const nameNode = utils.findProperty(define, 'name')
  98. if (!nameNode) return
  99. if (!canConvert(nameNode.value)) return
  100. convertName(nameNode.value)
  101. }
  102. })
  103. )
  104. }
  105. }