| // Copyright 2017-2021 The Verible Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "common/analysis/violation_handler.h" |
| |
| #include "absl/status/status.h" |
| #include "common/strings/diff.h" |
| #include "common/util/file_util.h" |
| #include "common/util/user_interaction.h" |
| |
| namespace verible { |
| namespace { |
| |
| void PrintFix(std::ostream& stream, absl::string_view text, |
| const verible::AutoFix& fix) { |
| std::string after = fix.Apply(text); |
| verible::LineDiffs diff(text, after); |
| |
| verible::LineDiffsToUnifiedDiff(stream, diff, 1); |
| } |
| |
| void PrintFixAlternatives(std::ostream& stream, absl::string_view text, |
| const std::vector<verible::AutoFix>& fixes) { |
| const bool print_alternative_number = fixes.size() > 1; |
| for (size_t i = 0; i < fixes.size(); ++i) { |
| if (print_alternative_number) { |
| stream << verible::term::inverse(absl::StrCat( |
| "[ ", (i + 1), ". Alternative ", fixes[i].Description(), " ]\n")); |
| } else { |
| stream << verible::term::inverse( |
| absl::StrCat("[ ", fixes[i].Description(), " ]\n")); |
| } |
| PrintFix(stream, text, fixes[i]); |
| } |
| } |
| |
| } // namespace |
| |
| void ViolationPrinter::HandleViolations( |
| const std::set<LintViolationWithStatus>& violations, absl::string_view base, |
| absl::string_view path) { |
| verible::LintStatusFormatter formatter(base); |
| for (auto violation : violations) { |
| formatter.FormatViolation(stream_, *violation.violation, base, path, |
| violation.status->url, |
| violation.status->lint_rule_name); |
| (*stream_) << std::endl; |
| } |
| } |
| |
| void ViolationWaiverPrinter::HandleViolations( |
| const std::set<LintViolationWithStatus>& violations, absl::string_view base, |
| absl::string_view path) { |
| verible::LintStatusFormatter formatter(base); |
| for (auto violation : violations) { |
| formatter.FormatViolation(message_stream_, *violation.violation, base, path, |
| violation.status->url, |
| violation.status->lint_rule_name); |
| (*message_stream_) << std::endl; |
| |
| formatter.FormatViolationWaiver(waiver_stream_, *violation.violation, base, |
| path, violation.status->lint_rule_name); |
| (*waiver_stream_) << std::endl; |
| } |
| } |
| |
| void ViolationFixer::CommitFixes(absl::string_view source_content, |
| absl::string_view source_path, |
| const verible::AutoFix& fix) const { |
| if (fix.Edits().empty()) { |
| return; |
| } |
| std::string fixed_content = fix.Apply(source_content); |
| |
| if (patch_stream_) { |
| verible::LineDiffs diff(source_content, fixed_content); |
| verible::LineDiffsToUnifiedDiff(*patch_stream_, diff, 1, source_path); |
| } else { |
| const absl::Status write_status = |
| verible::file::SetContents(source_path, fixed_content); |
| if (!write_status.ok()) { |
| LOG(ERROR) << "Failed to write fixes to file '" << source_path |
| << "': " << write_status.ToString(); |
| return; |
| } |
| } |
| } |
| |
| void ViolationFixer::HandleViolations( |
| const std::set<LintViolationWithStatus>& violations, absl::string_view base, |
| absl::string_view path) { |
| verible::AutoFix fix; |
| verible::LintStatusFormatter formatter(base); |
| for (auto violation : violations) { |
| HandleViolation(*violation.violation, base, path, violation.status->url, |
| violation.status->lint_rule_name, formatter, &fix); |
| } |
| |
| CommitFixes(base, path, fix); |
| } |
| |
| void ViolationFixer::HandleViolation( |
| const verible::LintViolation& violation, absl::string_view base, |
| absl::string_view path, absl::string_view url, absl::string_view rule_name, |
| const verible::LintStatusFormatter& formatter, verible::AutoFix* fix) { |
| std::stringstream violation_message; |
| formatter.FormatViolation(&violation_message, violation, base, path, url, |
| rule_name); |
| (*message_stream_) << violation_message.str() << std::endl; |
| |
| if (violation.autofixes.empty()) { |
| return; |
| } |
| |
| static absl::string_view previous_fix_conflict = |
| "The fix conflicts with " |
| "previously applied fixes, rejecting.\n"; |
| |
| Answer answer; |
| for (bool first_round = true; /**/; first_round = false) { |
| if (ultimate_answer_.choice != AnswerChoice::kUnknown) { |
| answer = ultimate_answer_; |
| } else if (auto found = rule_answers_.find(rule_name); |
| found != rule_answers_.end()) { |
| answer = found->second; |
| // If the ApplyAll specifies alternative not available here, use first. |
| if (answer.alternative >= violation.autofixes.size()) { |
| answer.alternative = 0; |
| } |
| } else { |
| if (is_interactive_ && first_round) { // Show the user what is available. |
| PrintFixAlternatives(*message_stream_, base, violation.autofixes); |
| } |
| answer = answer_chooser_(violation, rule_name); |
| } |
| |
| switch (answer.choice) { |
| case AnswerChoice::kApplyAll: |
| ultimate_answer_ = {AnswerChoice::kApply}; |
| [[fallthrough]]; |
| case AnswerChoice::kApplyAllForRule: |
| rule_answers_[rule_name] = {AnswerChoice::kApply, answer.alternative}; |
| [[fallthrough]]; |
| case AnswerChoice::kApply: // Apply fix chosen in the alternatative |
| if (answer.alternative >= violation.autofixes.size()) |
| continue; // ask again. |
| if (!fix->AddEdits(violation.autofixes[answer.alternative].Edits())) { |
| *message_stream_ << previous_fix_conflict; |
| } |
| break; |
| case AnswerChoice::kRejectAll: |
| ultimate_answer_ = {AnswerChoice::kReject}; |
| [[fallthrough]]; |
| case AnswerChoice::kRejectAllForRule: |
| rule_answers_[rule_name] = {AnswerChoice::kReject}; |
| [[fallthrough]]; |
| case AnswerChoice::kReject: |
| return; |
| |
| case AnswerChoice::kPrintFix: |
| PrintFixAlternatives(*message_stream_, base, violation.autofixes); |
| continue; |
| case AnswerChoice::kPrintAppliedFixes: |
| PrintFix(*message_stream_, base, *fix); |
| continue; |
| |
| default: |
| continue; |
| } |
| |
| break; |
| } |
| } |
| |
| ViolationFixer::Answer ViolationFixer::InteractiveAnswerChooser( |
| const verible::LintViolation& violation, absl::string_view rule_name) { |
| static absl::string_view fixed_help_message = |
| "n - reject fix\n" |
| "a - apply this and all remaining fixes for violations of this rule\n" |
| "d - reject this and all remaining fixes for violations of this rule\n" |
| "A - apply this and all remaining fixes\n" |
| "D - reject this and all remaining fixes\n" |
| "p - show fix\n" |
| "P - show fixes applied in this file so far\n" |
| "? - print this help and prompt again\n"; |
| |
| const size_t fix_count = violation.autofixes.size(); |
| std::string help_message; |
| std::string alternative_list; // Show alternatives in the short-menu. |
| if (fix_count > 1) { |
| help_message = |
| absl::StrCat("y - apply first fix\n[1-", fix_count, |
| "] - apply given alternative\n", fixed_help_message); |
| for (size_t i = 0; i < fix_count; ++i) |
| absl::StrAppend(&alternative_list, (i + 1), ","); |
| } else { |
| help_message = absl::StrCat("y - apply fix\n", fixed_help_message); |
| } |
| |
| for (;;) { |
| const char c = verible::ReadCharFromUser( |
| std::cin, std::cerr, verible::IsInteractiveTerminalSession(), |
| verible::term::bold("Autofix is available. Apply? [" + |
| alternative_list + "y,n,a,d,A,D,p,P,?] ")); |
| |
| // Single character digit chooses the available alternative. |
| if (c >= '1' && c <= '9' && |
| c < static_cast<char>('1' + violation.autofixes.size())) { |
| return {AnswerChoice::kApply, static_cast<size_t>(c - '1')}; |
| } |
| |
| switch (c) { |
| case 'y': |
| return {AnswerChoice::kApply, 0}; |
| |
| // TODO(hzeller): Should we provide a way to choose 'all for rule' |
| // including an alternative ? Maybe with a two-letter response |
| // such as 1a, 2a, 3a ? Current assumption of interaction is |
| // single character. |
| case 'a': |
| return {AnswerChoice::kApplyAllForRule}; |
| case 'A': |
| return {AnswerChoice::kApplyAll}; // No alternatives |
| case 'n': |
| return {AnswerChoice::kReject}; |
| case 'd': |
| return {AnswerChoice::kRejectAllForRule}; |
| case 'D': |
| return {AnswerChoice::kRejectAll}; |
| |
| case '\0': |
| // EOF: received when too few "answers" have been piped to stdin. |
| std::cerr << "Received EOF while there are questions left. " |
| << "Rejecting all remaining fixes." << std::endl; |
| return {AnswerChoice::kRejectAll}; |
| |
| case 'p': |
| return {AnswerChoice::kPrintFix}; |
| case 'P': |
| return {AnswerChoice::kPrintAppliedFixes}; |
| |
| case '\n': |
| continue; |
| |
| case '?': |
| default: |
| std::cerr << help_message << std::endl; |
| continue; |
| } |
| } |
| } |
| |
| } // namespace verible |