Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
added basic support for both pyrefly and solid-grader
  • Loading branch information
kendrickcurtis committed Jul 1, 2025
commit a638e63336800f82be31f0e7c43968b6074dcaa5
2 changes: 2 additions & 0 deletions 2 cli-v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
)

func main() {

fmt.Printf("TESTING CLI BUILD")
// Initialize config global object
config.Init()

Expand Down
29 changes: 29 additions & 0 deletions 29 cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,31 @@ func runEnigmaAnalysis(workDirectory string, pathsToCheck []string, outputFile s
return tools.RunEnigma(workDirectory, enigma.InstallDir, enigma.Binaries["codacy-enigma-cli"], pathsToCheck, outputFile, outputFormat)
}

func runSolidGraderAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) error {
solidGrader := config.Config.Tools()["solid_grader"]
if solidGrader == nil {
log.Fatal("solid_grader tool configuration not found")
}
solidGraderBinary := solidGrader.Binaries["solid_grader"]
return tools.RunSolidGrader(
workDirectory,
solidGrader.InstallDir,
solidGraderBinary,
pathsToCheck,
outputFile,
outputFormat,
)
}

func runPyreflyAnalysis(workDirectory string, pathsToCheck []string, outputFile string, outputFormat string) error {
pyrefly := config.Config.Tools()["pyrefly"]
if pyrefly == nil {
log.Fatal("Pyrefly tool configuration not found")
}
pyreflyBinary := pyrefly.Binaries["pyrefly"]
return tools.RunPyrefly(workDirectory, pyreflyBinary, pathsToCheck, outputFile, outputFormat)
}

var analyzeCmd = &cobra.Command{
Use: "analyze",
Short: "Runs all configured linters.",
Expand Down Expand Up @@ -522,6 +547,10 @@ func runTool(workDirectory string, toolName string, args []string, outputFile st
return runLizardAnalysis(workDirectory, args, outputFile, outputFormat)
case "codacy-enigma-cli":
return runEnigmaAnalysis(workDirectory, args, outputFile, outputFormat)
case "solid_grader":
return runSolidGraderAnalysis(workDirectory, args, outputFile, outputFormat)
case "pyrefly":
return runPyreflyAnalysis(workDirectory, args, outputFile, outputFormat)
default:
return fmt.Errorf("unsupported tool: %s", toolName)
}
Expand Down
2 changes: 2 additions & 0 deletions 2 domain/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
DartAnalyzer string = "d203d615-6cf1-41f9-be5f-e2f660f7850f"
Semgrep string = "6792c561-236d-41b7-ba5e-9d6bee0d548b"
Lizard string = "76348462-84b3-409a-90d3-955e90abfb87"
SolidGrader string = "716FCE5C-F5E8-4B9D-A7D2-86CDFAE79D45"
)

type ToolInfo struct {
Expand All @@ -45,4 +46,5 @@ var SupportedToolsMetadata = map[string]ToolInfo{
DartAnalyzer: {Name: "dartanalyzer", Priority: 0},
Lizard: {Name: "lizard", Priority: 0},
Semgrep: {Name: "semgrep", Priority: 0},
SolidGrader: {Name: "solid_grader", Priority: 0},
}
15 changes: 15 additions & 0 deletions 15 plugins/tools/pyrefly/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: pyrefly
# Pyrefly: Fast, modern Python type checker (https://pyrefly.org/en/docs/installation/)
description: Pyrefly is a fast, modern static type checker for Python, designed for developer productivity and CI integration.
default_version: 0.22.0
runtime: python
runtime_binaries:
package_manager: python3
execution: python3
binaries:
- name: pyrefly
path: "venv/bin/pyrefly"
output_options:
file_flag: "--output"
analysis_options:
default_path: "."
13 changes: 13 additions & 0 deletions 13 plugins/tools/solid_grader/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: solid_grader
# TODO: Update description with accurate details
description: Solid Grader scans your code for SOLID principle violations.
default_version: 0.1.0 # TODO: Update with actual version if needed
binaries:
- name: solid_grader
path: "/Users/kendrickcurtis/Documents/GitHub/solid-grader2/solid-grader2"
formatters:
- name: text
flag: ""
- name: sarif
flag: "-f sarif"
# TODO: Add download section if binary is to be downloaded automatically
5 changes: 5 additions & 0 deletions 5 tools/language_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ func CreateLanguagesConfigFile(apiTools []domain.Tool, toolsConfigDir string, to
Languages: []string{"C", "CPP", "C#", "Generic", "Go", "Java", "JavaScript", "JSON", "Kotlin", "Python", "TypeScript", "Ruby", "Rust", "JSX", "PHP", "Scala", "Swift", "Terraform"},
Extensions: []string{".c", ".cpp", ".h", ".hpp", ".cs", ".go", ".java", ".js", ".json", ".kt", ".py", ".ts", ".rb", ".rs", ".jsx", ".php", ".scala", ".swift", ".tf", ".tfvars"},
},
"pyrefly": {
Name: "pyrefly",
Languages: []string{"Python"},
Extensions: []string{".py"},
},
}

// Build a list of tool language info for enabled tools
Expand Down
78 changes: 78 additions & 0 deletions 78 tools/pyreflyRunner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package tools

import (
"codacy/cli-v2/utils"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
)

