Skip to content

Commit 03133b3

Browse files
authored
Merge pull request #43 from codacy/refactor-eslint-linter
refactor: Implement tool plugins with declarative configuration
2 parents 7753f16 + 28906b0 commit 03133b3

File tree

14 files changed

+482
-126
lines changed

14 files changed

+482
-126
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ go.work
2222
go.work.sum
2323

2424
.idea/
25+
.vscode/
2526

26-
cli-v2
27+
cli-v2

cmd/analyze.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ var analyzeCmd = &cobra.Command{
205205
}
206206

207207
eslint := config.Config.Tools()["eslint"]
208-
eslintInstallationDirectory := eslint.Info()["installDir"]
208+
eslintInstallationDirectory := eslint.InstallDir
209209
nodeRuntime := config.Config.Runtimes()["node"]
210210
nodeBinary := nodeRuntime.Binaries["node"]
211211

@@ -220,4 +220,4 @@ var analyzeCmd = &cobra.Command{
220220

221221
tools.RunEslint(workDirectory, eslintInstallationDirectory, nodeBinary, args, autoFix, outputFile, outputFormat)
222222
},
223-
}
223+
}

cmd/install.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cmd
22

33
import (
44
cfg "codacy/cli-v2/config"
5-
"fmt"
65
"log"
76

87
"github.com/spf13/cobra"
@@ -33,18 +32,9 @@ func installRuntimes(config *cfg.ConfigType) {
3332
}
3433

3534
func installTools(config *cfg.ConfigType) {
36-
for _, tool := range config.Tools() {
37-
switch tool.Name() {
38-
case "eslint":
39-
// eslint needs node runtime
40-
nodeRuntime := config.Runtimes()["node"]
41-
err := cfg.InstallEslint(nodeRuntime, tool, registry)
42-
if err != nil {
43-
fmt.Println(err.Error())
44-
log.Fatal(err)
45-
}
46-
default:
47-
log.Fatal("Unknown tool:", tool.Name())
48-
}
35+
// Use the new tools-installer instead of manual installation
36+
err := cfg.InstallTools()
37+
if err != nil {
38+
log.Fatal(err)
4939
}
50-
}
40+
}

config-file/configFile.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,22 @@ func parseConfigFile(configContents []byte) error {
3636
return err
3737
}
3838

39+
// Convert the tool strings to ToolConfig objects
40+
toolConfigs := make([]plugins.ToolConfig, 0, len(configFile.TOOLS))
3941
for _, tl := range configFile.TOOLS {
4042
ct, err := parseConfigTool(tl)
4143
if err != nil {
4244
return err
4345
}
44-
config.Config.AddTool(config.NewRuntime(ct.name, ct.version))
46+
toolConfigs = append(toolConfigs, plugins.ToolConfig{
47+
Name: ct.name,
48+
Version: ct.version,
49+
})
50+
}
51+
52+
// Add all tools at once
53+
if err := config.Config.AddTools(toolConfigs); err != nil {
54+
return err
4555
}
4656

4757
return nil

config/config.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type ConfigType struct {
1717
projectConfigFile string
1818

1919
runtimes map[string]*plugins.RuntimeInfo
20-
tools map[string]*Runtime
20+
tools map[string]*plugins.ToolInfo
2121
}
2222

2323
func (c *ConfigType) HomePath() string {
@@ -63,13 +63,23 @@ func (c *ConfigType) AddRuntimes(configs []plugins.RuntimeConfig) error {
6363
return nil
6464
}
6565

66-
// TODO do inheritance with tool
67-
func (c *ConfigType) Tools() map[string]*Runtime {
66+
func (c *ConfigType) Tools() map[string]*plugins.ToolInfo {
6867
return c.tools
6968
}
7069

71-
func (c *ConfigType) AddTool(t *Runtime) {
72-
c.tools[t.Name()] = t
70+
func (c *ConfigType) AddTools(configs []plugins.ToolConfig) error {
71+
// Process the tool configurations using the plugins.ProcessTools function
72+
toolInfoMap, err := plugins.ProcessTools(configs, c.toolsDirectory)
73+
if err != nil {
74+
return err
75+
}
76+
77+
// Store the tool information in the config
78+
for name, info := range toolInfoMap {
79+
c.tools[name] = info
80+
}
81+
82+
return nil
7383
}
7484

7585
func (c *ConfigType) initCodacyDirs() {
@@ -117,7 +127,7 @@ func Init() {
117127
Config.initCodacyDirs()
118128

119129
Config.runtimes = make(map[string]*plugins.RuntimeInfo)
120-
Config.tools = make(map[string]*Runtime)
130+
Config.tools = make(map[string]*plugins.ToolInfo)
121131
}
122132

123133
// Global singleton config-file

config/eslint-utils.go

Lines changed: 0 additions & 50 deletions
This file was deleted.

config/runtime.go

Lines changed: 0 additions & 45 deletions
This file was deleted.

config/tools-installer.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package config
2+
3+
import (
4+
"bytes"
5+
"codacy/cli-v2/plugins"
6+
"fmt"
7+
"log"
8+
"os"
9+
"os/exec"
10+
"strings"
11+
"text/template"
12+
)
13+
14+
// InstallTools installs all tools defined in the configuration
15+
func InstallTools() error {
16+
for name, toolInfo := range Config.Tools() {
17+
err := InstallTool(name, toolInfo)
18+
if err != nil {
19+
return fmt.Errorf("failed to install tool %s: %w", name, err)
20+
}
21+
}
22+
return nil
23+
}
24+
25+
// InstallTool installs a specific tool
26+
func InstallTool(name string, toolInfo *plugins.ToolInfo) error {
27+
// Check if the tool is already installed
28+
if isToolInstalled(toolInfo) {
29+
fmt.Printf("Tool %s v%s is already installed\n", name, toolInfo.Version)
30+
return nil
31+
}
32+
33+
// Get the runtime for this tool
34+
runtimeInfo, ok := Config.Runtimes()[toolInfo.Runtime]
35+
if !ok {
36+
return fmt.Errorf("required runtime %s not found for tool %s", toolInfo.Runtime, name)
37+
}
38+
39+
// Make sure the installation directory exists
40+
err := os.MkdirAll(toolInfo.InstallDir, 0755)
41+
if err != nil {
42+
return fmt.Errorf("failed to create installation directory: %w", err)
43+
}
44+
45+
// Prepare template data
46+
templateData := map[string]string{
47+
"InstallDir": toolInfo.InstallDir,
48+
"PackageName": toolInfo.Name,
49+
"Version": toolInfo.Version,
50+
"Registry": "", // TODO: Get registry from config
51+
}
52+
53+
// Get package manager binary based on the tool configuration
54+
packageManagerName := toolInfo.PackageManager
55+
packageManagerBinary, ok := runtimeInfo.Binaries[packageManagerName]
56+
if !ok {
57+
return fmt.Errorf("package manager binary %s not found in runtime %s", packageManagerName, toolInfo.Runtime)
58+
}
59+
60+
// Set registry if provided
61+
if toolInfo.RegistryCommand != "" {
62+
regCmd, err := executeToolTemplate(toolInfo.RegistryCommand, templateData)
63+
if err != nil {
64+
return fmt.Errorf("failed to prepare registry command: %w", err)
65+
}
66+
67+
if regCmd != "" {
68+
registryCmd := exec.Command(packageManagerBinary, strings.Split(regCmd, " ")...)
69+
if output, err := registryCmd.CombinedOutput(); err != nil {
70+
return fmt.Errorf("failed to set registry: %s: %w", string(output), err)
71+
}
72+
}
73+
}
74+
75+
// Execute installation command
76+
installCmd, err := executeToolTemplate(toolInfo.InstallCommand, templateData)
77+
if err != nil {
78+
return fmt.Errorf("failed to prepare install command: %w", err)
79+
}
80+
81+
// Execute the installation command using the package manager
82+
cmd := exec.Command(packageManagerBinary, strings.Split(installCmd, " ")...)
83+
84+
log.Printf("Installing %s v%s...\n", toolInfo.Name, toolInfo.Version)
85+
output, err := cmd.CombinedOutput()
86+
if err != nil {
87+
return fmt.Errorf("failed to install tool: %s: %w", string(output), err)
88+
}
89+
90+
log.Printf("Successfully installed %s v%s\n", toolInfo.Name, toolInfo.Version)
91+
return nil
92+
}
93+
94+
// isToolInstalled checks if a tool is already installed by checking for the binary
95+
func isToolInstalled(toolInfo *plugins.ToolInfo) bool {
96+
// If there are no binaries, check the install directory
97+
if len(toolInfo.Binaries) == 0 {
98+
_, err := os.Stat(toolInfo.InstallDir)
99+
return err == nil
100+
}
101+
102+
// Check if at least one binary exists
103+
for _, binaryPath := range toolInfo.Binaries {
104+
_, err := os.Stat(binaryPath)
105+
if err == nil {
106+
return true
107+
}
108+
}
109+
110+
return false
111+
}
112+
113+
// executeToolTemplate executes a template with the given data
114+
func executeToolTemplate(tmplStr string, data map[string]string) (string, error) {
115+
tmpl, err := template.New("command").Parse(tmplStr)
116+
if err != nil {
117+
return "", err
118+
}
119+
120+
var buf bytes.Buffer
121+
err = tmpl.Execute(&buf, data)
122+
if err != nil {
123+
return "", err
124+
}
125+
126+
return buf.String(), nil
127+
}

0 commit comments

Comments
 (0)