mrCommitsCommitMessage.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. const {
  2. minimumSummaryChars,
  3. maximumSummaryChars,
  4. maximumBodyLineChars,
  5. allowedTypes,
  6. } = require("./mrCommitsConstants.js");
  7. const { recordRuleExitStatus } = require("./configParameters.js");
  8. /**
  9. * Check that commit messages are based on the Espressif ESP-IDF project's rules for git commit messages.
  10. *
  11. * @dangerjs WARN
  12. */
  13. module.exports = async function () {
  14. const ruleName = "Commit messages style";
  15. const mrCommits = danger.gitlab.commits;
  16. const lint = require("@commitlint/lint").default;
  17. const lintingRules = {
  18. // rule definition: [(0-1 = off/on), (always/never = must be/mustn't be), (value)]
  19. "body-max-line-length": [1, "always", maximumBodyLineChars], // Max length of the body line
  20. "footer-leading-blank": [1, "always"], // Always have a blank line before the footer section
  21. "footer-max-line-length": [1, "always", maximumBodyLineChars], // Max length of the footer line
  22. "subject-max-length": [1, "always", maximumSummaryChars], // Max length of the "Summary"
  23. "subject-min-length": [1, "always", minimumSummaryChars], // Min length of the "Summary"
  24. "scope-case": [1, "always", "lower-case"], // "scope/component" must start with lower-case
  25. "subject-full-stop": [1, "never", "."], // "Summary" must not end with a full stop (period)
  26. "subject-empty": [1, "never"], // "Summary" is mandatory
  27. "type-case": [1, "always", "lower-case"], // "type/action" must start with lower-case
  28. "type-empty": [1, "never"], // "type/action" is mandatory
  29. "type-enum": [1, "always", allowedTypes], // "type/action" must be one of the allowed types
  30. "body-leading-blank": [1, "always"], // Always have a blank line before the body section
  31. };
  32. // Switcher for AI suggestions (for poor messages)
  33. let generateAISuggestion = false;
  34. // Search for the messages in each commit
  35. let issuesAllCommitMessages = [];
  36. for (const commit of mrCommits) {
  37. const commitMessage = commit.message;
  38. const commitMessageTitle = commit.title;
  39. let issuesSingleCommitMessage = [];
  40. let reportSingleCommitMessage = "";
  41. // Check if the commit message contains any Jira ticket references
  42. const jiraTicketRegex = /[A-Z0-9]+-[0-9]+/g;
  43. const jiraTicketMatches = commitMessage.match(jiraTicketRegex);
  44. if (jiraTicketMatches) {
  45. const jiraTicketNames = jiraTicketMatches.join(", ");
  46. issuesSingleCommitMessage.push(
  47. `- probably contains Jira ticket reference (\`${jiraTicketNames}\`). Please remove Jira tickets from commit messages.`
  48. );
  49. }
  50. // Lint commit messages with @commitlint (Conventional Commits style)
  51. const result = await lint(commit.message, lintingRules);
  52. for (const warning of result.warnings) {
  53. // Custom messages for each rule with terminology used by Espressif conventional commits guide
  54. switch (warning.name) {
  55. case "subject-max-length":
  56. issuesSingleCommitMessage.push(
  57. `- *summary* appears to be too long`
  58. );
  59. break;
  60. case "type-empty":
  61. issuesSingleCommitMessage.push(
  62. `- *type/action* looks empty`
  63. );
  64. break;
  65. case "type-case":
  66. issuesSingleCommitMessage.push(
  67. `- *type/action* should start with a lowercase letter`
  68. );
  69. break;
  70. case "scope-empty":
  71. issuesSingleCommitMessage.push(
  72. `- *scope/component* looks empty`
  73. );
  74. break;
  75. case "scope-case":
  76. issuesSingleCommitMessage.push(
  77. `- *scope/component* should be lowercase without whitespace, allowed special characters are \`_\` \`/\` \`.\` \`,\` \`*\` \`-\` \`.\``
  78. );
  79. break;
  80. case "subject-empty":
  81. issuesSingleCommitMessage.push(`- *summary* looks empty`);
  82. generateAISuggestion = true;
  83. break;
  84. case "subject-min-length":
  85. issuesSingleCommitMessage.push(
  86. `- *summary* looks too short`
  87. );
  88. generateAISuggestion = true;
  89. break;
  90. case "subject-case":
  91. issuesSingleCommitMessage.push(
  92. `- *summary* should start with a capital letter`
  93. );
  94. break;
  95. case "subject-full-stop":
  96. issuesSingleCommitMessage.push(
  97. `- *summary* should not end with a period (full stop)`
  98. );
  99. break;
  100. case "type-enum":
  101. issuesSingleCommitMessage.push(
  102. `- *type/action* should be one of [${allowedTypes
  103. .map((type) => `\`${type}\``)
  104. .join(", ")}]`
  105. );
  106. break;
  107. default:
  108. issuesSingleCommitMessage.push(`- ${warning.message}`);
  109. }
  110. }
  111. if (issuesSingleCommitMessage.length) {
  112. reportSingleCommitMessage = `- the commit message \`"${commitMessageTitle}"\`:\n${issuesSingleCommitMessage
  113. .map((message) => ` ${message}`) // Indent each issue by 2 spaces
  114. .join("\n")}`;
  115. issuesAllCommitMessages.push(reportSingleCommitMessage);
  116. }
  117. }
  118. // Create report
  119. if (issuesAllCommitMessages.length) {
  120. issuesAllCommitMessages.sort();
  121. const basicTips = [
  122. `- correct format of commit message should be: \`<type/action>(<scope/component>): <summary>\`, for example \`fix(esp32): Fixed startup timeout issue\``,
  123. `- allowed types are: \`${allowedTypes}\``,
  124. `- sufficiently descriptive message summary should be between ${minimumSummaryChars} to ${maximumSummaryChars} characters and start with upper case letter`,
  125. `- avoid Jira references in commit messages (unavailable/irrelevant for our customers)`,
  126. `- follow this [commit messages guide](${process.env.DANGER_GITLAB_HOST}/espressif/esp-idf/-/wikis/dev-proc/Commit-messages)`,
  127. ];
  128. let dangerMessage = `\n**Some issues found for the commit messages in this MR:**\n${issuesAllCommitMessages.join(
  129. "\n"
  130. )}
  131. \n***
  132. \n**Please consider updating these commit messages** - here are some basic tips:\n${basicTips.join(
  133. "\n"
  134. )}
  135. \n \`TIP:\` You can install commit-msg pre-commit hook (\`pre-commit install -t pre-commit -t commit-msg\`) to run this check when committing.
  136. \n***
  137. `;
  138. if (generateAISuggestion) {
  139. // Create AI generated suggestion for git commit message based of gitDiff and current commit messages
  140. const AImessageSuggestion =
  141. await require("./aiGenerateGitMessage.js")();
  142. dangerMessage += AImessageSuggestion;
  143. }
  144. recordRuleExitStatus(ruleName, "Failed");
  145. return warn(dangerMessage);
  146. }
  147. // At this point, the rule has passed
  148. recordRuleExitStatus(ruleName, "Passed");
  149. };