match-component-file-name.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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. // ------------------------------------------------------------------------------
  7. // Requirements
  8. // ------------------------------------------------------------------------------
  9. const utils = require('../utils')
  10. const casing = require('../utils/casing')
  11. const path = require('path')
  12. // ------------------------------------------------------------------------------
  13. // Rule Definition
  14. // ------------------------------------------------------------------------------
  15. module.exports = {
  16. meta: {
  17. type: 'suggestion',
  18. docs: {
  19. description: 'require component name property to match its file name',
  20. category: undefined,
  21. url: 'https://eslint.vuejs.org/rules/match-component-file-name.html'
  22. },
  23. fixable: null,
  24. schema: [
  25. {
  26. type: 'object',
  27. properties: {
  28. extensions: {
  29. type: 'array',
  30. items: {
  31. type: 'string'
  32. },
  33. uniqueItems: true,
  34. additionalItems: false
  35. },
  36. shouldMatchCase: {
  37. type: 'boolean'
  38. }
  39. },
  40. additionalProperties: false
  41. }
  42. ]
  43. },
  44. create (context) {
  45. const options = context.options[0]
  46. const shouldMatchCase = (options && options.shouldMatchCase) || false
  47. const extensionsArray = options && options.extensions
  48. const allowedExtensions = Array.isArray(extensionsArray) ? extensionsArray : ['jsx']
  49. const extension = path.extname(context.getFilename())
  50. const filename = path.basename(context.getFilename(), extension)
  51. const errors = []
  52. let componentCount = 0
  53. if (!allowedExtensions.includes(extension.replace(/^\./, ''))) {
  54. return {}
  55. }
  56. // ----------------------------------------------------------------------
  57. // Private
  58. // ----------------------------------------------------------------------
  59. function compareNames (name, filename) {
  60. if (shouldMatchCase) {
  61. return name === filename
  62. }
  63. return casing.pascalCase(name) === filename || casing.kebabCase(name) === filename
  64. }
  65. function verifyName (node) {
  66. let name
  67. if (node.type === 'TemplateLiteral') {
  68. const quasis = node.quasis[0]
  69. name = quasis.value.cooked
  70. } else {
  71. name = node.value
  72. }
  73. if (!compareNames(name, filename)) {
  74. errors.push({
  75. node: node,
  76. message: 'Component name `{{name}}` should match file name `{{filename}}`.',
  77. data: { filename, name }
  78. })
  79. }
  80. }
  81. function canVerify (node) {
  82. return node.type === 'Literal' || (
  83. node.type === 'TemplateLiteral' &&
  84. node.expressions.length === 0 &&
  85. node.quasis.length === 1
  86. )
  87. }
  88. return Object.assign({},
  89. {
  90. "CallExpression > MemberExpression > Identifier[name='component']" (node) {
  91. const parent = node.parent.parent
  92. const calleeObject = utils.unwrapTypes(parent.callee.object)
  93. if (calleeObject.type === 'Identifier' && calleeObject.name === 'Vue') {
  94. if (parent.arguments && parent.arguments.length === 2) {
  95. const argument = parent.arguments[0]
  96. if (canVerify(argument)) {
  97. verifyName(argument)
  98. }
  99. }
  100. }
  101. }
  102. },
  103. utils.executeOnVue(context, (object) => {
  104. const node = object.properties
  105. .find(item => (
  106. item.type === 'Property' &&
  107. item.key.name === 'name' &&
  108. canVerify(item.value)
  109. ))
  110. componentCount++
  111. if (!node) return
  112. verifyName(node.value)
  113. }),
  114. {
  115. 'Program:exit' () {
  116. if (componentCount > 1) return
  117. errors.forEach((error) => context.report(error))
  118. }
  119. }
  120. )
  121. }
  122. }