camelcase.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /**
  2. * @fileoverview Rule to flag non-camelcased identifiers
  3. * @author Nicholas C. Zakas
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. module.exports = {
  10. meta: {
  11. docs: {
  12. description: "enforce camelcase naming convention",
  13. category: "Stylistic Issues",
  14. recommended: false,
  15. url: "https://eslint.org/docs/rules/camelcase"
  16. },
  17. schema: [
  18. {
  19. type: "object",
  20. properties: {
  21. properties: {
  22. enum: ["always", "never"]
  23. }
  24. },
  25. additionalProperties: false
  26. }
  27. ],
  28. messages: {
  29. notCamelCase: "Identifier '{{name}}' is not in camel case."
  30. }
  31. },
  32. create(context) {
  33. //--------------------------------------------------------------------------
  34. // Helpers
  35. //--------------------------------------------------------------------------
  36. // contains reported nodes to avoid reporting twice on destructuring with shorthand notation
  37. const reported = [];
  38. const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]);
  39. /**
  40. * Checks if a string contains an underscore and isn't all upper-case
  41. * @param {string} name The string to check.
  42. * @returns {boolean} if the string is underscored
  43. * @private
  44. */
  45. function isUnderscored(name) {
  46. // if there's an underscore, it might be A_CONSTANT, which is okay
  47. return name.indexOf("_") > -1 && name !== name.toUpperCase();
  48. }
  49. /**
  50. * Reports an AST node as a rule violation.
  51. * @param {ASTNode} node The node to report.
  52. * @returns {void}
  53. * @private
  54. */
  55. function report(node) {
  56. if (reported.indexOf(node) < 0) {
  57. reported.push(node);
  58. context.report({ node, messageId: "notCamelCase", data: { name: node.name } });
  59. }
  60. }
  61. const options = context.options[0] || {};
  62. let properties = options.properties || "";
  63. if (properties !== "always" && properties !== "never") {
  64. properties = "always";
  65. }
  66. return {
  67. Identifier(node) {
  68. /*
  69. * Leading and trailing underscores are commonly used to flag
  70. * private/protected identifiers, strip them
  71. */
  72. const name = node.name.replace(/^_+|_+$/g, ""),
  73. effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
  74. // MemberExpressions get special rules
  75. if (node.parent.type === "MemberExpression") {
  76. // "never" check properties
  77. if (properties === "never") {
  78. return;
  79. }
  80. // Always report underscored object names
  81. if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && isUnderscored(name)) {
  82. report(node);
  83. // Report AssignmentExpressions only if they are the left side of the assignment
  84. } else if (effectiveParent.type === "AssignmentExpression" && isUnderscored(name) && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) {
  85. report(node);
  86. }
  87. /*
  88. * Properties have their own rules, and
  89. * AssignmentPattern nodes can be treated like Properties:
  90. * e.g.: const { no_camelcased = false } = bar;
  91. */
  92. } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
  93. if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
  94. if (node.parent.shorthand && node.parent.value.left && isUnderscored(name)) {
  95. report(node);
  96. }
  97. // prevent checking righthand side of destructured object
  98. if (node.parent.key === node && node.parent.value !== node) {
  99. return;
  100. }
  101. if (node.parent.value.name && isUnderscored(name)) {
  102. report(node);
  103. }
  104. }
  105. // "never" check properties
  106. if (properties === "never") {
  107. return;
  108. }
  109. // don't check right hand side of AssignmentExpression to prevent duplicate warnings
  110. if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) {
  111. report(node);
  112. }
  113. // Check if it's an import specifier
  114. } else if (["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"].indexOf(node.parent.type) >= 0) {
  115. // Report only if the local imported identifier is underscored
  116. if (node.parent.local && node.parent.local.name === node.name && isUnderscored(name)) {
  117. report(node);
  118. }
  119. // Report anything that is underscored that isn't a CallExpression
  120. } else if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
  121. report(node);
  122. }
  123. }
  124. };
  125. }
  126. };