From d8e5595e42ece3df79e04e8b76e5f646b2b2367f Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Fri, 6 Dec 2024 15:11:17 +0100 Subject: [PATCH 1/3] Add GitHub Actions formatter --- .../github_actions_formatter_spec.cr | 30 ++++++++++++ src/ameba/config.cr | 13 ++--- .../formatter/github_actions_formatter.cr | 48 +++++++++++++++++++ 3 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 spec/ameba/formatter/github_actions_formatter_spec.cr create mode 100644 src/ameba/formatter/github_actions_formatter.cr diff --git a/spec/ameba/formatter/github_actions_formatter_spec.cr b/spec/ameba/formatter/github_actions_formatter_spec.cr new file mode 100644 index 000000000..8268390e0 --- /dev/null +++ b/spec/ameba/formatter/github_actions_formatter_spec.cr @@ -0,0 +1,30 @@ +require "../../spec_helper" + +module Ameba::Formatter + describe GitHubActionsFormatter do + describe "#source_finished" do + it "writes valid source" do + output = IO::Memory.new + subject = GitHubActionsFormatter.new(output) + + source = Source.new "", "/path/to/file.cr" + + subject.source_finished(source) + output.to_s.should be_empty + end + + it "writes invalid source" do + output = IO::Memory.new + subject = GitHubActionsFormatter.new(output) + + source = Source.new "", "/path/to/file.cr" + location = Crystal::Location.new("/path/to/file.cr", 1, 2) + + source.add_issue DummyRule.new, location, location, "message\n2nd line" + + subject.source_finished(source) + output.to_s.should eq("::notice file=/path/to/file.cr,line=1,col=2,endLine=1,endColumn=2,title=Ameba/DummyRule::message\\n2nd line\n") + end + end + end +end diff --git a/src/ameba/config.cr b/src/ameba/config.cr index 2c0b84f8e..89a710e53 100644 --- a/src/ameba/config.cr +++ b/src/ameba/config.cr @@ -39,12 +39,13 @@ class Ameba::Config include GlobUtils AVAILABLE_FORMATTERS = { - progress: Formatter::DotFormatter, - todo: Formatter::TODOFormatter, - flycheck: Formatter::FlycheckFormatter, - silent: Formatter::BaseFormatter, - disabled: Formatter::DisabledFormatter, - json: Formatter::JSONFormatter, + progress: Formatter::DotFormatter, + todo: Formatter::TODOFormatter, + flycheck: Formatter::FlycheckFormatter, + silent: Formatter::BaseFormatter, + disabled: Formatter::DisabledFormatter, + json: Formatter::JSONFormatter, + "github-actions": Formatter::GitHubActionsFormatter, } XDG_CONFIG_HOME = ENV.fetch("XDG_CONFIG_HOME", "~/.config") diff --git a/src/ameba/formatter/github_actions_formatter.cr b/src/ameba/formatter/github_actions_formatter.cr new file mode 100644 index 000000000..711bf85d3 --- /dev/null +++ b/src/ameba/formatter/github_actions_formatter.cr @@ -0,0 +1,48 @@ +module Ameba::Formatter + # A formatter that outputs issues in a GitHub Actions compatible format. + # + # See [GitHub Actions documentation](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions) for details. + class GitHubActionsFormatter < BaseFormatter + @mutex = Mutex.new + + # Reports a result of the inspection of a corresponding source. + def source_finished(source : Source) : Nil + source.issues.each do |issue| + next if issue.disabled? + + @mutex.synchronize do + output << "::" + output << command_name(issue.rule.severity) + output << " " + output << "file=" + output << source.path + if location = issue.location + output << ",line=" + output << location.line_number + output << ",col=" + output << location.column_number + end + if end_location = issue.end_location + output << ",endLine=" + output << end_location.line_number + output << ",endColumn=" + output << end_location.column_number + end + output << ",title=" + output << issue.rule.name + output << "::" + output << issue.message.gsub('\n', "\\n") + output << "\n" + end + end + end + + private def command_name(severity : Severity) : String + case severity + in .error? then "error" + in .warning? then "warning" + in .convention? then "notice" + end + end + end +end From 5e7e2e9fed117fba6f1b2093094a965390f1bec9 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Fri, 6 Dec 2024 16:43:34 +0100 Subject: [PATCH 2/3] Fix multiline support in `GitHubActionsFormatter` https://github.com/actions/toolkit/issues/193 --- spec/ameba/formatter/github_actions_formatter_spec.cr | 2 +- src/ameba/formatter/github_actions_formatter.cr | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/ameba/formatter/github_actions_formatter_spec.cr b/spec/ameba/formatter/github_actions_formatter_spec.cr index 8268390e0..12988d606 100644 --- a/spec/ameba/formatter/github_actions_formatter_spec.cr +++ b/spec/ameba/formatter/github_actions_formatter_spec.cr @@ -23,7 +23,7 @@ module Ameba::Formatter source.add_issue DummyRule.new, location, location, "message\n2nd line" subject.source_finished(source) - output.to_s.should eq("::notice file=/path/to/file.cr,line=1,col=2,endLine=1,endColumn=2,title=Ameba/DummyRule::message\\n2nd line\n") + output.to_s.should eq("::notice file=/path/to/file.cr,line=1,col=2,endLine=1,endColumn=2,title=Ameba/DummyRule::message%0A2nd line\n") end end end diff --git a/src/ameba/formatter/github_actions_formatter.cr b/src/ameba/formatter/github_actions_formatter.cr index 711bf85d3..3beb1b288 100644 --- a/src/ameba/formatter/github_actions_formatter.cr +++ b/src/ameba/formatter/github_actions_formatter.cr @@ -31,7 +31,8 @@ module Ameba::Formatter output << ",title=" output << issue.rule.name output << "::" - output << issue.message.gsub('\n', "\\n") + # https://github.com/actions/toolkit/issues/193 + output << issue.message.gsub('\n', "%0A") output << "\n" end end From 9c07970e13b2b4f5a4691b29546c925702e29c06 Mon Sep 17 00:00:00 2001 From: Sijawusz Pur Rahnama Date: Fri, 6 Dec 2024 17:04:03 +0100 Subject: [PATCH 3/3] Make escaping on-par with the one found in actions/toolkit --- .../formatter/github_actions_formatter.cr | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/ameba/formatter/github_actions_formatter.cr b/src/ameba/formatter/github_actions_formatter.cr index 3beb1b288..dac1a9df4 100644 --- a/src/ameba/formatter/github_actions_formatter.cr +++ b/src/ameba/formatter/github_actions_formatter.cr @@ -15,7 +15,7 @@ module Ameba::Formatter output << command_name(issue.rule.severity) output << " " output << "file=" - output << source.path + output << escape_property(source.path) if location = issue.location output << ",line=" output << location.line_number @@ -29,10 +29,9 @@ module Ameba::Formatter output << end_location.column_number end output << ",title=" - output << issue.rule.name + output << escape_property(issue.rule.name) output << "::" - # https://github.com/actions/toolkit/issues/193 - output << issue.message.gsub('\n', "%0A") + output << escape_data(issue.message) output << "\n" end end @@ -45,5 +44,26 @@ module Ameba::Formatter in .convention? then "notice" end end + + # See for details: + # - https://github.com/actions/toolkit/blob/74906bea83a0dbf6aaba2d00b732deb0c3aefd2d/packages/core/src/command.ts#L92-L97 + # - https://github.com/actions/toolkit/issues/193 + private def escape_data(string : String) : String + string + .gsub('%', "%25") + .gsub('\r', "%0D") + .gsub('\n', "%0A") + end + + # See for details: + # - https://github.com/actions/toolkit/blob/74906bea83a0dbf6aaba2d00b732deb0c3aefd2d/packages/core/src/command.ts#L99-L106 + private def escape_property(string : String) : String + string + .gsub('%', "%25") + .gsub('\r', "%0D") + .gsub('\n', "%0A") + .gsub(':', "%3A") + .gsub(',', "%2C") + end end end