From d54a557d23ebcfb29b59eb9d8b95f666c5379d7a Mon Sep 17 00:00:00 2001 From: Boming Zhang Date: Mon, 4 Nov 2024 09:44:05 +0800 Subject: [PATCH] feat(parser): show match occurrences (#76) Reviewed-on: https://focs.ji.sjtu.edu.cn/git/JOJ/JOJ3/pulls/76 Co-authored-by: Boming Zhang Co-committed-by: Boming Zhang --- internal/parser/clangtidy/score.go | 54 ++++++++++++++----------- internal/parser/cppcheck/score.go | 53 ++++++++++++++++++++----- internal/parser/cpplint/parser.go | 64 +++++++++++++++++++++++------- internal/parser/keyword/parser.go | 43 +++++++++++++++----- 4 files changed, 155 insertions(+), 59 deletions(-) diff --git a/internal/parser/clangtidy/score.go b/internal/parser/clangtidy/score.go index 3689edb..62721a0 100644 --- a/internal/parser/clangtidy/score.go +++ b/internal/parser/clangtidy/score.go @@ -2,47 +2,53 @@ package clangtidy import ( "fmt" + "sort" "strings" - - "github.com/joint-online-judge/JOJ3/pkg/utils" ) func GetResult(jsonMessages []JsonMessage, conf Conf) (int, string) { score := conf.Score comment := "### Test results summary\n\n" - categoryCount := map[string]int{} + matchCount := make(map[string]int) + scoreChange := make(map[string]int) for _, jsonMessage := range jsonMessages { // checkName is commas separated string here checkName := jsonMessage.CheckName for _, match := range conf.Matches { for _, keyword := range match.Keywords { if strings.Contains(checkName, keyword) { - score -= match.Score + matchCount[keyword] += 1 + scoreChange[keyword] += -match.Score + score += -match.Score } } } - checkNames := strings.Split(checkName, ",") - for _, checkName := range checkNames { - parts := strings.Split(checkName, "-") - if len(parts) > 0 { - category := parts[0] - // checkName might be: -warnings-as-errors - if category == "" { - continue - } - categoryCount[category] += 1 - } - } } - sortedMap := utils.SortMap(categoryCount, - func(i, j utils.Pair[string, int]) bool { - if i.Value == j.Value { - return i.Key < j.Key - } - return i.Value > j.Value + type Result struct { + Keyword string + Count int + ScoreChange int + } + var results []Result + for keyword, count := range matchCount { + results = append(results, Result{ + Keyword: keyword, + Count: count, + ScoreChange: scoreChange[keyword], }) - for i, kv := range sortedMap { - comment += fmt.Sprintf("%d. %s: %d\n", i+1, kv.Key, kv.Value) + } + sort.Slice(results, func(i, j int) bool { + if results[i].ScoreChange != results[j].ScoreChange { + return results[i].ScoreChange < results[j].ScoreChange + } + if results[i].Count != results[j].Count { + return results[i].Count > results[j].Count + } + return results[i].Keyword < results[j].Keyword + }) + for i, result := range results { + comment += fmt.Sprintf("%d. `%s`: %d occurrence(s), %d point(s)\n", + i+1, result.Keyword, result.Count, result.ScoreChange) } return score, comment } diff --git a/internal/parser/cppcheck/score.go b/internal/parser/cppcheck/score.go index c777198..4cb26b4 100644 --- a/internal/parser/cppcheck/score.go +++ b/internal/parser/cppcheck/score.go @@ -3,6 +3,7 @@ package cppcheck import ( "fmt" "log/slog" + "sort" "strings" ) @@ -41,9 +42,9 @@ func severityFromString(severityString string) (Severity, error) { } func GetResult(records []Record, conf Conf) (string, int, error) { - result := "### Test results summary\n\n" - var severityCounts [UNKNOWN + 1]int score := conf.Score + comment := "### Test results summary\n\n" + var severityCounts [UNKNOWN + 1]int // TODO: remove me var severityScore [UNKNOWN + 1]int for _, match := range conf.Matches { @@ -73,22 +74,52 @@ func GetResult(records []Record, conf Conf) (string, int, error) { severityCounts[int(severity)] += 1 score -= severityScore[int(severity)] } + comment += fmt.Sprintf("1. error: %d\n", severityCounts[0]) + comment += fmt.Sprintf("2. warning: %d\n", severityCounts[1]) + comment += fmt.Sprintf("3. portability: %d\n", severityCounts[2]) + comment += fmt.Sprintf("4. performance: %d\n", severityCounts[3]) + comment += fmt.Sprintf("5. style: %d\n", severityCounts[4]) + comment += fmt.Sprintf("6. information: %d\n", severityCounts[5]) + comment += fmt.Sprintf("7. debug: %d\n", severityCounts[6]) } + matchCount := make(map[string]int) + scoreChange := make(map[string]int) for _, record := range records { for _, match := range conf.Matches { for _, keyword := range match.Keywords { if strings.Contains(record.Id, keyword) { - score -= match.Score + matchCount[keyword] += 1 + scoreChange[keyword] += -match.Score + score += -match.Score } } } } - result += fmt.Sprintf("1. error: %d\n", severityCounts[0]) - result += fmt.Sprintf("2. warning: %d\n", severityCounts[1]) - result += fmt.Sprintf("3. portability: %d\n", severityCounts[2]) - result += fmt.Sprintf("4. performance: %d\n", severityCounts[3]) - result += fmt.Sprintf("5. style: %d\n", severityCounts[4]) - result += fmt.Sprintf("6. information: %d\n", severityCounts[5]) - result += fmt.Sprintf("7. debug: %d\n", severityCounts[6]) - return result, score, nil + type Result struct { + Keyword string + Count int + ScoreChange int + } + var results []Result + for keyword, count := range matchCount { + results = append(results, Result{ + Keyword: keyword, + Count: count, + ScoreChange: scoreChange[keyword], + }) + } + sort.Slice(results, func(i, j int) bool { + if results[i].ScoreChange != results[j].ScoreChange { + return results[i].ScoreChange < results[j].ScoreChange + } + if results[i].Count != results[j].Count { + return results[i].Count > results[j].Count + } + return results[i].Keyword < results[j].Keyword + }) + for i, result := range results { + comment += fmt.Sprintf("%d. `%s`: %d occurrence(s), %d point(s)\n", + i+1, result.Keyword, result.Count, result.ScoreChange) + } + return comment, score, nil } diff --git a/internal/parser/cpplint/parser.go b/internal/parser/cpplint/parser.go index d3a0c3f..a6beac9 100644 --- a/internal/parser/cpplint/parser.go +++ b/internal/parser/cpplint/parser.go @@ -4,6 +4,7 @@ import ( "fmt" "log/slog" "regexp" + "sort" "strconv" "strings" @@ -30,13 +31,15 @@ func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { stderr := executorResult.Files[conf.Stderr] pattern := `(.+):(\d+): (.+) \[(.+)\] \[(\d)]\n` re := regexp.MustCompile(pattern) - matches := re.FindAllStringSubmatch(stderr, -1) + regexMatches := re.FindAllStringSubmatch(stderr, -1) score := conf.Score comment := "### Test results summary\n\n" - categoryCount := map[string]int{} - for _, match := range matches { - // fileName := match[1] - // lineNum, err := strconv.Atoi(match[2]) + categoryCount := make(map[string]int) + matchCount := make(map[string]int) + scoreChange := make(map[string]int) + for _, regexMatch := range regexMatches { + // fileName := regexMatch[1] + // lineNum, err := strconv.Atoi(regexMatch[2]) // if err != nil { // slog.Error("parse lineNum", "error", err) // return stage.ParserResult{ @@ -44,11 +47,11 @@ func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { // Comment: fmt.Sprintf("Unexpected parser error: %s.", err), // } // } - // message := match[3] - category := match[4] + // message := regexMatch[3] + category := regexMatch[4] // TODO: remove me if len(conf.Matches) == 0 { - confidence, err := strconv.Atoi(match[5]) + confidence, err := strconv.Atoi(regexMatch[5]) if err != nil { slog.Error("parse confidence", "error", err) return stage.ParserResult{ @@ -58,19 +61,23 @@ func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { } score -= confidence } - for _, match := range conf.Matches { - for _, keyword := range match.Keywords { - if strings.Contains(category, keyword) { - score -= match.Score - } - } - } parts := strings.Split(category, "/") if len(parts) > 0 { category := parts[0] categoryCount[category] += 1 } + // TODO: remove me ends + for _, match := range conf.Matches { + for _, keyword := range match.Keywords { + if strings.Contains(category, keyword) { + matchCount[keyword] += 1 + scoreChange[keyword] += -match.Score + score += -match.Score + } + } + } } + // TODO: remove me sortedMap := utils.SortMap(categoryCount, func(i, j utils.Pair[string, int]) bool { if i.Value == j.Value { @@ -81,6 +88,33 @@ func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { for i, kv := range sortedMap { comment += fmt.Sprintf("%d. %s: %d\n", i+1, kv.Key, kv.Value) } + // TODO: remove me ends + type Result struct { + Keyword string + Count int + ScoreChange int + } + var results []Result + for keyword, count := range matchCount { + results = append(results, Result{ + Keyword: keyword, + Count: count, + ScoreChange: scoreChange[keyword], + }) + } + sort.Slice(results, func(i, j int) bool { + if results[i].ScoreChange != results[j].ScoreChange { + return results[i].ScoreChange < results[j].ScoreChange + } + if results[i].Count != results[j].Count { + return results[i].Count > results[j].Count + } + return results[i].Keyword < results[j].Keyword + }) + for i, result := range results { + comment += fmt.Sprintf("%d. `%s`: %d occurrence(s), %d point(s)\n", + i+1, result.Keyword, result.Count, result.ScoreChange) + } return stage.ParserResult{ Score: score, Comment: comment, diff --git a/internal/parser/keyword/parser.go b/internal/parser/keyword/parser.go index f7a3913..57c992e 100644 --- a/internal/parser/keyword/parser.go +++ b/internal/parser/keyword/parser.go @@ -2,6 +2,7 @@ package keyword import ( "fmt" + "sort" "strings" "github.com/joint-online-judge/JOJ3/internal/stage" @@ -27,24 +28,48 @@ type Keyword struct{} func Parse(executorResult stage.ExecutorResult, conf Conf) stage.ParserResult { score := conf.Score comment := "" + matchCount := make(map[string]int) + scoreChange := make(map[string]int) for _, match := range conf.Matches { for _, keyword := range match.Keywords { - keywordMatchCount := 0 for _, file := range conf.Files { content := executorResult.Files[file] - keywordMatchCount += strings.Count(content, keyword) + matchCount[keyword] += strings.Count(content, keyword) } if match.MaxMatchCount > 0 { - keywordMatchCount = min(keywordMatchCount, match.MaxMatchCount) - } - if keywordMatchCount > 0 { - score -= keywordMatchCount * match.Score - comment += fmt.Sprintf( - "Matched keyword %d time(s): %s\n", - keywordMatchCount, keyword) + matchCount[keyword] = min( + matchCount[keyword], match.MaxMatchCount) } + score += -match.Score * matchCount[keyword] + scoreChange[keyword] = -match.Score * matchCount[keyword] } } + type Result struct { + Keyword string + Count int + ScoreChange int + } + var results []Result + for keyword, count := range matchCount { + results = append(results, Result{ + Keyword: keyword, + Count: count, + ScoreChange: scoreChange[keyword], + }) + } + sort.Slice(results, func(i, j int) bool { + if results[i].ScoreChange != results[j].ScoreChange { + return results[i].ScoreChange < results[j].ScoreChange + } + if results[i].Count != results[j].Count { + return results[i].Count > results[j].Count + } + return results[i].Keyword < results[j].Keyword + }) + for i, result := range results { + comment += fmt.Sprintf("%d. `%s`: %d occurrence(s), %d point(s)\n", + i+1, result.Keyword, result.Count, result.ScoreChange) + } return stage.ParserResult{ Score: score, Comment: comment,