a06f331eb8
install-script / powershell-syntax (push) Waiting to run
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
install-script / install (macos-14) (push) Waiting to run
install-script / install (ubuntu-latest) (push) Waiting to run
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
143 lines
3.4 KiB
Go
143 lines
3.4 KiB
Go
package lint
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// extLang maps file extensions to the linter languages this package supports.
|
|
var extLang = map[string]string{
|
|
".go": "go",
|
|
".py": "python",
|
|
".pyi": "python",
|
|
".sh": "shell",
|
|
".bash": "shell",
|
|
".zsh": "shell",
|
|
".ksh": "shell",
|
|
}
|
|
|
|
// LanguageForPath infers the linter language from a file's extension, or "" if
|
|
// no built-in linter targets that type.
|
|
func LanguageForPath(p string) string {
|
|
return extLang[strings.ToLower(filepath.Ext(p))]
|
|
}
|
|
|
|
// gccLineRe matches the `file:line[:col]: message` convention emitted by gofmt
|
|
// -e and many other compilers/linters.
|
|
var gccLineRe = regexp.MustCompile(`^(.*?):(\d+):(?:(\d+):)?\s*(.*)$`)
|
|
|
|
func parseGCCFormat(out []byte, linter string) []Diagnostic {
|
|
var diags []Diagnostic
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
line = strings.TrimRight(line, "\r")
|
|
if strings.TrimSpace(line) == "" {
|
|
continue
|
|
}
|
|
m := gccLineRe.FindStringSubmatch(line)
|
|
if m == nil {
|
|
continue
|
|
}
|
|
ln, _ := strconv.Atoi(m[2])
|
|
col := 0
|
|
if m[3] != "" {
|
|
col, _ = strconv.Atoi(m[3])
|
|
}
|
|
diags = append(diags, Diagnostic{
|
|
File: m[1],
|
|
Line: ln,
|
|
Column: col,
|
|
Severity: SeverityError,
|
|
Message: strings.TrimSpace(m[4]),
|
|
Linter: linter,
|
|
})
|
|
}
|
|
return diags
|
|
}
|
|
|
|
// shellcheckRe matches shellcheck's `-f gcc` output:
|
|
// `file:line:col: severity: message [SCxxxx]`.
|
|
var shellcheckRe = regexp.MustCompile(`^(.*?):(\d+):(\d+):\s*(\w+):\s*(.*?)(?:\s*\[(SC\d+)\])?$`)
|
|
|
|
func parseShellcheckGCC(out []byte, linter string) []Diagnostic {
|
|
var diags []Diagnostic
|
|
for _, line := range strings.Split(string(out), "\n") {
|
|
line = strings.TrimRight(line, "\r")
|
|
m := shellcheckRe.FindStringSubmatch(line)
|
|
if m == nil {
|
|
continue
|
|
}
|
|
ln, _ := strconv.Atoi(m[2])
|
|
col, _ := strconv.Atoi(m[3])
|
|
diags = append(diags, Diagnostic{
|
|
File: m[1],
|
|
Line: ln,
|
|
Column: col,
|
|
Severity: shellcheckSeverity(m[4]),
|
|
Message: strings.TrimSpace(m[5]),
|
|
Rule: m[6],
|
|
Linter: linter,
|
|
})
|
|
}
|
|
return diags
|
|
}
|
|
|
|
func shellcheckSeverity(s string) Severity {
|
|
switch strings.ToLower(s) {
|
|
case "error":
|
|
return SeverityError
|
|
case "warning":
|
|
return SeverityWarning
|
|
default: // note, style, info
|
|
return SeverityInfo
|
|
}
|
|
}
|
|
|
|
type ruffEntry struct {
|
|
Code string `json:"code"`
|
|
Message string `json:"message"`
|
|
Filename string `json:"filename"`
|
|
Location struct {
|
|
Row int `json:"row"`
|
|
Column int `json:"column"`
|
|
} `json:"location"`
|
|
}
|
|
|
|
func parseRuff(out []byte, linter string) []Diagnostic {
|
|
out = bytes.TrimSpace(out)
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
var entries []ruffEntry
|
|
if err := json.Unmarshal(out, &entries); err != nil {
|
|
return nil
|
|
}
|
|
diags := make([]Diagnostic, 0, len(entries))
|
|
for _, e := range entries {
|
|
diags = append(diags, Diagnostic{
|
|
File: e.Filename,
|
|
Line: e.Location.Row,
|
|
Column: e.Location.Column,
|
|
Severity: ruffSeverity(e.Code, e.Message),
|
|
Message: e.Message,
|
|
Rule: e.Code,
|
|
Linter: linter,
|
|
})
|
|
}
|
|
return diags
|
|
}
|
|
|
|
// ruffSeverity treats syntax errors (E999, the E9 class, or a SyntaxError
|
|
// message — ruff emits these with a null code) as errors; everything else is a
|
|
// lint warning.
|
|
func ruffSeverity(code, message string) Severity {
|
|
if code == "" || strings.HasPrefix(code, "E9") ||
|
|
strings.Contains(strings.ToLower(message), "syntaxerror") {
|
|
return SeverityError
|
|
}
|
|
return SeverityWarning
|
|
}
|