match-component-file-name.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /**
  2. * @fileoverview Require component name property to match its file name
  3. * @author Rodrigo Pedra Brum <rodrigo.pedra@gmail.com>
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. const casing = require('../utils/casing')
  8. const path = require('path')
  9. /**
  10. * @param {Expression | SpreadElement} node
  11. * @returns {node is (Literal | TemplateLiteral)}
  12. */
  13. function canVerify(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. // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions
  24. hasSuggestions: true,
  25. type: 'suggestion',
  26. docs: {
  27. description: 'require component name property to match its file name',
  28. categories: undefined,
  29. url: 'https://eslint.vuejs.org/rules/match-component-file-name.html'
  30. },
  31. fixable: null,
  32. schema: [
  33. {
  34. type: 'object',
  35. properties: {
  36. extensions: {
  37. type: 'array',
  38. items: {
  39. type: 'string'
  40. },
  41. uniqueItems: true,
  42. additionalItems: false
  43. },
  44. shouldMatchCase: {
  45. type: 'boolean'
  46. }
  47. },
  48. additionalProperties: false
  49. }
  50. ]
  51. },
  52. /** @param {RuleContext} context */
  53. create(context) {
  54. const options = context.options[0]
  55. const shouldMatchCase = (options && options.shouldMatchCase) || false
  56. const extensionsArray = options && options.extensions
  57. const allowedExtensions = Array.isArray(extensionsArray)
  58. ? extensionsArray
  59. : ['jsx']
  60. const extension = path.extname(context.getFilename())
  61. const filename = path.basename(context.getFilename(), extension)
  62. /** @type {Rule.ReportDescriptor[]} */
  63. const errors = []
  64. let componentCount = 0
  65. if (!allowedExtensions.includes(extension.replace(/^\./, ''))) {
  66. return {}
  67. }
  68. /**
  69. * @param {string} name
  70. * @param {string} filename
  71. */
  72. function compareNames(name, filename) {
  73. if (shouldMatchCase) {
  74. return name === filename
  75. }
  76. return (
  77. casing.pascalCase(name) === filename ||
  78. casing.kebabCase(name) === filename
  79. )
  80. }
  81. /**
  82. * @param {Literal | TemplateLiteral} node
  83. */
  84. function verifyName(node) {
  85. let name
  86. if (node.type === 'TemplateLiteral') {
  87. const quasis = node.quasis[0]
  88. name = quasis.value.cooked
  89. } else {
  90. name = `${node.value}`
  91. }
  92. if (!compareNames(name, filename)) {
  93. errors.push({
  94. node,
  95. message:
  96. 'Component name `{{name}}` should match file name `{{filename}}`.',
  97. data: { filename, name },
  98. suggest: [
  99. {
  100. desc: 'Rename component to match file name.',
  101. fix(fixer) {
  102. const quote =
  103. node.type === 'TemplateLiteral' ? '`' : node.raw[0]
  104. return fixer.replaceText(node, `${quote}${filename}${quote}`)
  105. }
  106. }
  107. ]
  108. })
  109. }
  110. }
  111. return Object.assign(
  112. {},
  113. utils.executeOnCallVueComponent(context, (node) => {
  114. if (node.arguments.length === 2) {
  115. const argument = node.arguments[0]
  116. if (canVerify(argument)) {
  117. verifyName(argument)
  118. }
  119. }
  120. }),
  121. utils.executeOnVue(context, (object) => {
  122. const node = utils.findProperty(object, 'name')
  123. componentCount++
  124. if (!node) return
  125. if (!canVerify(node.value)) return
  126. verifyName(node.value)
  127. }),
  128. {
  129. 'Program:exit'() {
  130. if (componentCount > 1) return
  131. for (const error of errors) context.report(error)
  132. }
  133. }
  134. )
  135. }
  136. }