index.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. /**
  20. * @fileoverview Rule to warn about translation template variables
  21. * @author Apache
  22. */
  23. //------------------------------------------------------------------------------
  24. // Rule Definition
  25. //------------------------------------------------------------------------------
  26. /** @type {import('eslint').Rule.RuleModule} */
  27. module.exports = {
  28. rules: {
  29. 'no-template-vars': {
  30. create(context) {
  31. function handler(node) {
  32. if (node.arguments.length) {
  33. const firstArgs = node.arguments[0];
  34. if (
  35. firstArgs.type === 'TemplateLiteral' &&
  36. firstArgs.expressions.length
  37. ) {
  38. context.report({
  39. node,
  40. message:
  41. "Don't use variables in translation string templates. Flask-babel is a static translation service, so it can't handle strings that include variables",
  42. });
  43. }
  44. }
  45. }
  46. return {
  47. "CallExpression[callee.name='t']": handler,
  48. "CallExpression[callee.name='tn']": handler,
  49. };
  50. },
  51. },
  52. 'sentence-case-buttons': {
  53. create(context) {
  54. function isTitleCase(str) {
  55. // Match "Delete Dataset", "Create Chart", etc. (2+ title-cased words)
  56. return /^[A-Z][a-z]+(\s+[A-Z][a-z]*)+$/.test(str);
  57. }
  58. function isButtonContext(node) {
  59. const { parent } = node;
  60. if (!parent) return false;
  61. // Check for button-specific props
  62. if (parent.type === 'Property') {
  63. const key = parent.key.name;
  64. return [
  65. 'primaryButtonName',
  66. 'secondaryButtonName',
  67. 'confirmButtonText',
  68. 'cancelButtonText',
  69. ].includes(key);
  70. }
  71. // Check for Button components
  72. if (parent.type === 'JSXExpressionContainer') {
  73. const jsx = parent.parent;
  74. if (jsx?.type === 'JSXElement') {
  75. const elementName = jsx.openingElement.name.name;
  76. return elementName === 'Button';
  77. }
  78. }
  79. return false;
  80. }
  81. function handler(node) {
  82. if (node.arguments.length) {
  83. const firstArg = node.arguments[0];
  84. if (
  85. firstArg.type === 'Literal' &&
  86. typeof firstArg.value === 'string'
  87. ) {
  88. const text = firstArg.value;
  89. if (isButtonContext(node) && isTitleCase(text)) {
  90. const sentenceCase = text
  91. .toLowerCase()
  92. .replace(/^\w/, c => c.toUpperCase());
  93. context.report({
  94. node: firstArg,
  95. message: `Button text should use sentence case: "${text}" should be "${sentenceCase}"`,
  96. });
  97. }
  98. }
  99. }
  100. }
  101. return {
  102. "CallExpression[callee.name='t']": handler,
  103. "CallExpression[callee.name='tn']": handler,
  104. };
  105. },
  106. },
  107. },
  108. };