component-options-name-casing.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. /**
  2. * @author Pig Fang
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const utils = require('../utils')
  10. const casing = require('../utils/casing')
  11. // ------------------------------------------------------------------------------
  12. // Helpers
  13. // ------------------------------------------------------------------------------
  14. /**
  15. * @param {import('../../typings/eslint-plugin-vue/util-types/ast').Expression} node
  16. * @returns {string | null}
  17. */
  18. function getOptionsComponentName(node) {
  19. if (node.type === 'Identifier') {
  20. return node.name
  21. }
  22. if (node.type === 'Literal') {
  23. return typeof node.value === 'string' ? node.value : null
  24. }
  25. return null
  26. }
  27. // ------------------------------------------------------------------------------
  28. // Rule Definition
  29. // ------------------------------------------------------------------------------
  30. module.exports = {
  31. meta: {
  32. type: 'suggestion',
  33. docs: {
  34. description:
  35. 'enforce the casing of component name in `components` options',
  36. categories: undefined,
  37. url: 'https://eslint.vuejs.org/rules/component-options-name-casing.html'
  38. },
  39. fixable: 'code',
  40. hasSuggestions: true,
  41. schema: [{ enum: casing.allowedCaseOptions }],
  42. messages: {
  43. caseNotMatched: 'Component name "{{component}}" is not {{caseType}}.',
  44. possibleRenaming: 'Rename component name to be in {{caseType}}.'
  45. }
  46. },
  47. /** @param {RuleContext} context */
  48. create(context) {
  49. const caseType = context.options[0] || 'PascalCase'
  50. const canAutoFix = caseType === 'PascalCase'
  51. const checkCase = casing.getChecker(caseType)
  52. const convert = casing.getConverter(caseType)
  53. return utils.executeOnVue(context, (obj) => {
  54. const node = utils.findProperty(obj, 'components')
  55. if (!node || node.value.type !== 'ObjectExpression') {
  56. return
  57. }
  58. node.value.properties.forEach((property) => {
  59. if (property.type !== 'Property') {
  60. return
  61. }
  62. const name = getOptionsComponentName(property.key)
  63. if (!name || checkCase(name)) {
  64. return
  65. }
  66. context.report({
  67. node: property.key,
  68. messageId: 'caseNotMatched',
  69. data: {
  70. component: name,
  71. caseType
  72. },
  73. fix: canAutoFix
  74. ? (fixer) => {
  75. const converted = convert(name)
  76. return property.shorthand
  77. ? fixer.replaceText(property, `${converted}: ${name}`)
  78. : fixer.replaceText(property.key, converted)
  79. }
  80. : undefined,
  81. suggest: canAutoFix
  82. ? undefined
  83. : [
  84. {
  85. messageId: 'possibleRenaming',
  86. data: { caseType },
  87. fix: (fixer) => {
  88. const converted = convert(name)
  89. if (caseType === 'kebab-case') {
  90. return property.shorthand
  91. ? fixer.replaceText(property, `'${converted}': ${name}`)
  92. : fixer.replaceText(property.key, `'${converted}'`)
  93. }
  94. return property.shorthand
  95. ? fixer.replaceText(property, `${converted}: ${name}`)
  96. : fixer.replaceText(property.key, converted)
  97. }
  98. }
  99. ]
  100. })
  101. })
  102. })
  103. }
  104. }