// RunPyrefly executes Pyrefly type checking on the specified directory or files
func RunPyrefly(workDirectory string, binary string, files []string, outputFile string, outputFormat string) error {
args := []string{"check"}

// Always use JSON output for SARIF conversion
var tempFile string
if outputFormat == "sarif" {
tmp, err := ioutil.TempFile("", "pyrefly-*.json")
if err != nil {
return fmt.Errorf("failed to create temporary file: %w", err)
}
tempFile = tmp.Name()
tmp.Close()
defer os.Remove(tempFile)
args = append(args, "--output", tempFile, "--output-format", "json")
} else if outputFile != "" {
args = append(args, "--output", outputFile)
}
if outputFormat == "json" && outputFile == "" {
args = append(args, "--output-format", "json")
}

// Detect config file (pyrefly.toml or pyproject.toml)
configFiles := []string{"pyrefly.toml", "pyproject.toml"}
for _, configFile := range configFiles {
if _, err := os.Stat(filepath.Join(workDirectory, configFile)); err == nil {
// Pyrefly auto-detects config, so no need to add a flag
break
}
}

// Add files to check, or "." for current directory
if len(files) > 0 {
args = append(args, files...)
} else {
args = append(args, ".")
}

cmd := exec.Command(binary, args...)
cmd.Dir = workDirectory
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

err := cmd.Run()
if err != nil {
if _, ok := err.(*exec.ExitError); !ok {
return fmt.Errorf("failed to run Pyrefly: %w", err)
}
}

if outputFormat == "sarif" {
jsonOutput, err := os.ReadFile(tempFile)
if err != nil {
return fmt.Errorf("failed to read Pyrefly output: %w", err)
}
sarifOutput := utils.ConvertPyreflyToSarif(jsonOutput)
if outputFile != "" {
err = os.WriteFile(outputFile, sarifOutput, 0644)
if err != nil {
return fmt.Errorf("failed to write SARIF output: %w", err)
}
} else {
fmt.Println(string(sarifOutput))
}
}
return nil
}
47 changes: 47 additions & 0 deletions 47 tools/solidGraderRunner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package tools

import (
"fmt"
"os"
"os/exec"
"path/filepath"
)

func RunSolidGrader(workDirectory string, installationDirectory string, binary string, files []string, outputFile string, outputFormat string) error {
args := []string{}

if outputFormat == "sarif" {
args = append(args, "-f", "sarif")
}

if len(files) > 0 {
args = append(args, files...)
} else {
args = append(args, ".")
}

/*if configExists != "" {
log.Println("Config file found, using it")
args = append(args, "--config", configExists)
} else {
log.Println("No config file found, using tool defaults")
}*/

cmd := exec.Command(binary, args...)
cmd.Dir = workDirectory
cmd.Stderr = os.Stderr
if outputFile != "" {
outputWriter, err := os.Create(filepath.Clean(outputFile))
if err != nil {
return fmt.Errorf("failed to create output file: %w", err)
}
defer outputWriter.Close()
cmd.Stdout = outputWriter
} else {
cmd.Stdout = os.Stdout
}
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run solid_grader: %w", err)
}
return nil
}
106 changes: 106 additions & 0 deletions 106 utils/sarif.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,109 @@ func FilterRulesFromSarif(sarifData []byte) ([]byte, error) {

return filteredData, nil
}

// PyreflyIssue represents a single issue in Pyrefly's JSON output
// Example fields: line, column, stop_line, stop_column, path, code, name, description, concise_description
// See: https://pyrefly.org/en/docs/usage/#output-formats

type PyreflyIssue struct {
Line int `json:"line"`
Column int `json:"column"`
StopLine int `json:"stop_line"`
StopColumn int `json:"stop_column"`
Path string `json:"path"`
Code int `json:"code"`
Name string `json:"name"`
Description string `json:"description"`
ConciseDescription string `json:"concise_description"`
}

// ConvertPyreflyToSarif converts Pyrefly JSON output to SARIF format
func ConvertPyreflyToSarif(pyreflyOutput []byte) []byte {
// Pyrefly outputs: { "errors": [ ... ] }
type pyreflyRoot struct {
Errors []PyreflyIssue `json:"errors"`
}
var root pyreflyRoot
var sarifReport SarifReport
if err := json.Unmarshal(pyreflyOutput, &root); err != nil {
// If parsing fails, return empty SARIF report with Pyrefly metadata
sarifReport = SarifReport{
Version: "2.1.0",
Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
Runs: []Run{
{
Tool: Tool{
Driver: Driver{
Name: "Pyrefly",
Version: "0.22.0",
InformationURI: "https://pyrefly.org",
},
},
Results: []Result{},
},
},
}
} else {
sarifReport = SarifReport{
Version: "2.1.0",
Schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
Runs: []Run{
{
Tool: Tool{
Driver: Driver{
Name: "Pyrefly",
Version: "0.22.0",
InformationURI: "https://pyrefly.org",
},
},
Results: make([]Result, 0, len(root.Errors)),
},
},
}
for _, issue := range root.Errors {
result := Result{
RuleID: issue.Name,
Level: "error", // Pyrefly only reports errors
Message: MessageText{
Text: issue.Description,
},
Locations: []Location{
{
PhysicalLocation: PhysicalLocation{
ArtifactLocation: ArtifactLocation{
URI: issue.Path,
},
Region: Region{
StartLine: issue.Line,
StartColumn: issue.Column,
},
},
},
},
}
sarifReport.Runs[0].Results = append(sarifReport.Runs[0].Results, result)
}
}
sarifData, err := json.MarshalIndent(sarifReport, "", " ")
if err != nil {
// If marshaling fails, return a minimal SARIF report with Pyrefly metadata
return []byte(`{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Pyrefly",
"version": "0.22.0",
"informationUri": "https://pyrefly.org"
}
},
"results": []
}
]
}`)
}
return sarifData
}
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.