prefer-kebab-url.js 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. const _ = require('lodash')
  2. const { ignoreWords } = require('./prefer-kebab-url-ignore')
  3. const removeTextBetweenBrackets = text => {
  4. while (text.includes('[') || text.includes('(')) {
  5. text = text.replaceAll(/\[[^[\]]*]/g, '')
  6. text = text.replaceAll(/\([^()]*\)/g, '')
  7. }
  8. return text
  9. }
  10. const shouldIgnoreWord = str =>
  11. str.includes(':') ||
  12. str.includes('(') ||
  13. str === '*' ||
  14. str.match(/^[a-z0-9.]+$/) ||
  15. ignoreWords.snake.has(str) ||
  16. ignoreWords.camel.has(str) ||
  17. ignoreWords.other.has(str)
  18. const getSuggestion = routePath => {
  19. if (typeof routePath === 'string') {
  20. const kebabed = routePath
  21. .split('/')
  22. .map(word => (shouldIgnoreWord(word) ? word : _.kebabCase(word)))
  23. .join('/')
  24. return kebabed === routePath ? null : `'${kebabed}'`
  25. }
  26. if (routePath instanceof RegExp) {
  27. const words = removeTextBetweenBrackets(routePath.source).match(/[\w-]+/g)
  28. if (!words) return routePath
  29. let newSource = routePath.source
  30. for (const word of words) {
  31. if (!shouldIgnoreWord(word)) {
  32. newSource = newSource.replaceAll(
  33. new RegExp(`\\b${word}\\b`, 'g'),
  34. _.kebabCase(word)
  35. )
  36. }
  37. }
  38. const kebabed = new RegExp(newSource, routePath.flags)
  39. return kebabed.source.toString() === routePath.source.toString()
  40. ? null
  41. : kebabed
  42. }
  43. }
  44. module.exports = {
  45. meta: {
  46. type: 'problem',
  47. fixable: 'code',
  48. hasSuggestions: true,
  49. docs: {
  50. description: 'Enforce using kebab-case for URL paths',
  51. },
  52. },
  53. create: context => ({
  54. CallExpression(node) {
  55. if (
  56. node.callee.type === 'MemberExpression' &&
  57. node.arguments[0]?.type === 'Literal' &&
  58. [/app/i, /router/i].some(callee =>
  59. typeof callee === 'string'
  60. ? node.callee.object.name === callee
  61. : callee.test(node.callee.object.name)
  62. ) &&
  63. ['get', 'post', 'put', 'delete'].includes(node.callee.property.name)
  64. ) {
  65. const routePath = node.arguments[0].value
  66. const suggestion = getSuggestion(routePath)
  67. if (suggestion) {
  68. context.report({
  69. node: node.arguments[0],
  70. message: 'Route path should be in kebab-case.',
  71. suggest: [
  72. {
  73. desc: `Change to kebab-case: ${suggestion}`,
  74. fix: fixer => fixer.replaceText(node.arguments[0], suggestion),
  75. },
  76. ],
  77. })
  78. }
  79. }
  80. },
  81. }),
  82. }