diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index 7064dd4a..08fef7dd 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -62,6 +62,19 @@ func isGitHubAction() bool { return os.Getenv("GITHUB_ACTIONS") == "true" } +func createPythonProject(t *testing.T, name string) string { + location := "/tmp/" + name + err := os.MkdirAll(location, 0o755) + if err != nil { + t.Fatal(err) + } + err = ioutil.WriteFile(location+"/hello.py", []byte("print(\"Hello\")"), 0o755) + if err != nil { + t.Fatal(err) + } + return location +} + // TestVersion verifies that the version command returns the correct version func TestVersion(t *testing.T) { b := bytes.NewBufferString("") @@ -119,12 +132,8 @@ func TestHelp(t *testing.T) { } func TestInitCommand(t *testing.T) { - projectPath := "/tmp/qodana_init" - err := os.MkdirAll(projectPath, 0o755) - if err != nil { - t.Fatal(err) - } - err = ioutil.WriteFile("/tmp/qodana_init/hello.py", []byte("print(\"Hello\")"), 0o755) + projectPath := createPythonProject(t, "qodana_init") + err := ioutil.WriteFile(projectPath+"/qodana.yml", []byte("version: 1.0"), 0o755) if err != nil { t.Fatal(err) } @@ -137,7 +146,13 @@ func TestInitCommand(t *testing.T) { t.Fatal(err) } - qodanaYaml := core.LoadQodanaYaml(projectPath) + filename := core.FindQodanaYaml(projectPath) + + if filename != "qodana.yml" { + t.Fatalf("expected \"qodana.yml\" got \"%s\"", filename) + } + + qodanaYaml := core.LoadQodanaYaml(projectPath, filename) if qodanaYaml.Linter != core.QDPY { t.Fatalf("expected \"%s\", but got %s", core.QDPY, qodanaYaml.Linter) @@ -189,15 +204,8 @@ func TestAllCommands(t *testing.T) { if err != nil { t.Fatal(err) } - projectPath := "/tmp/qodana_scan" - err = os.MkdirAll(projectPath, 0o755) - if err != nil { - t.Fatal(err) - } - err = ioutil.WriteFile(filepath.Join(projectPath, "hello.py"), []byte("println(\"Hello\")\n123"), 0o755) - if err != nil { - t.Fatal(err) - } + + projectPath := createPythonProject(t, "qodana_scan") // pull out := bytes.NewBufferString("") diff --git a/cmd/init.go b/cmd/init.go index 9305a263..fea2ec96 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -25,6 +25,7 @@ import ( type InitOptions struct { ProjectDir string Force bool + YamlName string } // NewInitCommand returns a new instance of the show command. @@ -35,18 +36,22 @@ func NewInitCommand() *cobra.Command { Short: "Configure a project for Qodana", Long: `Configure a project for Qodana: prepare Qodana configuration file by analyzing the project structure and generating a default configuration qodana.yaml file.`, Run: func(cmd *cobra.Command, args []string) { - qodanaYaml := core.LoadQodanaYaml(options.ProjectDir) + if options.YamlName == "" { + options.YamlName = core.FindQodanaYaml(options.ProjectDir) + } + qodanaYaml := core.LoadQodanaYaml(options.ProjectDir, options.YamlName) if qodanaYaml.Linter == "" || options.Force { - core.GetLinter(options.ProjectDir) + core.GetLinter(options.ProjectDir, options.YamlName) } else { core.EmptyMessage() core.SuccessMessage("The linter was already configured before: %s", core.PrimaryBold(qodanaYaml.Linter)) } - core.WarningMessage("Run %s to analyze the project. The configuration is stored in qodana.yaml and can be changed later", core.PrimaryBold("qodana scan")) + core.WarningMessage("Run %s to analyze the project. The configuration is stored in %s and can be changed later", core.PrimaryBold("qodana scan"), core.PrimaryBold(options.YamlName)) }, } flags := cmd.Flags() flags.StringVarP(&options.ProjectDir, "project-dir", "i", ".", "Root directory of the project to configure") flags.BoolVarP(&options.Force, "force", "f", false, "Force initialization (overwrite existing valid qodana.yaml)") + flags.StringVar(&options.YamlName, "yaml-name", "", "Override qodana.yaml name") return cmd } diff --git a/cmd/pull.go b/cmd/pull.go index 9a7d6e91..f262af14 100644 --- a/cmd/pull.go +++ b/cmd/pull.go @@ -29,6 +29,7 @@ import ( type PullOptions struct { Linter string ProjectDir string + YamlName string } // NewPullCommand returns a new instance of the show command. @@ -42,14 +43,17 @@ func NewPullCommand() *cobra.Command { core.CheckDockerHost() }, Run: func(cmd *cobra.Command, args []string) { + if options.YamlName == "" { + options.YamlName = core.FindQodanaYaml(options.ProjectDir) + } if options.Linter == "" { - qodanaYaml := core.LoadQodanaYaml(options.ProjectDir) + qodanaYaml := core.LoadQodanaYaml(options.ProjectDir, options.YamlName) if qodanaYaml.Linter == "" { core.WarningMessage( "No valid qodana.yaml found. Have you run %s? Running that for you...", core.PrimaryBold("qodana init"), ) - options.Linter = core.GetLinter(options.ProjectDir) + options.Linter = core.GetLinter(options.ProjectDir, options.YamlName) core.EmptyMessage() } else { options.Linter = qodanaYaml.Linter @@ -66,5 +70,6 @@ func NewPullCommand() *cobra.Command { flags := cmd.Flags() flags.StringVarP(&options.Linter, "linter", "l", "", "Override linter to use") flags.StringVarP(&options.ProjectDir, "project-dir", "i", ".", "Root directory of the inspected project") + flags.StringVarP(&options.YamlName, "yaml-name", "y", "", "Override qodana.yaml name") return cmd } diff --git a/cmd/scan.go b/cmd/scan.go index 8d23363a..96634651 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -43,14 +43,17 @@ But you can always override qodana.yaml options with the following command-line }, Run: func(cmd *cobra.Command, args []string) { ctx := cmd.Context() + if options.YamlName == "" { + options.YamlName = core.FindQodanaYaml(options.ProjectDir) + } if options.Linter == "" { - qodanaYaml := core.LoadQodanaYaml(options.ProjectDir) + qodanaYaml := core.LoadQodanaYaml(options.ProjectDir, options.YamlName) if qodanaYaml.Linter == "" { core.WarningMessage( "No valid qodana.yaml found. Have you run %s? Running that for you...", core.PrimaryBold("qodana init"), ) - options.Linter = core.GetLinter(options.ProjectDir) + options.Linter = core.GetLinter(options.ProjectDir, options.YamlName) core.EmptyMessage() } else { options.Linter = qodanaYaml.Linter @@ -117,6 +120,7 @@ But you can always override qodana.yaml options with the following command-line flags.BoolVar(&options.ClearCache, "clear-cache", false, "Clear the local Qodana cache before running the analysis") flags.BoolVarP(&options.ShowReport, "show-report", "w", false, "Serve HTML report on port") flags.IntVar(&options.Port, "port", 8080, "Port to serve the report on") + flags.StringVar(&options.YamlName, "yaml-name", "", "Override qodana.yaml name to use: 'qodana.yaml' or 'qodana.yml'") flags.StringVarP(&options.AnalysisId, "analysis-id", "a", "", "Unique report identifier (GUID) to be used by Qodana Cloud") flags.StringVarP(&options.Baseline, "baseline", "b", "", "Provide the path to an existing SARIF report to be used in the baseline state calculation") @@ -133,7 +137,7 @@ But you can always override qodana.yaml options with the following command-line flags.StringArrayVar(&options.Property, "property", []string{}, "Set a JVM property to be used while running Qodana using the --property property.name=value1,value2,...,valueN notation") flags.BoolVarP(&options.SaveReport, "save-report", "s", true, "Generate HTML report") - flags.BoolVar(&options.SendReport, "send-report", false, "Send the inspection report to Qodana Cloud, requires the '--token' option to be specified") + flags.BoolVar(&options.SendReport, "send-report", false, "Send the inspection report to Qodana Cloud, requires the 'QODANA_TOKEN' environment variable to be declared") flags.SortFlags = false diff --git a/cmd/show.go b/cmd/show.go index be392368..b5f71fad 100644 --- a/cmd/show.go +++ b/cmd/show.go @@ -31,6 +31,7 @@ type ShowOptions struct { ReportDir string Port int OpenDir bool + YamlName string } // NewShowCommand returns a new instance of the show command. @@ -46,8 +47,11 @@ be viewed via the file:// protocol (by double-clicking the index.html file). https://www.jetbrains.com/help/qodana/html-report.html This command serves the Qodana report locally and opens a browser to it.`, Run: func(cmd *cobra.Command, args []string) { + if options.YamlName == "" { + options.YamlName = core.FindQodanaYaml(options.ProjectDir) + } if options.ReportDir == "" { - linter := core.LoadQodanaYaml(options.ProjectDir).Linter + linter := core.LoadQodanaYaml(options.ProjectDir, options.YamlName).Linter if linter == "" { log.Fatalf("Can't automatically find the report...\n" + "Please specify the report directory with the --report-dir flag.") @@ -74,5 +78,6 @@ This command serves the Qodana report locally and opens a browser to it.`, "Specify HTML report path (the one with index.html inside) (default /JetBrains//results/report)") flags.IntVarP(&options.Port, "port", "p", 8080, "Specify port to serve report at") flags.BoolVarP(&options.OpenDir, "dir-only", "d", false, "Open report directory only, don't serve it") + flags.StringVarP(&options.YamlName, "yaml-name", "y", "", "Override qodana.yaml name") return cmd } diff --git a/core/common.go b/core/common.go index a243bafe..c2115387 100644 --- a/core/common.go +++ b/core/common.go @@ -54,12 +54,13 @@ type QodanaOptions struct { PrintProblems bool SkipPull bool ClearCache bool + YamlName string } var Version = "dev" -// GetLinter gets linter for the given path -func GetLinter(path string) string { +// GetLinter gets linter for the given path and saves configName +func GetLinter(path string, yamlName string) string { var linters []string var linter string printProcess(func() { @@ -67,11 +68,15 @@ func GetLinter(path string) string { if len(languages) == 0 { languages, _ = recognizeDirLanguages(path) } - WarningMessage("Detected technologies: " + strings.Join(languages, ", ") + "\n") - for _, language := range languages { - if linter, err := langsLinters[language]; err { - for _, l := range linter { - linters = Append(linters, l) + if len(languages) == 0 { + WarningMessage("No technologies detected (no source code files?)\n") + } else { + WarningMessage("Detected technologies: " + strings.Join(languages, ", ") + "\n") + for _, language := range languages { + if linter, err := langsLinters[language]; err { + for _, l := range linter { + linters = Append(linters, l) + } } } } @@ -95,7 +100,7 @@ func GetLinter(path string) string { } if linter != "" { log.Infof("Detected linters: %s", strings.Join(linters, ", ")) - WriteQodanaYaml(path, linter) + SetQodanaLinter(path, linter, yamlName) } SuccessMessage("Added %s", linter) return linter diff --git a/core/configurator.go b/core/configurator.go index 4448bbd3..f87c0b8c 100644 --- a/core/configurator.go +++ b/core/configurator.go @@ -30,14 +30,15 @@ import ( ) const ( - version = "2022.1" - eap = "-eap" - QDJVMC = "jetbrains/qodana-jvm-community:" + version - QDJVM = "jetbrains/qodana-jvm:" + version + eap - QDAND = "jetbrains/qodana-jvm-android:" + version + eap - QDPHP = "jetbrains/qodana-php:" + version + eap - QDPY = "jetbrains/qodana-python:" + version + eap - QDJS = "jetbrains/qodana-js:" + version + eap + configName = "qodana" + version = "2022.1" + eap = "-eap" + QDJVMC = "jetbrains/qodana-jvm-community:" + version + QDJVM = "jetbrains/qodana-jvm:" + version + eap + QDAND = "jetbrains/qodana-jvm-android:" + version + eap + QDPHP = "jetbrains/qodana-php:" + version + eap + QDPY = "jetbrains/qodana-python:" + version + eap + QDJS = "jetbrains/qodana-js:" + version + eap ) var langsLinters = map[string][]string{ diff --git a/core/yaml.go b/core/yaml.go index eb7cb56d..c8800e0b 100644 --- a/core/yaml.go +++ b/core/yaml.go @@ -43,11 +43,11 @@ type QodanaYaml struct { // FailThreshold is a number of problems to fail the analysis (to exit from Qodana with code 255). FailThreshold int `yaml:"failThreshold,omitempty"` - // Exclude property to disable the wanted checks on the wanted paths. - Excludes []Exclude `yaml:"exclude,omitempty"` + // Clude property to disable the wanted checks on the wanted paths. + Excludes []Clude `yaml:"exclude,omitempty"` // Include property to enable the wanted checks. - Includes []Include `yaml:"include,omitempty"` + Includes []Clude `yaml:"include,omitempty"` // Properties property to override IDE properties. Properties map[string]string `yaml:"properties,omitempty"` @@ -80,21 +80,15 @@ type Profile struct { Path string `yaml:"path,omitempty"` } -// Exclude A check id to disable. -type Exclude struct { - // The name of check to exclude. +// Clude A check id to enable/disable for include/exclude YAML field. +type Clude struct { + // The name of check to include/exclude. Name string `yaml:"name"` - // Relative to the project root path to disable analysis. + // Relative to the project root path to enable/disable analysis. Paths []string `yaml:"paths,omitempty"` } -// Include A check id to enable. -type Include struct { - // The name of check to exclude. - Name string `yaml:"name"` -} - // Plugin to be installed during the Qodana run. type Plugin struct { // Id plugin id to install. @@ -158,13 +152,20 @@ type CustomDependency struct { Licenses []License `yaml:"licenses"` } +// FindQodanaYaml checks whether qodana.yaml exists or not +func FindQodanaYaml(project string) string { + filename := configName + ".yml" + if info, _ := os.Stat(filepath.Join(project, filename)); info != nil { + return filename + } else { + return configName + ".yaml" + } +} + // LoadQodanaYaml gets Qodana YAML from the project. -func LoadQodanaYaml(project string) *QodanaYaml { +func LoadQodanaYaml(project string, filename string) *QodanaYaml { q := &QodanaYaml{} - qodanaYamlPath := filepath.Join(project, "qodana.yaml") - if _, err := os.Stat(qodanaYamlPath); errors.Is(err, os.ErrNotExist) { - qodanaYamlPath = filepath.Join(project, "qodana.yml") - } + qodanaYamlPath := filepath.Join(project, filename) if _, err := os.Stat(qodanaYamlPath); errors.Is(err, os.ErrNotExist) { return q } @@ -210,9 +211,9 @@ func (q *QodanaYaml) sort() *QodanaYaml { return q } -// WriteQodanaYaml writes the qodana.yaml file to the given path. -func WriteQodanaYaml(path string, linter string) { - q := LoadQodanaYaml(path) +// SetQodanaLinter writes the qodana.yaml file to the given path. +func SetQodanaLinter(path string, linter string, filename string) { + q := LoadQodanaYaml(path, filename) if q.Version == "" { q.Version = "1.0" } @@ -225,7 +226,7 @@ func WriteQodanaYaml(path string, linter string) { if err != nil { return } - err = ioutil.WriteFile(filepath.Join(path, "qodana.yaml"), b.Bytes(), 0o600) + err = ioutil.WriteFile(filepath.Join(path, filename), b.Bytes(), 0o600) if err != nil { log.Fatalf("Marshal: %v", err) }