diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f9c830c..4812602d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,11 @@ jobs: with: cache-dependency-path: "**/*.sum" go-version-file: go.work + - name: Set up Java 17 for config-loader-cli.jar + uses: actions/setup-java@v3 + with: + distribution: 'temurin' # Use the Temurin JDK distribution + java-version: '17' # Java version 17 - name: Overwrite tools (to run tests only on gh repo) run: | touch cdnet/clt.zip tooling/baseline-cli.jar tooling/intellij-report-converter.jar tooling/qodana-fuser.jar @@ -45,6 +50,11 @@ jobs: ) EOF shell: bash + - name: Download config-loader-cli.jar + run: | + chmod +x download_deps.sh + ./download_deps.sh + shell: bash - name: Set up gotestfmt uses: gotesttools/gotestfmt-action@v2 with: diff --git a/cdnet/options.go b/cdnet/options.go index 8f29786f..50818728 100644 --- a/cdnet/options.go +++ b/cdnet/options.go @@ -42,27 +42,28 @@ func (l CdnetLinter) computeCdnetArgs(c thirdpartyscan.Context) ([]string, error } props += p } + dotNet := c.QodanaYamlConfig().DotNet if c.CdnetConfiguration() != "" { if props != "" { props += ";" } props += "Configuration=" + c.CdnetConfiguration() - } else if c.QodanaYaml().DotNet.Configuration != "" { + } else if dotNet.Configuration != "" { if props != "" { props += ";" } - props += "Configuration=" + c.QodanaYaml().DotNet.Configuration + props += "Configuration=" + dotNet.Configuration } if c.CdnetPlatform() != "" { if props != "" { props += ";" } props += "Platform=" + c.CdnetPlatform() - } else if c.QodanaYaml().DotNet.Platform != "" { + } else if dotNet.Platform != "" { if props != "" { props += ";" } - props += "Platform=" + c.QodanaYaml().DotNet.Platform + props += "Platform=" + dotNet.Platform } mountInfo := c.MountInfo() @@ -94,8 +95,8 @@ func getSolutionOrProject(c thirdpartyscan.Context) string { paths := [4]string{ c.CdnetSolution(), c.CdnetProject(), - c.QodanaYaml().DotNet.Solution, - c.QodanaYaml().DotNet.Project, + c.QodanaYamlConfig().DotNet.Solution, + c.QodanaYamlConfig().DotNet.Project, } for _, path := range paths { if path != "" { diff --git a/cdnet/options_test.go b/cdnet/options_test.go index 6245da73..6edfceb0 100644 --- a/cdnet/options_test.go +++ b/cdnet/options_test.go @@ -28,8 +28,8 @@ import ( "testing" ) -func createDefaultYaml(sln string, prj string, cfg string, plt string) qdyaml.QodanaYaml { - return qdyaml.QodanaYaml{ +func createDefaultYaml(sln string, prj string, cfg string, plt string) thirdpartyscan.QodanaYamlConfig { + return thirdpartyscan.QodanaYamlConfig{ DotNet: qdyaml.DotNet{ Solution: sln, Project: prj, @@ -49,9 +49,9 @@ func TestComputeCdnetArgs(t *testing.T) { { name: "No solution/project specified", cb: thirdpartyscan.ContextBuilder{ - Property: []string{}, - ResultsDir: "", - QodanaYaml: createDefaultYaml("", "", "", ""), + Property: []string{}, + ResultsDir: "", + QodanaYamlConfig: createDefaultYaml("", "", "", ""), }, expectedArgs: nil, expectedErr: "solution/project relative file path is not specified. Use --solution or --project flags or create qodana.yaml file with respective fields", @@ -59,10 +59,10 @@ func TestComputeCdnetArgs(t *testing.T) { { name: "project specified", cb: thirdpartyscan.ContextBuilder{ - Property: []string{}, - ResultsDir: "", - CdnetProject: "project", - QodanaYaml: createDefaultYaml("", "", "", ""), + Property: []string{}, + ResultsDir: "", + CdnetProject: "project", + QodanaYamlConfig: createDefaultYaml("", "", "", ""), }, expectedArgs: []string{ "dotnet", @@ -78,9 +78,9 @@ func TestComputeCdnetArgs(t *testing.T) { { name: "project specified in yaml", cb: thirdpartyscan.ContextBuilder{ - Property: []string{}, - ResultsDir: "", - QodanaYaml: createDefaultYaml("", "project", "", ""), + Property: []string{}, + ResultsDir: "", + QodanaYamlConfig: createDefaultYaml("", "project", "", ""), }, expectedArgs: []string{ "dotnet", @@ -96,10 +96,10 @@ func TestComputeCdnetArgs(t *testing.T) { { name: "solution specified", cb: thirdpartyscan.ContextBuilder{ - Property: []string{}, - ResultsDir: "", - CdnetSolution: "solution", - QodanaYaml: createDefaultYaml("", "", "", ""), + Property: []string{}, + ResultsDir: "", + CdnetSolution: "solution", + QodanaYamlConfig: createDefaultYaml("", "", "", ""), }, expectedArgs: []string{ "dotnet", @@ -115,9 +115,9 @@ func TestComputeCdnetArgs(t *testing.T) { { name: "solution specified", cb: thirdpartyscan.ContextBuilder{ - Property: []string{}, - ResultsDir: "", - QodanaYaml: createDefaultYaml("solution", "", "", ""), + Property: []string{}, + ResultsDir: "", + QodanaYamlConfig: createDefaultYaml("solution", "", "", ""), }, expectedArgs: []string{ "dotnet", @@ -133,9 +133,9 @@ func TestComputeCdnetArgs(t *testing.T) { { name: "configuration specified in yaml", cb: thirdpartyscan.ContextBuilder{ - Property: []string{}, - ResultsDir: "", - QodanaYaml: createDefaultYaml("solution", "", "cfg", ""), + Property: []string{}, + ResultsDir: "", + QodanaYamlConfig: createDefaultYaml("solution", "", "cfg", ""), }, expectedArgs: []string{ "dotnet", @@ -155,7 +155,7 @@ func TestComputeCdnetArgs(t *testing.T) { Property: []string{}, ResultsDir: "", CdnetConfiguration: "cfg", - QodanaYaml: createDefaultYaml("solution", "", "", ""), + QodanaYamlConfig: createDefaultYaml("solution", "", "", ""), }, expectedArgs: []string{ "dotnet", @@ -172,9 +172,9 @@ func TestComputeCdnetArgs(t *testing.T) { { name: "platform specified in cfg", cb: thirdpartyscan.ContextBuilder{ - Property: []string{}, - ResultsDir: "", - QodanaYaml: createDefaultYaml("solution", "", "", "x64"), + Property: []string{}, + ResultsDir: "", + QodanaYamlConfig: createDefaultYaml("solution", "", "", "x64"), }, expectedArgs: []string{ "dotnet", @@ -191,10 +191,10 @@ func TestComputeCdnetArgs(t *testing.T) { { name: "platform specified", cb: thirdpartyscan.ContextBuilder{ - Property: []string{}, - ResultsDir: "", - CdnetPlatform: "x64", - QodanaYaml: createDefaultYaml("solution", "", "", ""), + Property: []string{}, + ResultsDir: "", + CdnetPlatform: "x64", + QodanaYamlConfig: createDefaultYaml("solution", "", "", ""), }, expectedArgs: []string{ "dotnet", @@ -215,7 +215,7 @@ func TestComputeCdnetArgs(t *testing.T) { ResultsDir: "", CdnetPlatform: "x64", CdnetConfiguration: "Debug", - QodanaYaml: createDefaultYaml("solution", "", "", ""), + QodanaYamlConfig: createDefaultYaml("solution", "", "", ""), }, expectedArgs: []string{ "dotnet", @@ -232,10 +232,10 @@ func TestComputeCdnetArgs(t *testing.T) { { name: "no-build", cb: thirdpartyscan.ContextBuilder{ - Property: []string{}, - ResultsDir: "", - CdnetNoBuild: true, - QodanaYaml: createDefaultYaml("solution", "", "", ""), + Property: []string{}, + ResultsDir: "", + CdnetNoBuild: true, + QodanaYamlConfig: createDefaultYaml("solution", "", "", ""), }, expectedArgs: []string{ "dotnet", @@ -261,8 +261,8 @@ func TestComputeCdnetArgs(t *testing.T) { "idea.diagnostic.opentelemetry.file=/data/results/log/open-telemetry.json", "jetbrains.security.package-checker.synchronizationTimeout=1000", }, - ResultsDir: "", - QodanaYaml: createDefaultYaml("solution", "", "", ""), + ResultsDir: "", + QodanaYamlConfig: createDefaultYaml("solution", "", "", ""), }, expectedArgs: []string{ "dotnet", diff --git a/cdnet/run.go b/cdnet/run.go index e3fd3590..10e0a967 100644 --- a/cdnet/run.go +++ b/cdnet/run.go @@ -39,7 +39,7 @@ func (l CdnetLinter) ComputeNewLinterInfo( } func (l CdnetLinter) RunAnalysis(c thirdpartyscan.Context) error { - utils.Bootstrap(c.QodanaYaml().Bootstrap, c.ProjectDir()) + utils.Bootstrap(c.QodanaYamlConfig().Bootstrap, c.ProjectDir()) args, err := l.computeCdnetArgs(c) if err != nil { return err diff --git a/clang b/clang index cbb59d5e..5b48c17b 160000 --- a/clang +++ b/clang @@ -1 +1 @@ -Subproject commit cbb59d5e70016c00528f21bc45dc65aef46015bb +Subproject commit 5b48c17b3d00a6296339705fc81d355df7d44a1e diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index 3b020d15..f0321975 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -149,13 +149,13 @@ func TestInitCommand(t *testing.T) { t.Fatal(err) } - filename := qdyaml.FindDefaultQodanaYaml(projectPath) + updatedQodanaYamlPath := qdyaml.GetLocalNotEffectiveQodanaYamlFullPath(projectPath, "") - if filename != "qodana.yml" { - t.Fatalf("expected \"qodana.yml\" got \"%s\"", filename) + if !strings.HasSuffix(updatedQodanaYamlPath, "qodana.yml") { + t.Fatalf("expected \"qodana.yml\" got \"%s\"", updatedQodanaYamlPath) } - qodanaYaml := qdyaml.LoadQodanaYaml(projectPath, filename) + qodanaYaml := qdyaml.LoadQodanaYamlByFullPath(updatedQodanaYamlPath) if qodanaYaml.Linter != product.Image(product.QDPY) { t.Fatalf("expected \"%s\", but got %s", product.Image(product.QDPY), qodanaYaml.Linter) diff --git a/cmd/init.go b/cmd/init.go index d31a13aa..5c16b39f 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -38,8 +38,14 @@ 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) { - cliOptions.ConfigName = qdyaml.FindDefaultQodanaYaml(cliOptions.ProjectDir) - qodanaYaml := qdyaml.LoadQodanaYaml(cliOptions.ProjectDir, cliOptions.ConfigName) + localQodanaYamlFullPath := qdyaml.GetLocalNotEffectiveQodanaYamlFullPath( + cliOptions.ProjectDir, + cliOptions.ConfigName, + ) + if localQodanaYamlFullPath == "" { + localQodanaYamlFullPath = filepath.Join(cliOptions.ProjectDir, "qodana.yaml") + } + qodanaYaml := qdyaml.LoadQodanaYamlByFullPath(localQodanaYamlFullPath) ide := qodanaYaml.Ide linter := qodanaYaml.Linter @@ -61,9 +67,8 @@ func newInitCommand() *cobra.Command { analyzer := commoncontext.GetAnalyzer(cliOptions.ProjectDir, token) qdyaml.WriteQodanaLinterToYamlFile( - cliOptions.ProjectDir, + localQodanaYamlFullPath, analyzer, - cliOptions.ConfigName, product.AllCodes, ) if product.IsNativeAnalyzer(analyzer) { @@ -86,11 +91,11 @@ func newInitCommand() *cobra.Command { ) } if msg.IsInteractive() && qodanaYaml.IsDotNet() && (qodanaYaml.DotNet.IsEmpty() || cliOptions.Force) { - if commoncontext.GetAndSaveDotNetConfig(cliOptions.ProjectDir, cliOptions.ConfigName) { + if commoncontext.GetAndSaveDotNetConfig(cliOptions.ProjectDir, localQodanaYamlFullPath) { msg.SuccessMessage("The .NET configuration was successfully set") } } - msg.PrintFile(filepath.Join(cliOptions.ProjectDir, cliOptions.ConfigName)) + msg.PrintFile(localQodanaYamlFullPath) commonCtx := commoncontext.Compute( linter, diff --git a/cmd/pull.go b/cmd/pull.go index efe96456..ca9c0e4c 100644 --- a/cmd/pull.go +++ b/cmd/pull.go @@ -21,7 +21,6 @@ import ( "github.com/JetBrains/qodana-cli/v2025/platform/commoncontext" "github.com/JetBrains/qodana-cli/v2025/platform/qdcontainer" "github.com/JetBrains/qodana-cli/v2025/platform/qdenv" - "github.com/JetBrains/qodana-cli/v2025/platform/qdyaml" "github.com/docker/docker/client" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -36,10 +35,6 @@ func newPullCommand() *cobra.Command { Short: "Pull latest version of linter", Long: `An alternative to pull an image.`, Run: func(cmd *cobra.Command, args []string) { - if cliOptions.ConfigName == "" { - cliOptions.ConfigName = qdyaml.FindDefaultQodanaYaml(cliOptions.ProjectDir) - } - commonCtx := commoncontext.Compute( cliOptions.Linter, "", diff --git a/cmd/scan.go b/cmd/scan.go index e1885e59..4c35de63 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -24,6 +24,7 @@ import ( "github.com/JetBrains/qodana-cli/v2025/platform" "github.com/JetBrains/qodana-cli/v2025/platform/cmd" "github.com/JetBrains/qodana-cli/v2025/platform/commoncontext" + "github.com/JetBrains/qodana-cli/v2025/platform/effectiveconfig" "github.com/JetBrains/qodana-cli/v2025/platform/msg" "github.com/JetBrains/qodana-cli/v2025/platform/qdenv" "github.com/JetBrains/qodana-cli/v2025/platform/qdyaml" @@ -50,8 +51,6 @@ But you can always override qodana.yaml options with the following command-line Run: func(cmd *cobra.Command, args []string) { ctx := cmd.Context() - qodanaYaml := qdyaml.LoadQodanaYaml(cliOptions.ProjectDir, cliOptions.ConfigName) - commonCtx := commoncontext.Compute( cliOptions.Linter, cliOptions.Ide, @@ -67,7 +66,39 @@ But you can always override qodana.yaml options with the following command-line checkProjectDir(commonCtx.ProjectDir) preparedHost := startup.PrepareHost(commonCtx) - scanContext := corescan.CreateContext(*cliOptions, commonCtx, preparedHost, qodanaYaml) + + effectiveConfigFiles := effectiveconfig.Files{} + qodanaYamlConfig := corescan.QodanaYamlConfig{} + if commonCtx.Ide != "" { + var err error + localQodanaYamlFullPath := qdyaml.GetLocalNotEffectiveQodanaYamlFullPath( + commonCtx.ProjectDir, + cliOptions.ConfigName, + ) + effectiveConfigFiles, err = effectiveconfig.CreateEffectiveConfigFiles( + localQodanaYamlFullPath, + cliOptions.GlobalConfigurationsFile, + cliOptions.GlobalConfigurationId, + preparedHost.Prod.JbrJava(), + commonCtx.QodanaSystemDir, + "qdconfig", + commonCtx.LogDir(), + ) + if err != nil { + log.Fatalf("Failed to load Qodana configuration %s", err) + } + if effectiveConfigFiles.EffectiveQodanaYamlPath != "" { + yaml := qdyaml.LoadQodanaYamlByFullPath(effectiveConfigFiles.EffectiveQodanaYamlPath) + qodanaYamlConfig = corescan.YamlConfig(yaml) + } + } + scanContext := corescan.CreateContext( + *cliOptions, + commonCtx, + preparedHost, + qodanaYamlConfig, + effectiveConfigFiles.ConfigDir, + ) exitCode := core.RunAnalysis(ctx, scanContext) if qdenv.IsContainer() { diff --git a/core/container.go b/core/container.go index 9c3b566a..7c2806cd 100644 --- a/core/container.go +++ b/core/container.go @@ -53,6 +53,8 @@ const ( officialImagePrefix = "jetbrains/qodana" dockerSpecialCharsLength = 8 containerJvmDebugPort = "5005" + // when container is launched by CLI, qodana-global-configurations.yaml file is mounted here + globalConfigsFileContainerMountPath = "/data/qdconfig/qodana-global-configurations.yaml" ) var ( @@ -318,6 +320,23 @@ func getDockerOptions(c corescan.Context) *backend.ContainerCreateConfig { Target: "/data/results", }, } + if c.GlobalConfigurationsFile() != "" { + globalConfigurationsAbsPath, err := filepath.Abs(c.GlobalConfigurationsFile()) + if err != nil { + log.Fatalf( + "Failed to get absolute path for global configurations file %s: %s", + c.GlobalConfigurationsFile(), + err, + ) + } + volumes = append( + volumes, mount.Mount{ + Type: mount.TypeBind, + Source: globalConfigurationsAbsPath, + Target: globalConfigsFileContainerMountPath, + }, + ) + } for _, volume := range c.Volumes() { source, target := extractDockerVolumes(volume) if source != "" && target != "" { diff --git a/core/core_test.go b/core/core_test.go index ab3e2d26..422512e6 100644 --- a/core/core_test.go +++ b/core/core_test.go @@ -235,6 +235,47 @@ func TestCliArgs(t *testing.T) { resultsDir, }, }, + { + name: "no --config-dir in <251", + majorVersion: "2024.3", + cb: corescan.ContextBuilder{ + StubProfile: "ignored", + ProjectDir: projectDir, + CacheDir: cacheDir, + ResultsDir: resultsDir, + FixesStrategy: "cleanup", + Ide: "/opt/idea/243", + }, + res: []string{ + filepath.FromSlash("/opt/idea/bin/idea.sh"), + "qodana", + "--cleanup", + projectDir, + resultsDir, + }, + }, + { + name: "--config-dir in >=251", + majorVersion: "2025.1", + cb: corescan.ContextBuilder{ + StubProfile: "ignored", + ProjectDir: projectDir, + CacheDir: cacheDir, + ResultsDir: resultsDir, + FixesStrategy: "cleanup", + EffectiveConfigurationDir: "/qdconfig", + Ide: "/opt/idea/251", + }, + res: []string{ + filepath.FromSlash("/opt/idea/bin/idea.sh"), + "qodana", + "--cleanup", + "--config-dir", + "/qdconfig", + projectDir, + resultsDir, + }, + }, } { t.Run( tc.name, func(t *testing.T) { @@ -668,7 +709,7 @@ func Test_Bootstrap(t *testing.T) { } projectDir := tmpDir utils.Bootstrap("echo 'bootstrap: touch qodana.yml' > qodana.yaml", projectDir) - config := qdyaml.GetQodanaYamlOrDefault(tmpDir) + config := qdyaml.TestOnlyLoadLocalNotEffectiveQodanaYaml(tmpDir, "qodana.yaml") utils.Bootstrap(config.Bootstrap, projectDir) if _, err := os.Stat(filepath.Join(projectDir, "qodana.yaml")); errors.Is(err, os.ErrNotExist) { t.Fatalf("No qodana.yml created by the bootstrap command in qodana.yaml") @@ -1107,7 +1148,7 @@ func Test_Properties(t *testing.T) { if err != nil { t.Fatal(err) } - qConfig := qdyaml.GetQodanaYamlOrDefault(projectDir) + qConfig := qdyaml.TestOnlyLoadLocalNotEffectiveQodanaYaml(projectDir, "qodana.yml") context := corescan.CreateContext( platformcmd.CliOptions{ @@ -1125,7 +1166,8 @@ func Test_Properties(t *testing.T) { Version: "2023.3", }, }, - qConfig, + corescan.YamlConfig(qConfig), + "", ) actual := GetScanProperties(context) assert.Equal(t, tc.expected, actual) diff --git a/core/corescan/context.go b/core/corescan/context.go index 68ab0449..9099627e 100644 --- a/core/corescan/context.go +++ b/core/corescan/context.go @@ -18,10 +18,12 @@ package corescan import ( "fmt" + "github.com/JetBrains/qodana-cli/v2025/platform/effectiveconfig" "github.com/JetBrains/qodana-cli/v2025/platform/msg" "github.com/JetBrains/qodana-cli/v2025/platform/product" "github.com/JetBrains/qodana-cli/v2025/platform/qdyaml" "math" + "os" "path/filepath" "strings" "time" @@ -59,7 +61,12 @@ type Context struct { ide string id string ideDir string - qodanaYaml qdyaml.QodanaYaml + effectiveConfigurationDir string + globalConfigurationsFile string + globalConfigurationId string + customLocalQodanaYamlPath string + effectiveYamlData effectiveconfig.Files + qodanaYamlConfig QodanaYamlConfig prod product.Product qodanaUploadToken string projectDir string @@ -115,71 +122,92 @@ type Context struct { jvmDebugPort int } -func (c Context) Linter() string { return c.linter } -func (c Context) Ide() string { return c.ide } -func (c Context) Id() string { return c.id } -func (c Context) IdeDir() string { return c.ideDir } -func (c Context) QodanaYaml() qdyaml.QodanaYaml { return c.qodanaYaml } -func (c Context) Prod() product.Product { return c.prod } -func (c Context) QodanaUploadToken() string { return c.qodanaUploadToken } -func (c Context) ProjectDir() string { return c.projectDir } -func (c Context) ResultsDir() string { return c.resultsDir } -func (c Context) ConfigDir() string { return c.configDir } -func (c Context) LogDir() string { return c.logDir } -func (c Context) QodanaSystemDir() string { return c.qodanaSystemDir } -func (c Context) CacheDir() string { return c.cacheDir } -func (c Context) ReportDir() string { return c.reportDir } -func (c Context) CoverageDir() string { return c.coverageDir } -func (c Context) SourceDirectory() string { return c.sourceDirectory } -func (c Context) DisableSanity() bool { return c.disableSanity } -func (c Context) ProfileName() string { return c.profileName } -func (c Context) ProfilePath() string { return c.profilePath } -func (c Context) RunPromo() string { return c.runPromo } -func (c Context) StubProfile() string { return c.stubProfile } -func (c Context) Baseline() string { return c.baseline } -func (c Context) BaselineIncludeAbsent() bool { return c.baselineIncludeAbsent } -func (c Context) SaveReport() bool { return c.saveReport } -func (c Context) ShowReport() bool { return c.showReport } -func (c Context) Port() int { return c.port } -func (c Context) Script() string { return c.script } -func (c Context) FailThreshold() string { return c.failThreshold } -func (c Context) Commit() string { return c.commit } -func (c Context) DiffStart() string { return c.diffStart } -func (c Context) DiffEnd() string { return c.diffEnd } -func (c Context) ForceLocalChangesScript() bool { return c.forceLocalChangesScript } -func (c Context) AnalysisId() string { return c.analysisId } -func (c Context) User() string { return c.user } -func (c Context) PrintProblems() bool { return c.printProblems } -func (c Context) GenerateCodeClimateReport() bool { return c.generateCodeClimateReport } -func (c Context) SendBitBucketInsights() bool { return c.sendBitBucketInsights } -func (c Context) SkipPull() bool { return c.skipPull } -func (c Context) ClearCache() bool { return c.clearCache } -func (c Context) ConfigName() string { return c.configName } -func (c Context) FullHistory() bool { return c.fullHistory } -func (c Context) ApplyFixes() bool { return c.applyFixes } -func (c Context) Cleanup() bool { return c.cleanup } -func (c Context) FixesStrategy() string { return c.fixesStrategy } -func (c Context) NoStatistics() bool { return c.noStatistics } -func (c Context) CdnetSolution() string { return c.cdnetSolution } -func (c Context) CdnetProject() string { return c.cdnetProject } -func (c Context) CdnetConfiguration() string { return c.cdnetConfiguration } -func (c Context) CdnetPlatform() string { return c.cdnetPlatform } -func (c Context) CdnetNoBuild() bool { return c.cdnetNoBuild } -func (c Context) ClangCompileCommands() string { return c.clangCompileCommands } -func (c Context) ClangArgs() string { return c.clangArgs } -func (c Context) AnalysisTimeoutMs() int { return c.analysisTimeoutMs } -func (c Context) AnalysisTimeoutExitCode() int { return c.analysisTimeoutExitCode } -func (c Context) JvmDebugPort() int { return c.jvmDebugPort } -func (c Context) Env() []string { return arrayCopy(c._env) } -func (c Context) Property() []string { return arrayCopy(c._property) } -func (c Context) Volumes() []string { return arrayCopy(c._volumes) } +// QodanaYamlConfig fields from qodana.yaml used in CLI for core linters (also `linter` and `ide`) +type QodanaYamlConfig struct { + Bootstrap string + Plugins []qdyaml.Plugin + Properties map[string]string + DotNet qdyaml.DotNet +} + +func YamlConfig(yaml qdyaml.QodanaYaml) QodanaYamlConfig { + return QodanaYamlConfig{ + Bootstrap: yaml.Bootstrap, + Plugins: yaml.Plugins, + Properties: yaml.Properties, + DotNet: yaml.DotNet, + } +} + +func (c Context) Linter() string { return c.linter } +func (c Context) Ide() string { return c.ide } +func (c Context) Id() string { return c.id } +func (c Context) IdeDir() string { return c.ideDir } +func (c Context) EffectiveConfigurationDir() string { return c.effectiveConfigurationDir } +func (c Context) GlobalConfigurationsFile() string { return c.globalConfigurationsFile } +func (c Context) GlobalConfigurationId() string { return c.globalConfigurationId } +func (c Context) CustomLocalQodanaYamlPath() string { return c.customLocalQodanaYamlPath } +func (c Context) QodanaYamlConfig() QodanaYamlConfig { return c.qodanaYamlConfig } +func (c Context) Prod() product.Product { return c.prod } +func (c Context) QodanaUploadToken() string { return c.qodanaUploadToken } +func (c Context) ProjectDir() string { return c.projectDir } +func (c Context) ResultsDir() string { return c.resultsDir } +func (c Context) ConfigDir() string { return c.configDir } +func (c Context) LogDir() string { return c.logDir } +func (c Context) QodanaSystemDir() string { return c.qodanaSystemDir } +func (c Context) CacheDir() string { return c.cacheDir } +func (c Context) ReportDir() string { return c.reportDir } +func (c Context) CoverageDir() string { return c.coverageDir } +func (c Context) SourceDirectory() string { return c.sourceDirectory } +func (c Context) DisableSanity() bool { return c.disableSanity } +func (c Context) ProfileName() string { return c.profileName } +func (c Context) ProfilePath() string { return c.profilePath } +func (c Context) RunPromo() string { return c.runPromo } +func (c Context) StubProfile() string { return c.stubProfile } +func (c Context) Baseline() string { return c.baseline } +func (c Context) BaselineIncludeAbsent() bool { return c.baselineIncludeAbsent } +func (c Context) SaveReport() bool { return c.saveReport } +func (c Context) ShowReport() bool { return c.showReport } +func (c Context) Port() int { return c.port } +func (c Context) Script() string { return c.script } +func (c Context) FailThreshold() string { return c.failThreshold } +func (c Context) Commit() string { return c.commit } +func (c Context) DiffStart() string { return c.diffStart } +func (c Context) DiffEnd() string { return c.diffEnd } +func (c Context) ForceLocalChangesScript() bool { return c.forceLocalChangesScript } +func (c Context) AnalysisId() string { return c.analysisId } +func (c Context) User() string { return c.user } +func (c Context) PrintProblems() bool { return c.printProblems } +func (c Context) GenerateCodeClimateReport() bool { return c.generateCodeClimateReport } +func (c Context) SendBitBucketInsights() bool { return c.sendBitBucketInsights } +func (c Context) SkipPull() bool { return c.skipPull } +func (c Context) ClearCache() bool { return c.clearCache } +func (c Context) ConfigName() string { return c.configName } +func (c Context) FullHistory() bool { return c.fullHistory } +func (c Context) ApplyFixes() bool { return c.applyFixes } +func (c Context) Cleanup() bool { return c.cleanup } +func (c Context) FixesStrategy() string { return c.fixesStrategy } +func (c Context) NoStatistics() bool { return c.noStatistics } +func (c Context) CdnetSolution() string { return c.cdnetSolution } +func (c Context) CdnetProject() string { return c.cdnetProject } +func (c Context) CdnetConfiguration() string { return c.cdnetConfiguration } +func (c Context) CdnetPlatform() string { return c.cdnetPlatform } +func (c Context) CdnetNoBuild() bool { return c.cdnetNoBuild } +func (c Context) ClangCompileCommands() string { return c.clangCompileCommands } +func (c Context) ClangArgs() string { return c.clangArgs } +func (c Context) AnalysisTimeoutMs() int { return c.analysisTimeoutMs } +func (c Context) AnalysisTimeoutExitCode() int { return c.analysisTimeoutExitCode } +func (c Context) JvmDebugPort() int { return c.jvmDebugPort } +func (c Context) Env() []string { return arrayCopy(c._env) } +func (c Context) Property() []string { return arrayCopy(c._property) } +func (c Context) Volumes() []string { return arrayCopy(c._volumes) } type ContextBuilder struct { Linter string Ide string Id string IdeDir string - QodanaYaml qdyaml.QodanaYaml + EffectiveConfigurationDir string Prod product.Product QodanaUploadToken string ProjectDir string @@ -233,6 +261,10 @@ type ContextBuilder struct { AnalysisTimeoutMs int AnalysisTimeoutExitCode int JvmDebugPort int + GlobalConfigurationsFile string + GlobalConfigurationId string + CustomLocalQodanaYamlPath string + QodanaYamlConfig QodanaYamlConfig } func (b ContextBuilder) Build() Context { @@ -241,7 +273,7 @@ func (b ContextBuilder) Build() Context { ide: b.Ide, id: b.Id, ideDir: b.IdeDir, - qodanaYaml: b.QodanaYaml, + effectiveConfigurationDir: b.EffectiveConfigurationDir, prod: b.Prod, qodanaUploadToken: b.QodanaUploadToken, projectDir: b.ProjectDir, @@ -295,6 +327,10 @@ func (b ContextBuilder) Build() Context { analysisTimeoutMs: b.AnalysisTimeoutMs, analysisTimeoutExitCode: b.AnalysisTimeoutExitCode, jvmDebugPort: b.JvmDebugPort, + globalConfigurationsFile: b.GlobalConfigurationsFile, + globalConfigurationId: b.GlobalConfigurationId, + customLocalQodanaYamlPath: b.CustomLocalQodanaYamlPath, + qodanaYamlConfig: b.QodanaYamlConfig, } } @@ -369,3 +405,12 @@ func (c Context) GetAnalysisTimeout() time.Duration { } return time.Duration(c.AnalysisTimeoutMs()) * time.Millisecond } + +func (c Context) LocalQodanaYamlExists() bool { + path := qdyaml.GetLocalNotEffectiveQodanaYamlFullPath(c.ProjectDir(), c.CustomLocalQodanaYamlPath()) + if path == "" { + return false + } + info, _ := os.Stat(path) + return info != nil +} diff --git a/core/corescan/context_changes.go b/core/corescan/context_changes.go index 57be5012..31fa2497 100644 --- a/core/corescan/context_changes.go +++ b/core/corescan/context_changes.go @@ -90,6 +90,13 @@ func (c Context) ForcedLocalChanges() Context { return c } +// in diff-start, diff-end scenario, for analysis of revision, we reset only effectiveConfigurationDir (which is used by IJ). +// Fields used by CLI besides bootsrap stay the same +func (c Context) WithEffectiveConfigurationDirOnRevision(effectiveConfigurationDir string) Context { + c.effectiveConfigurationDir = effectiveConfigurationDir + return c +} + func (c Context) withAddedProperties(propertiesToAdd ...string) Context { props := c.Property() props = append(props, propertiesToAdd...) diff --git a/core/corescan/create_context.go b/core/corescan/create_context.go index 89c778c3..dcb2f577 100644 --- a/core/corescan/create_context.go +++ b/core/corescan/create_context.go @@ -21,7 +21,6 @@ import ( "github.com/JetBrains/qodana-cli/v2025/platform/cmd" "github.com/JetBrains/qodana-cli/v2025/platform/commoncontext" "github.com/JetBrains/qodana-cli/v2025/platform/qdenv" - "github.com/JetBrains/qodana-cli/v2025/platform/qdyaml" "path/filepath" "strings" ) @@ -30,7 +29,8 @@ func CreateContext( cliOptions platformcmd.CliOptions, commonCtx commoncontext.Context, preparedHost startup.PreparedHost, - qodanaYaml qdyaml.QodanaYaml, + qodanaYamlConfig QodanaYamlConfig, + effectiveConfigurationDir string, ) Context { coverageDir := cliOptions.CoverageDir if coverageDir == "" { @@ -51,7 +51,7 @@ func CreateContext( Ide: commonCtx.Ide, Id: commonCtx.Id, IdeDir: preparedHost.IdeDir, - QodanaYaml: qodanaYaml, + EffectiveConfigurationDir: effectiveConfigurationDir, Prod: preparedHost.Prod, QodanaUploadToken: preparedHost.QodanaUploadToken, ProjectDir: commonCtx.ProjectDir, @@ -105,5 +105,9 @@ func CreateContext( AnalysisTimeoutMs: cliOptions.AnalysisTimeoutMs, AnalysisTimeoutExitCode: cliOptions.AnalysisTimeoutExitCode, JvmDebugPort: cliOptions.JvmDebugPort, + GlobalConfigurationsFile: cliOptions.GlobalConfigurationsFile, + GlobalConfigurationId: cliOptions.GlobalConfigurationId, + CustomLocalQodanaYamlPath: cliOptions.ConfigName, + QodanaYamlConfig: qodanaYamlConfig, }.Build() } diff --git a/core/ide.go b/core/ide.go index 2a5cb28d..e1bdebee 100644 --- a/core/ide.go +++ b/core/ide.go @@ -151,10 +151,8 @@ func GetIdeArgs(c corescan.Context) []string { } } - prod := product.GuessProductCode( - c.Ide(), - c.Linter(), - ) // TODO : think how it could be better handled in presence of random 3rd party linters + // TODO : think how it could be better handled in presence of random 3rd party linters + prod := product.GuessProductCode(c.Ide(), c.Linter()) if prod == product.QDNETC || prod == product.QDCL { // third party common options if c.NoStatistics() { @@ -210,10 +208,17 @@ func GetIdeArgs(c corescan.Context) []string { if c.JvmDebugPort() > 0 { arguments = append(arguments, "--jvm-debug-port", strconv.Itoa(c.JvmDebugPort())) } - + if c.GlobalConfigurationsFile() != "" { + arguments = append(arguments, "--global-configs-file", globalConfigsFileContainerMountPath) + } + if c.GlobalConfigurationId() != "" { + arguments = append(arguments, "--global-config-id", c.GlobalConfigurationId()) + } for _, property := range c.Property() { arguments = append(arguments, "--property="+property) } + } else if c.Prod().Is251orNewer() { + arguments = append(arguments, "--config-dir", utils.QuoteForWindows(c.EffectiveConfigurationDir())) } return arguments } @@ -240,7 +245,7 @@ func installPlugins(c corescan.Context) { return } - plugins := c.QodanaYaml().Plugins + plugins := c.QodanaYamlConfig().Plugins if len(plugins) > 0 { setInstallPluginsVmoptions(c) } diff --git a/core/properties.go b/core/properties.go index 8014b755..76eaddaf 100644 --- a/core/properties.go +++ b/core/properties.go @@ -114,7 +114,7 @@ func GetInstallPluginsProperties(c corescan.Context) []string { // GetScanProperties writes key=value `props` to file `f` having later key occurrence win func GetScanProperties(c corescan.Context) []string { - yaml := c.QodanaYaml() + yaml := c.QodanaYamlConfig() yamlProps := yaml.Properties dotNetOptions := yaml.DotNet plugins := getPluginIds(yaml.Plugins) diff --git a/core/system.go b/core/system.go index 0f98abfc..2ace2318 100644 --- a/core/system.go +++ b/core/system.go @@ -24,6 +24,7 @@ import ( "github.com/JetBrains/qodana-cli/v2025/core/corescan" "github.com/JetBrains/qodana-cli/v2025/core/startup" "github.com/JetBrains/qodana-cli/v2025/platform" + "github.com/JetBrains/qodana-cli/v2025/platform/effectiveconfig" "github.com/JetBrains/qodana-cli/v2025/platform/git" "github.com/JetBrains/qodana-cli/v2025/platform/msg" "github.com/JetBrains/qodana-cli/v2025/platform/nuget" @@ -155,7 +156,7 @@ func RunAnalysis(ctx context.Context, c corescan.Context) int { installPlugins(c) // this way of running needs to do bootstrap twice on different commits and will do it internally if scenario != corescan.RunScenarioScoped && c.Ide() != "" { - utils.Bootstrap(c.QodanaYaml().Bootstrap, c.ProjectDir()) + utils.Bootstrap(c.QodanaYamlConfig().Bootstrap, c.ProjectDir()) } switch scenario { case corescan.RunScenarioFullHistory: @@ -282,14 +283,37 @@ func runScopeScript(ctx context.Context, c corescan.Context, startHash string) i startup.PrepareDirectories(c.Prod(), c.CacheDir(), c.LogDir(), c.ConfigDir()) log.Infof("Analysing %s", hash) - configAtHash, e := qdyaml.GetQodanaYaml(c.ProjectDir()) - if e != nil { - log.Warnf("Could not read qodana yaml at %s: %v. Using last known config", hash, e) - configAtHash = c.QodanaYaml() + // for CLI, we use only bootstrap from this effective yaml + // all other fields are used from the one (effective aswell) obtained at the start + localQodanaYamlFullPath := qdyaml.GetLocalNotEffectiveQodanaYamlFullPath( + c.ProjectDir(), + c.CustomLocalQodanaYamlPath(), + ) + effectiveConfigFiles, err := effectiveconfig.CreateEffectiveConfigFiles( + localQodanaYamlFullPath, + c.GlobalConfigurationsFile(), + c.GlobalConfigurationId(), + c.Prod().JbrJava(), + c.QodanaSystemDir(), + "qdconfig-"+hash, + c.LogDir(), + ) + if err != nil { + log.Fatalf("Failed to load Qodana configuration during analysis of commit %s: %v", hash, err) + } + + // if local qodana yaml doesn't exist on revision, for bootstrap fallback to the one constructed at the start + var bootstrap string + if c.LocalQodanaYamlExists() { + yaml := qdyaml.LoadQodanaYamlByFullPath(effectiveConfigFiles.EffectiveQodanaYamlPath) + bootstrap = yaml.Bootstrap + } else { + bootstrap = c.QodanaYamlConfig().Bootstrap } - utils.Bootstrap(configAtHash.Bootstrap, c.ProjectDir()) + utils.Bootstrap(bootstrap, c.ProjectDir()) - exitCode := runQodana(ctx, c) // TODO WHY qodana yaml is not passed further to runQodana??? + contextForAnalysis := c.WithEffectiveConfigurationDirOnRevision(effectiveConfigFiles.ConfigDir) + exitCode := runQodana(ctx, contextForAnalysis) // TODO WHY qodana yaml is not passed further to runQodana? if !(exitCode == 0 || exitCode == 255) { log.Errorf("Qodana analysis on %s exited with code %d. Aborting", hash, exitCode) return true, exitCode diff --git a/download_checksum_of_deps.sh b/download_checksum_of_deps.sh new file mode 100755 index 00000000..d37dba29 --- /dev/null +++ b/download_checksum_of_deps.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -euo pipefail + +# Loads checksum of config-loader-cli.jar from maven to /tooling/ dir +# Execute this script LOCALLY ONLY, on CI/CD checksum file must be present + +# Variables +CONFIG_LOADER_CLI_VERSION="0.0.6" +URL="https://packages.jetbrains.team/maven/p/ij/intellij-dependencies/org/jetbrains/qodana/config-loader-cli/$CONFIG_LOADER_CLI_VERSION/config-loader-cli-$CONFIG_LOADER_CLI_VERSION.jar.sha256" +DEST_DIR="tooling" +SHA256_FILE="$DEST_DIR/config-loader-cli-$CONFIG_LOADER_CLI_VERSION.jar.sha256" + +# Ensure the tooling directory exists +mkdir -p "$DEST_DIR" + +# Remove other versions checksum files +find "$DEST_DIR" -name "config-loader-cli-*.jar.sha256" -type f -exec rm -f {} \; + +# Download the SHA256 file +echo "Downloading the SHA256 file..." +curl -o "$SHA256_FILE" -L "$URL" +if [ $? -ne 0 ]; then + echo "Error: Failed to download the SHA256 file." + exit 1 +fi +echo "SHA256 file downloaded: $SHA256_FILE" \ No newline at end of file diff --git a/download_deps.sh b/download_deps.sh new file mode 100755 index 00000000..3919b5ec --- /dev/null +++ b/download_deps.sh @@ -0,0 +1,61 @@ +#!/bin/bash +set -euo pipefail + +# Variables +CONFIG_LOADER_CLI_VERSION="0.0.6" +URL="https://packages.jetbrains.team/maven/p/ij/intellij-dependencies/org/jetbrains/qodana/config-loader-cli/$CONFIG_LOADER_CLI_VERSION/config-loader-cli-$CONFIG_LOADER_CLI_VERSION.jar" +DEST_DIR="tooling" +JAR_FILE="$DEST_DIR/config-loader-cli.jar" +HASH_FILE="$DEST_DIR/config-loader-cli-$CONFIG_LOADER_CLI_VERSION.jar.sha256" + +# Ensure the tooling directory exists +mkdir -p "$DEST_DIR" + +# Download the JAR file +echo "Downloading the JAR file..." +curl -o "$JAR_FILE" -L "$URL" +if [ $? -ne 0 ]; then + echo "Error: Failed to download the JAR file." + exit 1 +fi +echo "Download completed." + +# Verify the SHA256 checksum +echo "Verifying the SHA256 checksum..." +if [ ! -f "$HASH_FILE" ]; then + echo "Error: SHA256 checksum file not found: $HASH_FILE" + exit 1 +fi + +# Function to calculate SHA256 +function calculate_sha256 { + local file="$1" + + if command -v sha256sum > /dev/null; then + # Linux or macOS with sha256sum + sha256sum "$file" | awk '{print $1}' + elif command -v shasum > /dev/null; then + # macOS with shasum + shasum -a 256 "$file" | awk '{print $1}' + elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then + # Windows with CertUtil + certutil -hashfile "$file" SHA256 | findstr /v "SHA256" | tr -d '\r\n' + else + echo "Error: No supported hashing utility found!" >&2 + exit 1 + fi +} + +# Compute and compare the SHA256 hashes +COMPUTED_HASH=$(calculate_sha256 "$JAR_FILE") +EXPECTED_HASH=$(cat "$HASH_FILE" | tr -d ' \r\n') + +if [ "$COMPUTED_HASH" == "$EXPECTED_HASH" ]; then + echo "SHA256 hash verification succeeded." +else + echo "SHA256 hash verification failed." + echo "Expected: $EXPECTED_HASH" + echo "Got: $COMPUTED_HASH" + rm -f "$JAR_FILE" + exit 1 +fi \ No newline at end of file diff --git a/platform/cmd/scan_options.go b/platform/cmd/scan_options.go index e255e247..fecebedf 100644 --- a/platform/cmd/scan_options.go +++ b/platform/cmd/scan_options.go @@ -78,6 +78,8 @@ type CliOptions struct { AnalysisTimeoutMs int AnalysisTimeoutExitCode int JvmDebugPort int + GlobalConfigurationsFile string + GlobalConfigurationId string } func (o CliOptions) Env() []string { @@ -335,12 +337,34 @@ func ComputeFlags(cmd *cobra.Command, options *CliOptions) error { cmd.MarkFlagsMutuallyExclusive("env", "ide") } + flags.StringVar( + &options.GlobalConfigurationsFile, + "global-configs-file", + "", + "Path to the global configurations .yaml file, must be specified with '--global-configuration-id'", + ) + flags.StringVar( + &options.GlobalConfigurationId, + "global-config-id", + "", + "Id of the global configuration from `--global-configs-file`, must be specified with '--global-configs-file'", + ) + err := flags.MarkHidden("global-configs-file") + cmd.MarkFlagsRequiredTogether("global-configs-file", "global-config-id") + if err != nil { + return err + } + err = flags.MarkHidden("global-config-id") + if err != nil { + return err + } + cmd.MarkFlagsMutuallyExclusive("script", "force-local-changes-script", "full-history") cmd.MarkFlagsMutuallyExclusive("commit", "script", "diff-start") cmd.MarkFlagsMutuallyExclusive("profile-name", "profile-path") cmd.MarkFlagsMutuallyExclusive("apply-fixes", "cleanup") - err := cmd.Flags().MarkDeprecated("fixes-strategy", "use --apply-fixes / --cleanup instead") + err = cmd.Flags().MarkDeprecated("fixes-strategy", "use --apply-fixes / --cleanup instead") if err != nil { return err } diff --git a/platform/commoncontext/common.go b/platform/commoncontext/common.go index 94129a0e..be857f6c 100644 --- a/platform/commoncontext/common.go +++ b/platform/commoncontext/common.go @@ -108,7 +108,7 @@ func filterByLicensePlan(codes []string, token string) []string { } // GetAndSaveDotNetConfig gets .NET config for the given path and saves configName -func GetAndSaveDotNetConfig(projectDir string, yamlName string) bool { +func GetAndSaveDotNetConfig(projectDir string, qodanaYamlFullPath string) bool { possibleOptions := utils.FindFiles(projectDir, []string{".sln", ".csproj", ".vbproj", ".fsproj"}) if len(possibleOptions) <= 1 { return false @@ -125,7 +125,7 @@ func GetAndSaveDotNetConfig(projectDir string, yamlName string) bool { } else { dotnet.Project = filepath.Base(choice) } - return qdyaml.SetQodanaDotNet(projectDir, dotnet, yamlName) + return qdyaml.SetQodanaDotNet(qodanaYamlFullPath, dotnet) } func selectAnalyzer(path string, analyzers []string, interactive bool, selectFunc func([]string) string) string { diff --git a/platform/commoncontext/compute.go b/platform/commoncontext/compute.go index e5970f1a..81b78d6a 100644 --- a/platform/commoncontext/compute.go +++ b/platform/commoncontext/compute.go @@ -37,14 +37,14 @@ func Compute( qodanaCloudToken string, clearCache bool, projectDir string, - qodanaYamlPath string, + localNotEffectiveQodanaYamlPathInProject string, ) Context { linter, ide := computeActualLinterAndIde( linterFromCliOptions, ideFromCliOptions, qodanaCloudToken, projectDir, - qodanaYamlPath, + localNotEffectiveQodanaYamlPathInProject, ) qodanaId := computeId(linter, ide, projectDir) systemDir := computeQodanaSystemDir(cacheDirFromCliOptions) @@ -73,17 +73,21 @@ func computeActualLinterAndIde( ideFromCliOptions string, qodanaCloudToken string, projectDir string, - qodanaYamlPath string, + localNotEffectiveQodanaYamlPathInProject string, ) (string, string) { linter := linterFromCliOptions ide := ideFromCliOptions if linter == "" && ide == "" { - qodanaYaml := qdyaml.LoadQodanaYaml(projectDir, qodanaYamlPath) + qodanaYamlPath := qdyaml.GetLocalNotEffectiveQodanaYamlFullPath( + projectDir, + localNotEffectiveQodanaYamlPathInProject, + ) + qodanaYaml := qdyaml.LoadQodanaYamlByFullPath(qodanaYamlPath) if qodanaYaml.Linter == "" && qodanaYaml.Ide == "" { msg.WarningMessage( "No valid `linter:` or `ide:` field found in %s. Have you run %s? Running that for you...", - msg.PrimaryBold(qodanaYamlPath), + msg.PrimaryBold(localNotEffectiveQodanaYamlPathInProject), msg.PrimaryBold("qodana init"), ) analyzer := GetAnalyzer(projectDir, qodanaCloudToken) @@ -97,7 +101,7 @@ func computeActualLinterAndIde( "You have both `linter:` (%s) and `ide:` (%s) fields set in %s. Modify the configuration file to keep one of them", qodanaYaml.Linter, qodanaYaml.Ide, - qodanaYamlPath, + localNotEffectiveQodanaYamlPathInProject, ) os.Exit(1) } diff --git a/platform/effectiveconfig/config.go b/platform/effectiveconfig/config.go new file mode 100644 index 00000000..520151dc --- /dev/null +++ b/platform/effectiveconfig/config.go @@ -0,0 +1,273 @@ +/* + * Copyright 2021-2024 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package effectiveconfig + +import ( + "errors" + "fmt" + "github.com/JetBrains/qodana-cli/v2025/platform/msg" + "github.com/JetBrains/qodana-cli/v2025/platform/qdyaml" + "github.com/JetBrains/qodana-cli/v2025/platform/utils" + "github.com/JetBrains/qodana-cli/v2025/tooling" + log "github.com/sirupsen/logrus" + "os" + "path/filepath" +) + +// Files – effective configuration files, constructed by calling config-loader-cli.jar, +// all paths are absolute +// + also profile files are stored in config dir +type Files struct { + ConfigDir string + EffectiveQodanaYamlPath string + LocalQodanaYamlPath string + QodanaConfigJsonPath string +} + +func CreateEffectiveConfigFiles( + localQodanaYamlFullPath string, + globalConfigurationsFile string, + globalConfigId string, + jrePath string, + systemDir string, + effectiveConfigDirName string, + logDir string, +) (Files, error) { + if globalConfigId != "" && globalConfigurationsFile == "" { + return Files{}, fmt.Errorf( + "global configuration id %s is defined without global cofigurations file", + globalConfigId, + ) + } + if globalConfigurationsFile != "" && globalConfigId == "" { + return Files{}, fmt.Errorf( + "global configurations file %s is defined without global configuration id", + globalConfigurationsFile, + ) + } + + configLoaderCliJar, err := createConfigLoaderCliJar(systemDir) + if err != nil { + return Files{}, err + } + defer func(name string) { + err := os.Remove(name) + if err != nil { + log.Warnf("Failed to delete config-loader-cli.jar: %v", err) + } + }(configLoaderCliJar) + + effectiveConfigDir := filepath.Join(systemDir, effectiveConfigDirName) + args, err := configurationLoaderCliArgs( + jrePath, + configLoaderCliJar, + localQodanaYamlFullPath, + globalConfigurationsFile, + globalConfigId, + effectiveConfigDir, + ) + if err != nil { + return Files{}, err + } + + log.Debugf("Creating effective configuration in '%s' directory, args: %v", effectiveConfigDir, args) + if _, _, res, err := utils.LaunchAndLog(logDir, "config-loader-cli", args...); res > 0 || err != nil { + if err == nil { + err = errors.New("failed to create effective configuration") + } else { + //goland:noinspection GoErrorStringFormat + err = fmt.Errorf("Failed to create effective configuration: %v", err) + } + msg.ErrorMessage("Failed to create effective configuration. See log above for details") + return Files{}, err + } + + effectiveQodanaYamlData, err := getEffectiveQodanaYamlData(effectiveConfigDir) + if err != nil { + return Files{}, err + } + + err = verifyEffectiveQodanaYamlIdeAndLinterMatchLocal(effectiveQodanaYamlData, localQodanaYamlFullPath) + if err != nil { + return Files{}, err + } + msg.SuccessMessage("Loaded Qodana Configuration") + return effectiveQodanaYamlData, nil +} + +func createConfigLoaderCliJar(systemDir string) (string, error) { + configLoaderCliJarPath := filepath.Join(systemDir, "tools", "config-loader-cli.jar") + if isFileExists(configLoaderCliJarPath) { + err := os.Remove(configLoaderCliJarPath) + if err != nil { + return "", fmt.Errorf("failed to delete existing config-loader-cli.jar: %v", err) + } + } + err := os.MkdirAll(filepath.Dir(configLoaderCliJarPath), 0755) + if err != nil { + return "", fmt.Errorf("failed to create directory for config-loader-cli.jar: %v", err) + } + log.Debugf("creating config-loader-cli.jar at '%s'", configLoaderCliJarPath) + err = os.WriteFile(configLoaderCliJarPath, tooling.ConfigLoaderCli, 0644) + if err != nil { + return "", fmt.Errorf("failed to write config-loader-cli.jar content to %s: %v", configLoaderCliJarPath, err) + } + return configLoaderCliJarPath, nil +} + +func configurationLoaderCliArgs( + jrePath string, + configLoaderCliJarPath string, + localQodanaYamlPath string, + globalConfigurationsFile string, + globalConfigId string, + effectiveConfigDir string, +) ([]string, error) { + if jrePath == "" { + return nil, fmt.Errorf("JRE not found. Required for effective configuration creation") + } + if configLoaderCliJarPath == "" { + return nil, fmt.Errorf("config-loader-cli.jar not found. Required for effective configuration creation") + } + + var err error + args := []string{ + utils.QuoteIfSpace(utils.QuoteForWindows(jrePath)), + "-jar", + utils.QuoteForWindows(configLoaderCliJarPath), + } + + effectiveConfigDirAbs, err := filepath.Abs(effectiveConfigDir) + if err != nil { + err := fmt.Errorf( + "failed to compute absolute path of effective configuration directory %s: %v", + effectiveConfigDir, + err, + ) + return nil, err + } + args = append(args, "--effective-config-out-dir", utils.QuoteForWindows(effectiveConfigDirAbs)) + + if localQodanaYamlPath != "" { + localQodanaYamlPathAbs, err := filepath.Abs(localQodanaYamlPath) + if err != nil { + err := fmt.Errorf( + "failed to compute absolute path of local qodana.yaml file %s: %v", + localQodanaYamlPath, + err, + ) + return nil, err + } + args = append(args, "--local-qodana-yaml", utils.QuoteIfSpace(utils.QuoteForWindows(localQodanaYamlPathAbs))) + } + + if globalConfigurationsFile != "" { + globalConfigurationsFileAbs, err := filepath.Abs(globalConfigurationsFile) + if err != nil { + err := fmt.Errorf( + "failed to compute absolute path of global configurations file %s: %v", + globalConfigurationsFile, + err, + ) + return nil, err + } + args = append( + args, + "--global-configs-file", + utils.QuoteIfSpace(utils.QuoteForWindows(globalConfigurationsFileAbs)), + ) + } + if globalConfigId != "" { + args = append(args, "--global-config-id", utils.QuoteIfSpace(utils.QuoteForWindows(globalConfigId))) + } + return args, nil +} + +func getEffectiveQodanaYamlData(effectiveConfigDir string) (Files, error) { + effectiveQodanaYamlPath := filepath.Join(effectiveConfigDir, "effective.qodana.yaml") + if !isFileExists(effectiveQodanaYamlPath) { + effectiveQodanaYamlPath = "" + } + localQodanaYamlPath := filepath.Join(effectiveConfigDir, "qodana.yaml") + if !isFileExists(localQodanaYamlPath) { + localQodanaYamlPath = "" + } + qodanaConfigJsonPath := filepath.Join(effectiveConfigDir, "qodana-config.json") + if !isFileExists(qodanaConfigJsonPath) { + qodanaConfigJsonPath = "" + } + + if effectiveQodanaYamlPath != "" && qodanaConfigJsonPath == "" { + return Files{}, errors.New("effective.qodana.yaml file doesn't have a qodana-config.json file") + } + if localQodanaYamlPath != "" && effectiveQodanaYamlPath == "" { + return Files{}, errors.New("local qodana.yaml file doesn't have an effective.qodana.yaml file") + } + return Files{ + ConfigDir: effectiveConfigDir, + EffectiveQodanaYamlPath: effectiveQodanaYamlPath, + LocalQodanaYamlPath: localQodanaYamlPath, + QodanaConfigJsonPath: qodanaConfigJsonPath, + }, nil +} + +func isFileExists(path string) bool { + if _, err := os.Stat(path); err == nil { + return true + } else if os.IsNotExist(err) { + return false + } else { + log.Fatalf("Failed to verify existence of file %s: %s", path, err) + } + return false +} + +func verifyEffectiveQodanaYamlIdeAndLinterMatchLocal( + effectiveQodanaYamlData Files, + localQodanaYamlPathFromRoot string, +) error { + effectiveYaml := qdyaml.LoadQodanaYamlByFullPath(effectiveQodanaYamlData.EffectiveQodanaYamlPath) + effectiveLinter := effectiveYaml.Linter + effectiveIde := effectiveYaml.Ide + if effectiveLinter == "" && effectiveIde == "" { + return nil + } + + isLocalQodanaYamlPresent := effectiveQodanaYamlData.LocalQodanaYamlPath != "" + if isLocalQodanaYamlPresent { + localQodanaYaml := qdyaml.LoadQodanaYamlByFullPath(effectiveQodanaYamlData.LocalQodanaYamlPath) + + failedToCreateEffectiveConfigurationMessage := "Failed to create effective configuration" + + topMessageTemplate := "'%s: %s' is specified in one of files provided by 'imports' from " + localQodanaYamlPathFromRoot + " '%s' is required in root qodana.yaml" + bottomMessageTemplate := "Add `ide: %s` to " + localQodanaYamlPathFromRoot + if effectiveIde != localQodanaYaml.Ide { + msg.ErrorMessage(failedToCreateEffectiveConfigurationMessage) + msg.ErrorMessage(topMessageTemplate, "ide", effectiveIde, "ide") + msg.ErrorMessage(bottomMessageTemplate, effectiveIde) + return errors.New("effective.qodana.yaml `ide` doesn't match root qodana.yaml `ide`") + } + //goland:noinspection GoDfaConstantCondition + if effectiveLinter != localQodanaYaml.Linter { + msg.ErrorMessage(failedToCreateEffectiveConfigurationMessage) + msg.ErrorMessage(topMessageTemplate, "linter", effectiveLinter, "linter") + msg.ErrorMessage(bottomMessageTemplate, effectiveLinter) + return errors.New("effective.qodana.yaml `linter` doesn't match root qodana.yaml `linter`") + } + } + return nil +} diff --git a/platform/effectiveconfig/config_test.go b/platform/effectiveconfig/config_test.go new file mode 100644 index 00000000..0868823c --- /dev/null +++ b/platform/effectiveconfig/config_test.go @@ -0,0 +1,287 @@ +/* + * Copyright 2021-2024 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package effectiveconfig + +import ( + "github.com/JetBrains/qodana-cli/v2025/platform/utils" + "github.com/stretchr/testify/assert" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestSuccess(t *testing.T) { + assert.Equal(t, true, utils.IsInstalled("java")) + workingDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get working directory: %v", err) + } + + testCases := []struct { + testCaseName string + localQodanaYaml string + globalConfigurationsFile string + globalConfigurationId string + }{ + { + testCaseName: "local and global input qodana yaml", + localQodanaYaml: "local/qodana.yaml", + globalConfigurationsFile: "global/qodana-global-configurations.yaml", + globalConfigurationId: "main", + }, + { + testCaseName: "only local input qodana yaml", + localQodanaYaml: "local/qodana.yaml", + }, + { + testCaseName: "only global input qodana yaml", + globalConfigurationsFile: "global/qodana-global-configurations.yaml", + globalConfigurationId: "main", + }, + { + testCaseName: "no input qodana yaml", + }, + { + testCaseName: "ide and linter defined in root and child qodana yaml", + localQodanaYaml: "local/qodana.yaml", + }, + } + + for _, tc := range testCases { + t.Run( + tc.testCaseName, func(t *testing.T) { + testDataPath := filepath.Join(workingDir, "testdata", tc.testCaseName) + configurationDir := filepath.Join(testDataPath, "configuration") + if tc.localQodanaYaml != "" { + tc.localQodanaYaml = filepath.Join(configurationDir, tc.localQodanaYaml) + } + if tc.globalConfigurationsFile != "" { + tc.globalConfigurationsFile = filepath.Join(configurationDir, tc.globalConfigurationsFile) + } + + systemDir := t.TempDir() + logDir := t.TempDir() + + configFiles, err := CreateEffectiveConfigFiles( + tc.localQodanaYaml, + tc.globalConfigurationsFile, + tc.globalConfigurationId, + "java", + systemDir, + "qdconfig", + logDir, + ) + if err != nil { + assert.FailNow(t, err.Error()) + } + + verifyDirectoriesContentEqual(t, filepath.Join(testDataPath, "expected"), configFiles.ConfigDir) + + isEmptyConfiguration := tc.globalConfigurationsFile == "" && + tc.globalConfigurationId == "" && + tc.localQodanaYaml == "" + if !isEmptyConfiguration { + effectiveConfig := isFileExists(configFiles.EffectiveQodanaYamlPath) + assert.True(t, effectiveConfig) + } + }, + ) + } +} + +func TestError(t *testing.T) { + assert.Equal(t, true, utils.IsInstalled("java")) + workingDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get working directory: %v", err) + } + + testCases := []struct { + testCaseName string + localQodanaYaml string + globalConfigurationsFile string + globalConfigurationId string + }{ + { + testCaseName: "error no global config id", + globalConfigurationsFile: "global/qodana-global-configurations.yaml", + }, + { + testCaseName: "error no global config file", + globalConfigurationId: "main", + }, + { + testCaseName: "error ide and linter defined in inner qodana yaml only", + localQodanaYaml: "local/qodana.yaml", + }, + { + testCaseName: "error local qodana yaml doesn't exist", + localQodanaYaml: "local/no-qodana.yaml", + }, + { + testCaseName: "error global configurations file doesn't exist", + globalConfigurationsFile: "global/no-qodana-global-configurations.yaml", + globalConfigurationId: "main", + }, + { + testCaseName: "error global configuration id doesn't exist", + globalConfigurationsFile: "global/qodana-global-configurations.yaml", + globalConfigurationId: "no-main", + }, + { + testCaseName: "error global configuration qodana yaml doesn't exist", + globalConfigurationsFile: "global/qodana-global-configurations.yaml", + globalConfigurationId: "main", + }, + { + testCaseName: "error inner qodana yaml doesnt exist", + localQodanaYaml: "local/qodana.yaml", + }, + { + testCaseName: "error profile-path doesnt exist", + localQodanaYaml: "local/qodana.yaml", + }, + { + testCaseName: "error profile-base-path doesnt exist", + localQodanaYaml: "local/qodana.yaml", + }, + { + testCaseName: "error inner profile doesnt exist", + localQodanaYaml: "local/qodana.yaml", + }, + } + + for _, tc := range testCases { + t.Run( + tc.testCaseName, func(t *testing.T) { + testDataPath := filepath.Join(workingDir, "testdata", tc.testCaseName) + configurationDir := filepath.Join(testDataPath, "configuration") + if tc.localQodanaYaml != "" { + tc.localQodanaYaml = filepath.Join(configurationDir, tc.localQodanaYaml) + } + if tc.globalConfigurationsFile != "" { + tc.globalConfigurationsFile = filepath.Join(configurationDir, tc.globalConfigurationsFile) + } + + systemDir := t.TempDir() + logDir := t.TempDir() + + configFiles, err := CreateEffectiveConfigFiles( + tc.localQodanaYaml, + tc.globalConfigurationsFile, + tc.globalConfigurationId, + "java", + systemDir, + "qdconfig", + logDir, + ) + if err == nil { + assert.FailNow(t, "Expected to fail with error") + } + isEmptyConfigFiles := configFiles.ConfigDir == "" && + configFiles.EffectiveQodanaYamlPath == "" && + configFiles.LocalQodanaYamlPath == "" && + configFiles.QodanaConfigJsonPath == "" + + if !isEmptyConfigFiles { + assert.FailNow(t, "Expected to fail with empty config files, files: %s", configFiles.ConfigDir) + } + }, + ) + } +} + +func verifyDirectoriesContentEqual(t *testing.T, expectedDir string, actualDir string) { + if _, err := os.Stat(actualDir); os.IsNotExist(err) { + t.Fatalf("actualDir does not exist: %s", actualDir) + } + // Collect files and directories from a given base directory. + collectEntries := func(baseDir string) (map[string]os.FileInfo, error) { + entries := make(map[string]os.FileInfo) + err := filepath.Walk( + baseDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + rel, err := filepath.Rel(baseDir, path) + if err != nil { + return err + } + entries[rel] = info + return nil + }, + ) + return entries, err + } + + expectedEntries, err := collectEntries(expectedDir) + if err != nil { + t.Error(err) + } + actualEntries, err := collectEntries(actualDir) + if err != nil { + t.Error(err) + } + + // Compare expected entries with actual entries. + for rel, expectedInfo := range expectedEntries { + if expectedInfo.Name() == ".gitkeep" { + continue + } + actualInfo, ok := actualEntries[rel] + if !ok { + t.Errorf("file or directory missing: %s", filepath.Join(actualDir, rel)) + } + + if expectedInfo.IsDir() != actualInfo.IsDir() { + t.Errorf("mismatched type for: %s", rel) + } + + // If it's a file, compare its content. + if !expectedInfo.IsDir() { + expectedContent, err := os.ReadFile(filepath.Join(expectedDir, rel)) + if err != nil { + t.Error(err) + } + actualContent, err := os.ReadFile(filepath.Join(actualDir, rel)) + if err != nil { + t.Error(err) + } + assert.Equal( + t, + systemIndependentLinebreaks(string(expectedContent)), + systemIndependentLinebreaks(string(actualContent)), + "mismatched content for: %s", + rel, + ) + } + } + + // Check for unexpected extra entries in actual directory. + for rel := range actualEntries { + if _, ok := expectedEntries[rel]; !ok { + t.Errorf("unexpected file or directory found: %s", rel) + } + } +} + +func systemIndependentLinebreaks(input string) string { + input = strings.ReplaceAll(input, "\r\n", "\n") + input = strings.ReplaceAll(input, "\r", "\n") + return input +} diff --git a/platform/effectiveconfig/testdata/error global configuration id doesn't exist/configuration/global/files/a-profile.yaml b/platform/effectiveconfig/testdata/error global configuration id doesn't exist/configuration/global/files/a-profile.yaml new file mode 100644 index 00000000..48d24290 --- /dev/null +++ b/platform/effectiveconfig/testdata/error global configuration id doesn't exist/configuration/global/files/a-profile.yaml @@ -0,0 +1,141 @@ +name: qodana.recommended +baseProfile: Project Default + +groups: + - groupId: ReSharperInspections # ReSharper inspections are enabled by default + groups: + - 'category:C++' + - 'category:C#' + - 'category:XAML' + - 'category:VB.NET' + - 'category:Aspx' + - 'category:HTML/Common Practices and Code Improvements' + - 'category:HTML/Potential Code Quality Issues' + - 'category:ResX' + - 'category:Web.Config' + - 'category:Razor' + - 'category:Blazor' + - 'category:Angular 2 HTML' + - 'category:HttpHandler or WebService' + - 'category:F#' + - 'category:ASP.NET route templates' + - 'category:RegExpBase' + - 'category:XML/Spelling Issues' + - 'category:T4' + - 'category:Roslyn' + - '!RiderStaticSanityInspections' + + - groupId: ReSharperGlobalInspections # ReSharper global inspections that could be disabled for faster non-SWEA scan + inspections: + - MemberCanBePrivate.Global + - MemberCanBeProtected.Global + - MemberCanBeInternal + - MemberCanBeFileLocal + - MemberCanBeMadeStatic.Global + - ClassCanBeSealed.Global + - FieldCanBeMadeReadOnly.Global + - StructCanBeMadeReadOnly + - AutoPropertyCanBeMadeGetOnly.Global + - PropertyCanBeMadeInitOnly.Global + - ConvertToConstant.Global + - ParameterTypeCanBeEnumerable.Global + - ReturnTypeCanBeEnumerable.Global + - ClassNeverInstantiated.Global + - NotAccessedField.Global + - EventNeverSubscribedTo.Global + - UnassignedField.Global + - UnusedAutoPropertyAccessor.Global + - NotAccessedPositionalProperty.Global + - EventNeverInvoked.Global + - SuspiciousTypeConversion.Global + - CollectionNeverUpdated.Global + - CollectionNeverQueried.Global + - RedundantUsingDirective.Global + - IntroduceOptionalParameters.Global + - RedundantOverload.Global + - UnusedMember.Global + - UnusedType.Global + - UnusedMemberInSuper.Global + - UnusedMemberHierarchy.Global + - UnusedMethodReturnValue.Global + - UnusedParameter.Global + - ParameterOnlyUsedForPreconditionCheck.Global + - OutParameterValueIsAlwaysDiscarded.Global + - VirtualMemberNeverOverridden.Global + - ClassWithVirtualMembersNeverInherited.Global + - EntityNameCapturedOnly.Global + + - groupId: QodanaDotNetInspections # Qodana inspections for .NET - coverage and vulnerability analysis + inspections: + - NetCoverageInspection + - CheckDependencyLicenses + - RiderSecurityErrorsInspection + + # JavaScript inspections + - groupId: JSRelatedInspections + inspections: + - JsCoverageInspection + + - groupId: JSInspections + groups: + - 'category:JavaScript and TypeScript' + - 'category:Angular' + - 'category:Vue' + - 'category:MongoJS' + - 'category:Pug_Jade' + - JSRelatedInspections + + - groupId: FlakyInspections + inspections: + - InconsistentNaming + - SpellCheckingInspection + + - groupId: ProofreadingInspections + inspections: + - LanguageDetectionInspection + - GrazieInspection + + - groupId: UnsupportedInspections # Annotator causes exceptions on MSBuild related files + inspections: + - Annotator + + - groupId: DotnetExcluded + groups: + - GLOBAL # Qodana doesn't run global inspections by default, due to significant time consumption + - JSRelatedInspections + - JSInspections + - FlakyInspections + - ProofreadingInspections + - UnsupportedInspections + - RiderStaticSanityInspections # Inspections that appear in sanity profile + - 'severity:INFORMATION' # Qodana doesn't run "invisible" and "technical" in IDE inspections + - 'severity:TEXT ATTRIBUTES' # Qodana don't run "invisible" and "technical" in IDE inspections + - groupId: NonReSharperInspections + groups: + - ALL + - '!ReSharperInspections' + - '!QodanaDotNetInspections' + + - groupId: LowSeverity + groups: + - 'severity:WEAK WARNING' + - 'severity:HINT' + - 'severity:TYPO' + +inspections: + - group: DotnetExcluded + enabled: false + - group: QodanaDotNetInspections # Explicitly enable coverage and dependency vulnerability analysis by default + enabled: true + - inspection: HttpUrlsUsage # Spam + enabled: false + - group: ALL + ignore: + - '.qodana/**' + - '**/*.DotSettings' # Ignore .DotSettings files by default + - 'scope#$gitignore' # $gitignore scope available only in qodana execution + - 'scope#$UnrealEngine' # Ignore Unreal Engine files by default + - group: LowSeverity # CLT doesn't provide them by default + enabled: false + - group: NonReSharperInspections + enabled: false \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/error global configuration id doesn't exist/configuration/global/files/qodana.yaml b/platform/effectiveconfig/testdata/error global configuration id doesn't exist/configuration/global/files/qodana.yaml new file mode 100644 index 00000000..cc0bce14 --- /dev/null +++ b/platform/effectiveconfig/testdata/error global configuration id doesn't exist/configuration/global/files/qodana.yaml @@ -0,0 +1,20 @@ +version: 1.0 + +profile: + path: a-profile.yaml + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + # from inner + - name: InspectionC # from inner + - name: InspectionD + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root + moderate: 3 # moderate is from inner \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/error global configuration id doesn't exist/configuration/global/qodana-global-configurations.yaml b/platform/effectiveconfig/testdata/error global configuration id doesn't exist/configuration/global/qodana-global-configurations.yaml new file mode 100644 index 00000000..43a2c54b --- /dev/null +++ b/platform/effectiveconfig/testdata/error global configuration id doesn't exist/configuration/global/qodana-global-configurations.yaml @@ -0,0 +1,5 @@ +configurations: + - id: "main" + name: "global configuration" + description: "global configuration" + qodanaYaml: "files/qodana.yaml" \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/error global configuration qodana yaml doesn't exist/configuration/global/qodana-global-configurations.yaml b/platform/effectiveconfig/testdata/error global configuration qodana yaml doesn't exist/configuration/global/qodana-global-configurations.yaml new file mode 100644 index 00000000..c72a9e94 --- /dev/null +++ b/platform/effectiveconfig/testdata/error global configuration qodana yaml doesn't exist/configuration/global/qodana-global-configurations.yaml @@ -0,0 +1,5 @@ +configurations: + - id: "main" + name: "global configuration" + description: "global configuration" + qodanaYaml: "files/no-qodana.yaml" \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/error ide and linter defined in inner qodana yaml only/configuration/local/inner.yaml b/platform/effectiveconfig/testdata/error ide and linter defined in inner qodana yaml only/configuration/local/inner.yaml new file mode 100644 index 00000000..2ee926ec --- /dev/null +++ b/platform/effectiveconfig/testdata/error ide and linter defined in inner qodana yaml only/configuration/local/inner.yaml @@ -0,0 +1,15 @@ +version: "1.0" + +ide: QDJVM + +linter: jetbrains/qodana-jvm:latest + +failureConditions: + severityThresholds: + critical: 3 + moderate: 3 # moderate is from inner + +include: + # from inner + - name: InspectionC # from inner + - name: InspectionD diff --git a/platform/effectiveconfig/testdata/error ide and linter defined in inner qodana yaml only/configuration/local/qodana.yaml b/platform/effectiveconfig/testdata/error ide and linter defined in inner qodana yaml only/configuration/local/qodana.yaml new file mode 100644 index 00000000..cc600856 --- /dev/null +++ b/platform/effectiveconfig/testdata/error ide and linter defined in inner qodana yaml only/configuration/local/qodana.yaml @@ -0,0 +1,16 @@ +version: 1.0 + +imports: + - inner.yaml + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/error inner profile doesnt exist/configuration/local/a-profile.yaml b/platform/effectiveconfig/testdata/error inner profile doesnt exist/configuration/local/a-profile.yaml new file mode 100644 index 00000000..e2413b37 --- /dev/null +++ b/platform/effectiveconfig/testdata/error inner profile doesnt exist/configuration/local/a-profile.yaml @@ -0,0 +1,128 @@ +name: "qodana.recommended" + +include: + - "b-profile.yaml" + +groups: + - groupId: ReSharperInspections # ReSharper inspections are enabled by default + groups: + - "category:C++" + - "category:C#" + - "category:XAML" + - "category:VB.NET" + - "category:Aspx" + - "category:HTML/Common Practices and Code Improvements" + - "category:HTML/Potential Code Quality Issues" + - "category:ResX" + - "category:Web.Config" + - "category:Razor" + - "category:Blazor" + - "category:Angular 2 HTML" + - "category:HttpHandler or WebService" + - "category:F#" + - "category:ASP.NET route templates" + - "category:RegExpBase" + - "category:XML/Spelling Issues" + - "category:T4" + - "category:Roslyn" + - "!RiderStaticSanityInspections" + + - groupId: ReSharperGlobalInspections # ReSharper global inspections that could be disabled for faster non-SWEA scan + inspections: + - MemberCanBePrivate.Global + - MemberCanBeProtected.Global + - MemberCanBeInternal + - MemberCanBeFileLocal + - MemberCanBeMadeStatic.Global + - ClassCanBeSealed.Global + - FieldCanBeMadeReadOnly.Global + - StructCanBeMadeReadOnly + - AutoPropertyCanBeMadeGetOnly.Global + - PropertyCanBeMadeInitOnly.Global + - ConvertToConstant.Global + - ParameterTypeCanBeEnumerable.Global + - ReturnTypeCanBeEnumerable.Global + - ClassNeverInstantiated.Global + - NotAccessedField.Global + - EventNeverSubscribedTo.Global + - UnassignedField.Global + - UnusedAutoPropertyAccessor.Global + - NotAccessedPositionalProperty.Global + - EventNeverInvoked.Global + - SuspiciousTypeConversion.Global + - CollectionNeverUpdated.Global + - CollectionNeverQueried.Global + - RedundantUsingDirective.Global + - IntroduceOptionalParameters.Global + - RedundantOverload.Global + - UnusedMember.Global + - UnusedType.Global + - UnusedMemberInSuper.Global + - UnusedMemberHierarchy.Global + - UnusedMethodReturnValue.Global + - UnusedParameter.Global + - ParameterOnlyUsedForPreconditionCheck.Global + - OutParameterValueIsAlwaysDiscarded.Global + - VirtualMemberNeverOverridden.Global + - ClassWithVirtualMembersNeverInherited.Global + - EntityNameCapturedOnly.Global + + - groupId: QodanaDotNetInspections # Qodana inspections for .NET - coverage and vulnerability analysis + inspections: + - NetCoverageInspection + - CheckDependencyLicenses + - RiderSecurityErrorsInspection + + # JavaScript inspections + - groupId: JSRelatedInspections + inspections: + - JsCoverageInspection + + - groupId: JSInspections + groups: + - "category:JavaScript and TypeScript" + - "category:Angular" + - "category:Vue" + - "category:MongoJS" + - "category:Pug_Jade" + - "JSRelatedInspections" + + - groupId: FlakyInspections + inspections: + - InconsistentNaming + - SpellCheckingInspection + + - groupId: ProofreadingInspections + inspections: + - LanguageDetectionInspection + - GrazieInspection + + - groupId: UnsupportedInspections # Annotator causes exceptions on MSBuild related files + inspections: + - Annotator + + - groupId: DotnetExcluded + groups: + - "GLOBAL" # Qodana doesn't run global inspections by default, due to significant time consumption + - "JSRelatedInspections" + - "JSInspections" + - "FlakyInspections" + - "ProofreadingInspections" + - "UnsupportedInspections" + - "RiderStaticSanityInspections" # Inspections that appear in sanity profile + - "severity:INFORMATION" # Qodana doesn't run "invisible" and "technical" in IDE inspections + - "severity:TEXT ATTRIBUTES" # Qodana don't run "invisible" and "technical" in IDE inspections + +inspections: + - group: DotnetExcluded + enabled: false + - group: QodanaDotNetInspections # Explicitly enable coverage and dependency vulnerability analysis by default + enabled: true + - inspection: HttpUrlsUsage # Spam + enabled: false + - group: ALL + ignore: + - ".qodana/**" + - "**/*.DotSettings" # Ignore .DotSettings files by default + - "scope#$gitignore" # $gitignore scope available only in qodana execution + - "scope#$UnrealEngine" # Ignore Unreal Engine files by default \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/error inner profile doesnt exist/configuration/local/inner.yaml b/platform/effectiveconfig/testdata/error inner profile doesnt exist/configuration/local/inner.yaml new file mode 100644 index 00000000..e7ad3f87 --- /dev/null +++ b/platform/effectiveconfig/testdata/error inner profile doesnt exist/configuration/local/inner.yaml @@ -0,0 +1,11 @@ +version: "1.0" + +failureConditions: + severityThresholds: + critical: 3 + moderate: 3 # moderate is from inner + +include: + # from inner + - name: InspectionC # from inner + - name: InspectionD diff --git a/platform/effectiveconfig/testdata/error inner profile doesnt exist/configuration/local/qodana.yaml b/platform/effectiveconfig/testdata/error inner profile doesnt exist/configuration/local/qodana.yaml new file mode 100644 index 00000000..8fc1b86b --- /dev/null +++ b/platform/effectiveconfig/testdata/error inner profile doesnt exist/configuration/local/qodana.yaml @@ -0,0 +1,19 @@ +version: 1.0 + +imports: + - inner.yaml + +profile: + path: a-profile.yaml + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/error inner qodana yaml doesnt exist/configuration/local/qodana.yaml b/platform/effectiveconfig/testdata/error inner qodana yaml doesnt exist/configuration/local/qodana.yaml new file mode 100644 index 00000000..cc600856 --- /dev/null +++ b/platform/effectiveconfig/testdata/error inner qodana yaml doesnt exist/configuration/local/qodana.yaml @@ -0,0 +1,16 @@ +version: 1.0 + +imports: + - inner.yaml + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/error profile-base-path doesnt exist/configuration/local/qodana.yaml b/platform/effectiveconfig/testdata/error profile-base-path doesnt exist/configuration/local/qodana.yaml new file mode 100644 index 00000000..8f381ec9 --- /dev/null +++ b/platform/effectiveconfig/testdata/error profile-base-path doesnt exist/configuration/local/qodana.yaml @@ -0,0 +1,17 @@ +version: 1.0 + +profile: + base: + path: "profile.yaml" + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/error profile-path doesnt exist/configuration/local/qodana.yaml b/platform/effectiveconfig/testdata/error profile-path doesnt exist/configuration/local/qodana.yaml new file mode 100644 index 00000000..c805fbd6 --- /dev/null +++ b/platform/effectiveconfig/testdata/error profile-path doesnt exist/configuration/local/qodana.yaml @@ -0,0 +1,16 @@ +version: 1.0 + +profile: + path: "profile.yaml" + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/configuration/local/inner.yaml b/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/configuration/local/inner.yaml new file mode 100644 index 00000000..2ee926ec --- /dev/null +++ b/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/configuration/local/inner.yaml @@ -0,0 +1,15 @@ +version: "1.0" + +ide: QDJVM + +linter: jetbrains/qodana-jvm:latest + +failureConditions: + severityThresholds: + critical: 3 + moderate: 3 # moderate is from inner + +include: + # from inner + - name: InspectionC # from inner + - name: InspectionD diff --git a/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/configuration/local/qodana.yaml b/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/configuration/local/qodana.yaml new file mode 100644 index 00000000..fe3dde10 --- /dev/null +++ b/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/configuration/local/qodana.yaml @@ -0,0 +1,20 @@ +version: 1.0 + +imports: + - inner.yaml + +ide: QDJVM + +linter: jetbrains/qodana-jvm:latest + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/expected/effective.qodana.yaml b/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/expected/effective.qodana.yaml new file mode 100644 index 00000000..0515b66b --- /dev/null +++ b/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/expected/effective.qodana.yaml @@ -0,0 +1,21 @@ +version: 1.0 + +ide: QDJVM + +linter: 'jetbrains/qodana-jvm:latest' + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + # from inner + - name: InspectionC # from inner + - name: InspectionD + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root + moderate: 3 # moderate is from inner \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/expected/qodana-config.json b/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/expected/qodana-config.json new file mode 100644 index 00000000..13c908ee --- /dev/null +++ b/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/expected/qodana-config.json @@ -0,0 +1,6 @@ +{ + "local": { + "filename": "qodana.yaml", + "content": "version: 1.0\n\nimports:\n - inner.yaml\n\nide: QDJVM\n\nlinter: jetbrains/qodana-jvm:latest\n\ninclude:\n # from root\n - name: InspectionA\n - name: InspectionB #from root\n\n#this is a failure conditions section\nfailureConditions:\n # this is a severityThresholds section\n severityThresholds:\n any: 1 #from root\n critical: 2 # from root" + } +} \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/expected/qodana.yaml b/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/expected/qodana.yaml new file mode 100644 index 00000000..fe3dde10 --- /dev/null +++ b/platform/effectiveconfig/testdata/ide and linter defined in root and child qodana yaml/expected/qodana.yaml @@ -0,0 +1,20 @@ +version: 1.0 + +imports: + - inner.yaml + +ide: QDJVM + +linter: jetbrains/qodana-jvm:latest + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/global/files/a-profile.yaml b/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/global/files/a-profile.yaml new file mode 100644 index 00000000..65962d5c --- /dev/null +++ b/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/global/files/a-profile.yaml @@ -0,0 +1,125 @@ +name: "qodana.recommended" + +groups: + - groupId: ReSharperInspections # ReSharper inspections are enabled by default + groups: + - "category:C++" + - "category:C#" + - "category:XAML" + - "category:VB.NET" + - "category:Aspx" + - "category:HTML/Common Practices and Code Improvements" + - "category:HTML/Potential Code Quality Issues" + - "category:ResX" + - "category:Web.Config" + - "category:Razor" + - "category:Blazor" + - "category:Angular 2 HTML" + - "category:HttpHandler or WebService" + - "category:F#" + - "category:ASP.NET route templates" + - "category:RegExpBase" + - "category:XML/Spelling Issues" + - "category:T4" + - "category:Roslyn" + - "!RiderStaticSanityInspections" + + - groupId: ReSharperGlobalInspections # ReSharper global inspections that could be disabled for faster non-SWEA scan + inspections: + - MemberCanBePrivate.Global + - MemberCanBeProtected.Global + - MemberCanBeInternal + - MemberCanBeFileLocal + - MemberCanBeMadeStatic.Global + - ClassCanBeSealed.Global + - FieldCanBeMadeReadOnly.Global + - StructCanBeMadeReadOnly + - AutoPropertyCanBeMadeGetOnly.Global + - PropertyCanBeMadeInitOnly.Global + - ConvertToConstant.Global + - ParameterTypeCanBeEnumerable.Global + - ReturnTypeCanBeEnumerable.Global + - ClassNeverInstantiated.Global + - NotAccessedField.Global + - EventNeverSubscribedTo.Global + - UnassignedField.Global + - UnusedAutoPropertyAccessor.Global + - NotAccessedPositionalProperty.Global + - EventNeverInvoked.Global + - SuspiciousTypeConversion.Global + - CollectionNeverUpdated.Global + - CollectionNeverQueried.Global + - RedundantUsingDirective.Global + - IntroduceOptionalParameters.Global + - RedundantOverload.Global + - UnusedMember.Global + - UnusedType.Global + - UnusedMemberInSuper.Global + - UnusedMemberHierarchy.Global + - UnusedMethodReturnValue.Global + - UnusedParameter.Global + - ParameterOnlyUsedForPreconditionCheck.Global + - OutParameterValueIsAlwaysDiscarded.Global + - VirtualMemberNeverOverridden.Global + - ClassWithVirtualMembersNeverInherited.Global + - EntityNameCapturedOnly.Global + + - groupId: QodanaDotNetInspections # Qodana inspections for .NET - coverage and vulnerability analysis + inspections: + - NetCoverageInspection + - CheckDependencyLicenses + - RiderSecurityErrorsInspection + + # JavaScript inspections + - groupId: JSRelatedInspections + inspections: + - JsCoverageInspection + + - groupId: JSInspections + groups: + - "category:JavaScript and TypeScript" + - "category:Angular" + - "category:Vue" + - "category:MongoJS" + - "category:Pug_Jade" + - "JSRelatedInspections" + + - groupId: FlakyInspections + inspections: + - InconsistentNaming + - SpellCheckingInspection + + - groupId: ProofreadingInspections + inspections: + - LanguageDetectionInspection + - GrazieInspection + + - groupId: UnsupportedInspections # Annotator causes exceptions on MSBuild related files + inspections: + - Annotator + + - groupId: DotnetExcluded + groups: + - "GLOBAL" # Qodana doesn't run global inspections by default, due to significant time consumption + - "JSRelatedInspections" + - "JSInspections" + - "FlakyInspections" + - "ProofreadingInspections" + - "UnsupportedInspections" + - "RiderStaticSanityInspections" # Inspections that appear in sanity profile + - "severity:INFORMATION" # Qodana doesn't run "invisible" and "technical" in IDE inspections + - "severity:TEXT ATTRIBUTES" # Qodana don't run "invisible" and "technical" in IDE inspections + +inspections: + - group: DotnetExcluded + enabled: false + - group: QodanaDotNetInspections # Explicitly enable coverage and dependency vulnerability analysis by default + enabled: true + - inspection: HttpUrlsUsage # Spam + enabled: false + - group: ALL + ignore: + - ".qodana/**" + - "**/*.DotSettings" # Ignore .DotSettings files by default + - "scope#$gitignore" # $gitignore scope available only in qodana execution + - "scope#$UnrealEngine" # Ignore Unreal Engine files by default \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/global/files/qodana.yaml b/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/global/files/qodana.yaml new file mode 100644 index 00000000..3d2ee1f7 --- /dev/null +++ b/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/global/files/qodana.yaml @@ -0,0 +1,14 @@ +version: 1.0 + +profile: + path: a-profile.yaml + +failureConditions: + severityThresholds: + critical: 3 + moderate: 3 # moderate is global + +include: + # from global + - name: InspectionC # from global + - name: InspectionD \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/global/qodana-global-configurations.yaml b/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/global/qodana-global-configurations.yaml new file mode 100644 index 00000000..43a2c54b --- /dev/null +++ b/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/global/qodana-global-configurations.yaml @@ -0,0 +1,5 @@ +configurations: + - id: "main" + name: "global configuration" + description: "global configuration" + qodanaYaml: "files/qodana.yaml" \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/local/b-profile.yaml b/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/local/b-profile.yaml new file mode 100644 index 00000000..afcbb5d9 --- /dev/null +++ b/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/local/b-profile.yaml @@ -0,0 +1,21 @@ +baseProfile: "Project Default" +name: "qodana.starter" + +groups: + - groupId: NonReSharperInspections + groups: + - "ALL" + - "!ReSharperInspections" + - "!QodanaDotNetInspections" + + - groupId: LowSeverity + groups: + - "severity:WEAK WARNING" + - "severity:HINT" + - "severity:TYPO" + +inspections: + - group: LowSeverity # CLT doesn't provide them by default + enabled: false + - group: NonReSharperInspections + enabled: false \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/local/qodana.yaml b/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/local/qodana.yaml new file mode 100644 index 00000000..b0f62ec1 --- /dev/null +++ b/platform/effectiveconfig/testdata/local and global input qodana yaml/configuration/local/qodana.yaml @@ -0,0 +1,16 @@ +version: 1.0 + +profile: + path: b-profile.yaml + +include: + # from local + - name: InspectionA + - name: InspectionB #from local + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from local + critical: 2 # from local \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/b-profile.yaml b/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/b-profile.yaml new file mode 100644 index 00000000..7f3b0fbf --- /dev/null +++ b/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/b-profile.yaml @@ -0,0 +1,21 @@ +baseProfile: Project Default +name: qodana.starter + +groups: + - groupId: NonReSharperInspections + groups: + - ALL + - '!ReSharperInspections' + - '!QodanaDotNetInspections' + + - groupId: LowSeverity + groups: + - 'severity:WEAK WARNING' + - 'severity:HINT' + - 'severity:TYPO' + +inspections: + - group: LowSeverity # CLT doesn't provide them by default + enabled: false + - group: NonReSharperInspections + enabled: false \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/effective.qodana.yaml b/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/effective.qodana.yaml new file mode 100644 index 00000000..a2810f45 --- /dev/null +++ b/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/effective.qodana.yaml @@ -0,0 +1,20 @@ +version: 1.0 + +profile: + path: b-profile.yaml + +include: + # from local + - name: InspectionA + - name: InspectionB #from local + # from global + - name: InspectionC # from global + - name: InspectionD + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from local + critical: 2 # from local + moderate: 3 # moderate is global \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/qodana-config.json b/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/qodana-config.json new file mode 100644 index 00000000..d8f18431 --- /dev/null +++ b/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/qodana-config.json @@ -0,0 +1,42 @@ +{ + "local": { + "filename": "qodana.yaml", + "content": "version: 1.0\n\nprofile:\n path: b-profile.yaml\n\ninclude:\n # from local\n - name: InspectionA\n - name: InspectionB #from local\n\n#this is a failure conditions section\nfailureConditions:\n # this is a severityThresholds section\n severityThresholds:\n any: 1 #from local\n critical: 2 # from local" + }, + "global": { + "filename": "qodana.yaml", + "properties": { + "id": "main", + "name": "global configuration" + }, + "content": "version: 1.0\n\nprofile:\n path: a-profile.yaml\n\nfailureConditions:\n severityThresholds:\n critical: 3\n moderate: 3 # moderate is global\n\ninclude:\n # from global\n - name: InspectionC # from global\n - name: InspectionD" + }, + "localGlobal": { + "filename": "effective.qodana.yaml", + "properties": { + "linesStatuses": [ + "L_OVER_G", + "LG", + "LG", + "L_OVER_G", + "LG", + "LG", + "L", + "L", + "L", + "G", + "G", + "G", + "LG", + "LG", + "LG", + "LG", + "LG", + "L", + "L_OVER_G", + "G" + ] + }, + "content": "version: 1.0\n\nprofile:\n path: b-profile.yaml\n\ninclude:\n # from local\n - name: InspectionA\n - name: InspectionB #from local\n # from global\n - name: InspectionC # from global\n - name: InspectionD\n\n#this is a failure conditions section\nfailureConditions:\n # this is a severityThresholds section\n severityThresholds:\n any: 1 #from local\n critical: 2 # from local\n moderate: 3 # moderate is global" + } +} \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/qodana.yaml b/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/qodana.yaml new file mode 100644 index 00000000..b0f62ec1 --- /dev/null +++ b/platform/effectiveconfig/testdata/local and global input qodana yaml/expected/qodana.yaml @@ -0,0 +1,16 @@ +version: 1.0 + +profile: + path: b-profile.yaml + +include: + # from local + - name: InspectionA + - name: InspectionB #from local + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from local + critical: 2 # from local \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/no input qodana yaml/expected/.gitkeep b/platform/effectiveconfig/testdata/no input qodana yaml/expected/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/platform/effectiveconfig/testdata/only global input qodana yaml/configuration/global/files/a-profile.yaml b/platform/effectiveconfig/testdata/only global input qodana yaml/configuration/global/files/a-profile.yaml new file mode 100644 index 00000000..48d24290 --- /dev/null +++ b/platform/effectiveconfig/testdata/only global input qodana yaml/configuration/global/files/a-profile.yaml @@ -0,0 +1,141 @@ +name: qodana.recommended +baseProfile: Project Default + +groups: + - groupId: ReSharperInspections # ReSharper inspections are enabled by default + groups: + - 'category:C++' + - 'category:C#' + - 'category:XAML' + - 'category:VB.NET' + - 'category:Aspx' + - 'category:HTML/Common Practices and Code Improvements' + - 'category:HTML/Potential Code Quality Issues' + - 'category:ResX' + - 'category:Web.Config' + - 'category:Razor' + - 'category:Blazor' + - 'category:Angular 2 HTML' + - 'category:HttpHandler or WebService' + - 'category:F#' + - 'category:ASP.NET route templates' + - 'category:RegExpBase' + - 'category:XML/Spelling Issues' + - 'category:T4' + - 'category:Roslyn' + - '!RiderStaticSanityInspections' + + - groupId: ReSharperGlobalInspections # ReSharper global inspections that could be disabled for faster non-SWEA scan + inspections: + - MemberCanBePrivate.Global + - MemberCanBeProtected.Global + - MemberCanBeInternal + - MemberCanBeFileLocal + - MemberCanBeMadeStatic.Global + - ClassCanBeSealed.Global + - FieldCanBeMadeReadOnly.Global + - StructCanBeMadeReadOnly + - AutoPropertyCanBeMadeGetOnly.Global + - PropertyCanBeMadeInitOnly.Global + - ConvertToConstant.Global + - ParameterTypeCanBeEnumerable.Global + - ReturnTypeCanBeEnumerable.Global + - ClassNeverInstantiated.Global + - NotAccessedField.Global + - EventNeverSubscribedTo.Global + - UnassignedField.Global + - UnusedAutoPropertyAccessor.Global + - NotAccessedPositionalProperty.Global + - EventNeverInvoked.Global + - SuspiciousTypeConversion.Global + - CollectionNeverUpdated.Global + - CollectionNeverQueried.Global + - RedundantUsingDirective.Global + - IntroduceOptionalParameters.Global + - RedundantOverload.Global + - UnusedMember.Global + - UnusedType.Global + - UnusedMemberInSuper.Global + - UnusedMemberHierarchy.Global + - UnusedMethodReturnValue.Global + - UnusedParameter.Global + - ParameterOnlyUsedForPreconditionCheck.Global + - OutParameterValueIsAlwaysDiscarded.Global + - VirtualMemberNeverOverridden.Global + - ClassWithVirtualMembersNeverInherited.Global + - EntityNameCapturedOnly.Global + + - groupId: QodanaDotNetInspections # Qodana inspections for .NET - coverage and vulnerability analysis + inspections: + - NetCoverageInspection + - CheckDependencyLicenses + - RiderSecurityErrorsInspection + + # JavaScript inspections + - groupId: JSRelatedInspections + inspections: + - JsCoverageInspection + + - groupId: JSInspections + groups: + - 'category:JavaScript and TypeScript' + - 'category:Angular' + - 'category:Vue' + - 'category:MongoJS' + - 'category:Pug_Jade' + - JSRelatedInspections + + - groupId: FlakyInspections + inspections: + - InconsistentNaming + - SpellCheckingInspection + + - groupId: ProofreadingInspections + inspections: + - LanguageDetectionInspection + - GrazieInspection + + - groupId: UnsupportedInspections # Annotator causes exceptions on MSBuild related files + inspections: + - Annotator + + - groupId: DotnetExcluded + groups: + - GLOBAL # Qodana doesn't run global inspections by default, due to significant time consumption + - JSRelatedInspections + - JSInspections + - FlakyInspections + - ProofreadingInspections + - UnsupportedInspections + - RiderStaticSanityInspections # Inspections that appear in sanity profile + - 'severity:INFORMATION' # Qodana doesn't run "invisible" and "technical" in IDE inspections + - 'severity:TEXT ATTRIBUTES' # Qodana don't run "invisible" and "technical" in IDE inspections + - groupId: NonReSharperInspections + groups: + - ALL + - '!ReSharperInspections' + - '!QodanaDotNetInspections' + + - groupId: LowSeverity + groups: + - 'severity:WEAK WARNING' + - 'severity:HINT' + - 'severity:TYPO' + +inspections: + - group: DotnetExcluded + enabled: false + - group: QodanaDotNetInspections # Explicitly enable coverage and dependency vulnerability analysis by default + enabled: true + - inspection: HttpUrlsUsage # Spam + enabled: false + - group: ALL + ignore: + - '.qodana/**' + - '**/*.DotSettings' # Ignore .DotSettings files by default + - 'scope#$gitignore' # $gitignore scope available only in qodana execution + - 'scope#$UnrealEngine' # Ignore Unreal Engine files by default + - group: LowSeverity # CLT doesn't provide them by default + enabled: false + - group: NonReSharperInspections + enabled: false \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only global input qodana yaml/configuration/global/files/qodana.yaml b/platform/effectiveconfig/testdata/only global input qodana yaml/configuration/global/files/qodana.yaml new file mode 100644 index 00000000..cc0bce14 --- /dev/null +++ b/platform/effectiveconfig/testdata/only global input qodana yaml/configuration/global/files/qodana.yaml @@ -0,0 +1,20 @@ +version: 1.0 + +profile: + path: a-profile.yaml + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + # from inner + - name: InspectionC # from inner + - name: InspectionD + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root + moderate: 3 # moderate is from inner \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only global input qodana yaml/configuration/global/qodana-global-configurations.yaml b/platform/effectiveconfig/testdata/only global input qodana yaml/configuration/global/qodana-global-configurations.yaml new file mode 100644 index 00000000..43a2c54b --- /dev/null +++ b/platform/effectiveconfig/testdata/only global input qodana yaml/configuration/global/qodana-global-configurations.yaml @@ -0,0 +1,5 @@ +configurations: + - id: "main" + name: "global configuration" + description: "global configuration" + qodanaYaml: "files/qodana.yaml" \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only global input qodana yaml/expected/a-profile.yaml b/platform/effectiveconfig/testdata/only global input qodana yaml/expected/a-profile.yaml new file mode 100644 index 00000000..48d24290 --- /dev/null +++ b/platform/effectiveconfig/testdata/only global input qodana yaml/expected/a-profile.yaml @@ -0,0 +1,141 @@ +name: qodana.recommended +baseProfile: Project Default + +groups: + - groupId: ReSharperInspections # ReSharper inspections are enabled by default + groups: + - 'category:C++' + - 'category:C#' + - 'category:XAML' + - 'category:VB.NET' + - 'category:Aspx' + - 'category:HTML/Common Practices and Code Improvements' + - 'category:HTML/Potential Code Quality Issues' + - 'category:ResX' + - 'category:Web.Config' + - 'category:Razor' + - 'category:Blazor' + - 'category:Angular 2 HTML' + - 'category:HttpHandler or WebService' + - 'category:F#' + - 'category:ASP.NET route templates' + - 'category:RegExpBase' + - 'category:XML/Spelling Issues' + - 'category:T4' + - 'category:Roslyn' + - '!RiderStaticSanityInspections' + + - groupId: ReSharperGlobalInspections # ReSharper global inspections that could be disabled for faster non-SWEA scan + inspections: + - MemberCanBePrivate.Global + - MemberCanBeProtected.Global + - MemberCanBeInternal + - MemberCanBeFileLocal + - MemberCanBeMadeStatic.Global + - ClassCanBeSealed.Global + - FieldCanBeMadeReadOnly.Global + - StructCanBeMadeReadOnly + - AutoPropertyCanBeMadeGetOnly.Global + - PropertyCanBeMadeInitOnly.Global + - ConvertToConstant.Global + - ParameterTypeCanBeEnumerable.Global + - ReturnTypeCanBeEnumerable.Global + - ClassNeverInstantiated.Global + - NotAccessedField.Global + - EventNeverSubscribedTo.Global + - UnassignedField.Global + - UnusedAutoPropertyAccessor.Global + - NotAccessedPositionalProperty.Global + - EventNeverInvoked.Global + - SuspiciousTypeConversion.Global + - CollectionNeverUpdated.Global + - CollectionNeverQueried.Global + - RedundantUsingDirective.Global + - IntroduceOptionalParameters.Global + - RedundantOverload.Global + - UnusedMember.Global + - UnusedType.Global + - UnusedMemberInSuper.Global + - UnusedMemberHierarchy.Global + - UnusedMethodReturnValue.Global + - UnusedParameter.Global + - ParameterOnlyUsedForPreconditionCheck.Global + - OutParameterValueIsAlwaysDiscarded.Global + - VirtualMemberNeverOverridden.Global + - ClassWithVirtualMembersNeverInherited.Global + - EntityNameCapturedOnly.Global + + - groupId: QodanaDotNetInspections # Qodana inspections for .NET - coverage and vulnerability analysis + inspections: + - NetCoverageInspection + - CheckDependencyLicenses + - RiderSecurityErrorsInspection + + # JavaScript inspections + - groupId: JSRelatedInspections + inspections: + - JsCoverageInspection + + - groupId: JSInspections + groups: + - 'category:JavaScript and TypeScript' + - 'category:Angular' + - 'category:Vue' + - 'category:MongoJS' + - 'category:Pug_Jade' + - JSRelatedInspections + + - groupId: FlakyInspections + inspections: + - InconsistentNaming + - SpellCheckingInspection + + - groupId: ProofreadingInspections + inspections: + - LanguageDetectionInspection + - GrazieInspection + + - groupId: UnsupportedInspections # Annotator causes exceptions on MSBuild related files + inspections: + - Annotator + + - groupId: DotnetExcluded + groups: + - GLOBAL # Qodana doesn't run global inspections by default, due to significant time consumption + - JSRelatedInspections + - JSInspections + - FlakyInspections + - ProofreadingInspections + - UnsupportedInspections + - RiderStaticSanityInspections # Inspections that appear in sanity profile + - 'severity:INFORMATION' # Qodana doesn't run "invisible" and "technical" in IDE inspections + - 'severity:TEXT ATTRIBUTES' # Qodana don't run "invisible" and "technical" in IDE inspections + - groupId: NonReSharperInspections + groups: + - ALL + - '!ReSharperInspections' + - '!QodanaDotNetInspections' + + - groupId: LowSeverity + groups: + - 'severity:WEAK WARNING' + - 'severity:HINT' + - 'severity:TYPO' + +inspections: + - group: DotnetExcluded + enabled: false + - group: QodanaDotNetInspections # Explicitly enable coverage and dependency vulnerability analysis by default + enabled: true + - inspection: HttpUrlsUsage # Spam + enabled: false + - group: ALL + ignore: + - '.qodana/**' + - '**/*.DotSettings' # Ignore .DotSettings files by default + - 'scope#$gitignore' # $gitignore scope available only in qodana execution + - 'scope#$UnrealEngine' # Ignore Unreal Engine files by default + - group: LowSeverity # CLT doesn't provide them by default + enabled: false + - group: NonReSharperInspections + enabled: false \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only global input qodana yaml/expected/effective.qodana.yaml b/platform/effectiveconfig/testdata/only global input qodana yaml/expected/effective.qodana.yaml new file mode 100644 index 00000000..cc0bce14 --- /dev/null +++ b/platform/effectiveconfig/testdata/only global input qodana yaml/expected/effective.qodana.yaml @@ -0,0 +1,20 @@ +version: 1.0 + +profile: + path: a-profile.yaml + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + # from inner + - name: InspectionC # from inner + - name: InspectionD + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root + moderate: 3 # moderate is from inner \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only global input qodana yaml/expected/qodana-config.json b/platform/effectiveconfig/testdata/only global input qodana yaml/expected/qodana-config.json new file mode 100644 index 00000000..269d6744 --- /dev/null +++ b/platform/effectiveconfig/testdata/only global input qodana yaml/expected/qodana-config.json @@ -0,0 +1,10 @@ +{ + "global": { + "filename": "qodana.yaml", + "properties": { + "id": "main", + "name": "global configuration" + }, + "content": "version: 1.0\n\nprofile:\n path: a-profile.yaml\n\ninclude:\n # from root\n - name: InspectionA\n - name: InspectionB #from root\n # from inner\n - name: InspectionC # from inner\n - name: InspectionD\n\n#this is a failure conditions section\nfailureConditions:\n # this is a severityThresholds section\n severityThresholds:\n any: 1 #from root\n critical: 2 # from root\n moderate: 3 # moderate is from inner" + } +} \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/a-profile.yaml b/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/a-profile.yaml new file mode 100644 index 00000000..e2413b37 --- /dev/null +++ b/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/a-profile.yaml @@ -0,0 +1,128 @@ +name: "qodana.recommended" + +include: + - "b-profile.yaml" + +groups: + - groupId: ReSharperInspections # ReSharper inspections are enabled by default + groups: + - "category:C++" + - "category:C#" + - "category:XAML" + - "category:VB.NET" + - "category:Aspx" + - "category:HTML/Common Practices and Code Improvements" + - "category:HTML/Potential Code Quality Issues" + - "category:ResX" + - "category:Web.Config" + - "category:Razor" + - "category:Blazor" + - "category:Angular 2 HTML" + - "category:HttpHandler or WebService" + - "category:F#" + - "category:ASP.NET route templates" + - "category:RegExpBase" + - "category:XML/Spelling Issues" + - "category:T4" + - "category:Roslyn" + - "!RiderStaticSanityInspections" + + - groupId: ReSharperGlobalInspections # ReSharper global inspections that could be disabled for faster non-SWEA scan + inspections: + - MemberCanBePrivate.Global + - MemberCanBeProtected.Global + - MemberCanBeInternal + - MemberCanBeFileLocal + - MemberCanBeMadeStatic.Global + - ClassCanBeSealed.Global + - FieldCanBeMadeReadOnly.Global + - StructCanBeMadeReadOnly + - AutoPropertyCanBeMadeGetOnly.Global + - PropertyCanBeMadeInitOnly.Global + - ConvertToConstant.Global + - ParameterTypeCanBeEnumerable.Global + - ReturnTypeCanBeEnumerable.Global + - ClassNeverInstantiated.Global + - NotAccessedField.Global + - EventNeverSubscribedTo.Global + - UnassignedField.Global + - UnusedAutoPropertyAccessor.Global + - NotAccessedPositionalProperty.Global + - EventNeverInvoked.Global + - SuspiciousTypeConversion.Global + - CollectionNeverUpdated.Global + - CollectionNeverQueried.Global + - RedundantUsingDirective.Global + - IntroduceOptionalParameters.Global + - RedundantOverload.Global + - UnusedMember.Global + - UnusedType.Global + - UnusedMemberInSuper.Global + - UnusedMemberHierarchy.Global + - UnusedMethodReturnValue.Global + - UnusedParameter.Global + - ParameterOnlyUsedForPreconditionCheck.Global + - OutParameterValueIsAlwaysDiscarded.Global + - VirtualMemberNeverOverridden.Global + - ClassWithVirtualMembersNeverInherited.Global + - EntityNameCapturedOnly.Global + + - groupId: QodanaDotNetInspections # Qodana inspections for .NET - coverage and vulnerability analysis + inspections: + - NetCoverageInspection + - CheckDependencyLicenses + - RiderSecurityErrorsInspection + + # JavaScript inspections + - groupId: JSRelatedInspections + inspections: + - JsCoverageInspection + + - groupId: JSInspections + groups: + - "category:JavaScript and TypeScript" + - "category:Angular" + - "category:Vue" + - "category:MongoJS" + - "category:Pug_Jade" + - "JSRelatedInspections" + + - groupId: FlakyInspections + inspections: + - InconsistentNaming + - SpellCheckingInspection + + - groupId: ProofreadingInspections + inspections: + - LanguageDetectionInspection + - GrazieInspection + + - groupId: UnsupportedInspections # Annotator causes exceptions on MSBuild related files + inspections: + - Annotator + + - groupId: DotnetExcluded + groups: + - "GLOBAL" # Qodana doesn't run global inspections by default, due to significant time consumption + - "JSRelatedInspections" + - "JSInspections" + - "FlakyInspections" + - "ProofreadingInspections" + - "UnsupportedInspections" + - "RiderStaticSanityInspections" # Inspections that appear in sanity profile + - "severity:INFORMATION" # Qodana doesn't run "invisible" and "technical" in IDE inspections + - "severity:TEXT ATTRIBUTES" # Qodana don't run "invisible" and "technical" in IDE inspections + +inspections: + - group: DotnetExcluded + enabled: false + - group: QodanaDotNetInspections # Explicitly enable coverage and dependency vulnerability analysis by default + enabled: true + - inspection: HttpUrlsUsage # Spam + enabled: false + - group: ALL + ignore: + - ".qodana/**" + - "**/*.DotSettings" # Ignore .DotSettings files by default + - "scope#$gitignore" # $gitignore scope available only in qodana execution + - "scope#$UnrealEngine" # Ignore Unreal Engine files by default \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/b-profile.yaml b/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/b-profile.yaml new file mode 100644 index 00000000..afcbb5d9 --- /dev/null +++ b/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/b-profile.yaml @@ -0,0 +1,21 @@ +baseProfile: "Project Default" +name: "qodana.starter" + +groups: + - groupId: NonReSharperInspections + groups: + - "ALL" + - "!ReSharperInspections" + - "!QodanaDotNetInspections" + + - groupId: LowSeverity + groups: + - "severity:WEAK WARNING" + - "severity:HINT" + - "severity:TYPO" + +inspections: + - group: LowSeverity # CLT doesn't provide them by default + enabled: false + - group: NonReSharperInspections + enabled: false \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/inner.yaml b/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/inner.yaml new file mode 100644 index 00000000..e7ad3f87 --- /dev/null +++ b/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/inner.yaml @@ -0,0 +1,11 @@ +version: "1.0" + +failureConditions: + severityThresholds: + critical: 3 + moderate: 3 # moderate is from inner + +include: + # from inner + - name: InspectionC # from inner + - name: InspectionD diff --git a/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/qodana.yaml b/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/qodana.yaml new file mode 100644 index 00000000..8fc1b86b --- /dev/null +++ b/platform/effectiveconfig/testdata/only local input qodana yaml/configuration/local/qodana.yaml @@ -0,0 +1,19 @@ +version: 1.0 + +imports: + - inner.yaml + +profile: + path: a-profile.yaml + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only local input qodana yaml/expected/a-profile.yaml b/platform/effectiveconfig/testdata/only local input qodana yaml/expected/a-profile.yaml new file mode 100644 index 00000000..48d24290 --- /dev/null +++ b/platform/effectiveconfig/testdata/only local input qodana yaml/expected/a-profile.yaml @@ -0,0 +1,141 @@ +name: qodana.recommended +baseProfile: Project Default + +groups: + - groupId: ReSharperInspections # ReSharper inspections are enabled by default + groups: + - 'category:C++' + - 'category:C#' + - 'category:XAML' + - 'category:VB.NET' + - 'category:Aspx' + - 'category:HTML/Common Practices and Code Improvements' + - 'category:HTML/Potential Code Quality Issues' + - 'category:ResX' + - 'category:Web.Config' + - 'category:Razor' + - 'category:Blazor' + - 'category:Angular 2 HTML' + - 'category:HttpHandler or WebService' + - 'category:F#' + - 'category:ASP.NET route templates' + - 'category:RegExpBase' + - 'category:XML/Spelling Issues' + - 'category:T4' + - 'category:Roslyn' + - '!RiderStaticSanityInspections' + + - groupId: ReSharperGlobalInspections # ReSharper global inspections that could be disabled for faster non-SWEA scan + inspections: + - MemberCanBePrivate.Global + - MemberCanBeProtected.Global + - MemberCanBeInternal + - MemberCanBeFileLocal + - MemberCanBeMadeStatic.Global + - ClassCanBeSealed.Global + - FieldCanBeMadeReadOnly.Global + - StructCanBeMadeReadOnly + - AutoPropertyCanBeMadeGetOnly.Global + - PropertyCanBeMadeInitOnly.Global + - ConvertToConstant.Global + - ParameterTypeCanBeEnumerable.Global + - ReturnTypeCanBeEnumerable.Global + - ClassNeverInstantiated.Global + - NotAccessedField.Global + - EventNeverSubscribedTo.Global + - UnassignedField.Global + - UnusedAutoPropertyAccessor.Global + - NotAccessedPositionalProperty.Global + - EventNeverInvoked.Global + - SuspiciousTypeConversion.Global + - CollectionNeverUpdated.Global + - CollectionNeverQueried.Global + - RedundantUsingDirective.Global + - IntroduceOptionalParameters.Global + - RedundantOverload.Global + - UnusedMember.Global + - UnusedType.Global + - UnusedMemberInSuper.Global + - UnusedMemberHierarchy.Global + - UnusedMethodReturnValue.Global + - UnusedParameter.Global + - ParameterOnlyUsedForPreconditionCheck.Global + - OutParameterValueIsAlwaysDiscarded.Global + - VirtualMemberNeverOverridden.Global + - ClassWithVirtualMembersNeverInherited.Global + - EntityNameCapturedOnly.Global + + - groupId: QodanaDotNetInspections # Qodana inspections for .NET - coverage and vulnerability analysis + inspections: + - NetCoverageInspection + - CheckDependencyLicenses + - RiderSecurityErrorsInspection + + # JavaScript inspections + - groupId: JSRelatedInspections + inspections: + - JsCoverageInspection + + - groupId: JSInspections + groups: + - 'category:JavaScript and TypeScript' + - 'category:Angular' + - 'category:Vue' + - 'category:MongoJS' + - 'category:Pug_Jade' + - JSRelatedInspections + + - groupId: FlakyInspections + inspections: + - InconsistentNaming + - SpellCheckingInspection + + - groupId: ProofreadingInspections + inspections: + - LanguageDetectionInspection + - GrazieInspection + + - groupId: UnsupportedInspections # Annotator causes exceptions on MSBuild related files + inspections: + - Annotator + + - groupId: DotnetExcluded + groups: + - GLOBAL # Qodana doesn't run global inspections by default, due to significant time consumption + - JSRelatedInspections + - JSInspections + - FlakyInspections + - ProofreadingInspections + - UnsupportedInspections + - RiderStaticSanityInspections # Inspections that appear in sanity profile + - 'severity:INFORMATION' # Qodana doesn't run "invisible" and "technical" in IDE inspections + - 'severity:TEXT ATTRIBUTES' # Qodana don't run "invisible" and "technical" in IDE inspections + - groupId: NonReSharperInspections + groups: + - ALL + - '!ReSharperInspections' + - '!QodanaDotNetInspections' + + - groupId: LowSeverity + groups: + - 'severity:WEAK WARNING' + - 'severity:HINT' + - 'severity:TYPO' + +inspections: + - group: DotnetExcluded + enabled: false + - group: QodanaDotNetInspections # Explicitly enable coverage and dependency vulnerability analysis by default + enabled: true + - inspection: HttpUrlsUsage # Spam + enabled: false + - group: ALL + ignore: + - '.qodana/**' + - '**/*.DotSettings' # Ignore .DotSettings files by default + - 'scope#$gitignore' # $gitignore scope available only in qodana execution + - 'scope#$UnrealEngine' # Ignore Unreal Engine files by default + - group: LowSeverity # CLT doesn't provide them by default + enabled: false + - group: NonReSharperInspections + enabled: false \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only local input qodana yaml/expected/effective.qodana.yaml b/platform/effectiveconfig/testdata/only local input qodana yaml/expected/effective.qodana.yaml new file mode 100644 index 00000000..cc0bce14 --- /dev/null +++ b/platform/effectiveconfig/testdata/only local input qodana yaml/expected/effective.qodana.yaml @@ -0,0 +1,20 @@ +version: 1.0 + +profile: + path: a-profile.yaml + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + # from inner + - name: InspectionC # from inner + - name: InspectionD + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root + moderate: 3 # moderate is from inner \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only local input qodana yaml/expected/qodana-config.json b/platform/effectiveconfig/testdata/only local input qodana yaml/expected/qodana-config.json new file mode 100644 index 00000000..9f07c184 --- /dev/null +++ b/platform/effectiveconfig/testdata/only local input qodana yaml/expected/qodana-config.json @@ -0,0 +1,6 @@ +{ + "local": { + "filename": "qodana.yaml", + "content": "version: 1.0\n\nimports:\n - inner.yaml\n\nprofile:\n path: a-profile.yaml\n\ninclude:\n # from root\n - name: InspectionA\n - name: InspectionB #from root\n\n#this is a failure conditions section\nfailureConditions:\n # this is a severityThresholds section\n severityThresholds:\n any: 1 #from root\n critical: 2 # from root" + } +} \ No newline at end of file diff --git a/platform/effectiveconfig/testdata/only local input qodana yaml/expected/qodana.yaml b/platform/effectiveconfig/testdata/only local input qodana yaml/expected/qodana.yaml new file mode 100644 index 00000000..8fc1b86b --- /dev/null +++ b/platform/effectiveconfig/testdata/only local input qodana yaml/expected/qodana.yaml @@ -0,0 +1,19 @@ +version: 1.0 + +imports: + - inner.yaml + +profile: + path: a-profile.yaml + +include: + # from root + - name: InspectionA + - name: InspectionB #from root + +#this is a failure conditions section +failureConditions: + # this is a severityThresholds section + severityThresholds: + any: 1 #from root + critical: 2 # from root \ No newline at end of file diff --git a/platform/product/product_info.go b/platform/product/product_info.go index 26d293b7..d3bc6a8f 100644 --- a/platform/product/product_info.go +++ b/platform/product/product_info.go @@ -220,6 +220,10 @@ func (p Product) Is242orNewer() bool { return p.isNotOlderThan(242) } +func (p Product) Is251orNewer() bool { + return p.isNotOlderThan(251) +} + func (p Product) isNotOlderThan(version int) bool { number, err := strconv.Atoi(p.GetVersionBranch()) if err != nil { diff --git a/platform/qdyaml/yaml.go b/platform/qdyaml/yaml.go index 5b52a36a..a747a2c5 100644 --- a/platform/qdyaml/yaml.go +++ b/platform/qdyaml/yaml.go @@ -19,7 +19,6 @@ package qdyaml import ( "bytes" "errors" - "fmt" "github.com/JetBrains/qodana-cli/v2025/platform/utils" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" @@ -29,45 +28,6 @@ import ( "strings" ) -// GetQodanaYamlPath returns the path to qodana.yaml or qodana.yml -func GetQodanaYamlPath(project string) (string, error) { - qodanaYamlPath := filepath.Join(project, "qodana.yaml") - if _, err := os.Stat(qodanaYamlPath); errors.Is(err, os.ErrNotExist) { - qodanaYamlPath = filepath.Join(project, "qodana.yml") - } - if _, err := os.Stat(qodanaYamlPath); errors.Is(err, os.ErrNotExist) { - return "", errors.New("qodana.yaml or qodana.yml not found") - } - return qodanaYamlPath, nil -} - -// GetQodanaYaml returns a parsed qodana.yaml or qodana.yml or error if not found/invalid -func GetQodanaYaml(project string) (QodanaYaml, error) { - q := &QodanaYaml{} - qodanaYamlPath, err := GetQodanaYamlPath(project) - if err != nil { - return *q, err - } - yamlFile, err := os.ReadFile(qodanaYamlPath) - if err != nil { - return *q, err - } - err = yaml.Unmarshal(yamlFile, q) - if err != nil { - return *q, fmt.Errorf("not a valid qodana.yaml: %w", err) - } - return *q, nil -} - -// GetQodanaYamlOrDefault reads qodana.yaml or qodana.yml and returns an empty config if not found or invalid -func GetQodanaYamlOrDefault(project string) QodanaYaml { - q, err := GetQodanaYaml(project) - if err != nil { - log.Printf("Problem loading qodana.yaml: %v ", err) - } - return q -} - // QodanaYaml A standard qodana.yaml (or qodana.yml) format for Qodana configuration. // https://github.com/JetBrains/qodana-profiles/blob/master/schemas/qodana-yaml-1.0.json type QodanaYaml struct { @@ -359,32 +319,38 @@ type Php struct { Version string `yaml:"version,omitempty"` } -// FindDefaultQodanaYaml checks whether qodana.yaml exists or not -func FindDefaultQodanaYaml(project string) string { - filename := "qodana.yml" - if info, _ := os.Stat(filepath.Join(project, filename)); info != nil { - return filename - } else { - return "qodana.yaml" +func defaultLocalNotEffectiveQodanaYamlIfExists(project string) string { + filenames := []string{"qodana.yml", "qodana.yaml"} + for _, filename := range filenames { + if info, _ := os.Stat(filepath.Join(project, filename)); info != nil { + return filename + } } + return "" } -func GetQodanaYamlPathWithProject(project string, filename string) string { - if filename == "" { - filename = FindDefaultQodanaYaml(project) +// GetLocalNotEffectiveQodanaYamlFullPath does not process `imports` field and doesn't use global configuration +func GetLocalNotEffectiveQodanaYamlFullPath(project string, yamlPathInProject string) string { + if yamlPathInProject == "" { + yamlPathInProject = defaultLocalNotEffectiveQodanaYamlIfExists(project) + } + if yamlPathInProject == "" { + return "" } - qodanaYamlPath := filepath.Join(project, filename) - return qodanaYamlPath + return filepath.Join(project, yamlPathInProject) } -// LoadQodanaYaml gets Qodana YAML from the project. -func LoadQodanaYaml(project string, filename string) QodanaYaml { - qodanaYamlPath := GetQodanaYamlPathWithProject(project, filename) - q := LoadQodanaYamlByFullPath(qodanaYamlPath) - return q +// TestOnlyLoadLocalNotEffectiveQodanaYaml test only! +// Gets Qodana YAML from the project. Does not process `imports` field and doesn't use global configurations +func TestOnlyLoadLocalNotEffectiveQodanaYaml(project string, yamlPathInProject string) QodanaYaml { + qodanaYamlPath := GetLocalNotEffectiveQodanaYamlFullPath(project, yamlPathInProject) + return LoadQodanaYamlByFullPath(qodanaYamlPath) } func LoadQodanaYamlByFullPath(fullPath string) QodanaYaml { + if fullPath == "" { + return QodanaYaml{} + } q := &QodanaYaml{} if _, err := os.Stat(fullPath); errors.Is(err, os.ErrNotExist) { return *q @@ -460,8 +426,8 @@ func (q *QodanaYaml) IsDotNet() bool { } // WriteQodanaLinterToYamlFile adds the linter to the qodana.yaml file. -func WriteQodanaLinterToYamlFile(path string, linter string, filename string, allProductCodes []string) { - q := LoadQodanaYaml(path, filename) +func WriteQodanaLinterToYamlFile(qodanaYamlFullPath string, linter string, allProductCodes []string) { + q := LoadQodanaYamlByFullPath(qodanaYamlFullPath) if q.Version == "" { q.Version = "1.0" } @@ -471,17 +437,17 @@ func WriteQodanaLinterToYamlFile(path string, linter string, filename string, al } else { q.Linter = linter } - err := q.WriteConfig(filepath.Join(path, filename)) + err := q.WriteConfig(qodanaYamlFullPath) if err != nil { log.Fatalf("writeConfig: %v", err) } } // SetQodanaDotNet adds the .NET configuration to the qodana.yaml file. -func SetQodanaDotNet(path string, dotNet *DotNet, filename string) bool { - q := LoadQodanaYaml(path, filename) +func SetQodanaDotNet(qodanaYamlFullPath string, dotNet *DotNet) bool { + q := LoadQodanaYamlByFullPath(qodanaYamlFullPath) q.DotNet = *dotNet - err := q.WriteConfig(filepath.Join(path, filename)) + err := q.WriteConfig(qodanaYamlFullPath) if err != nil { log.Fatalf("writeConfig: %v", err) } diff --git a/platform/qdyaml/yaml_test.go b/platform/qdyaml/yaml_test.go index da7f98e8..b5bba254 100644 --- a/platform/qdyaml/yaml_test.go +++ b/platform/qdyaml/yaml_test.go @@ -151,7 +151,7 @@ script: t.Run( tc.description, func(t *testing.T) { tc.setup(tc.filename) - actual := LoadQodanaYaml(tc.project, tc.filename) + actual := TestOnlyLoadLocalNotEffectiveQodanaYaml(tc.project, tc.filename) _ = os.Remove(filepath.Join(tc.project, tc.filename)) assert.Equal(t, tc.expected, actual) }, diff --git a/platform/run.go b/platform/run.go index 3b7d9ed4..d4c036c5 100644 --- a/platform/run.go +++ b/platform/run.go @@ -22,6 +22,7 @@ import ( "github.com/JetBrains/qodana-cli/v2025/cloud" "github.com/JetBrains/qodana-cli/v2025/platform/cmd" "github.com/JetBrains/qodana-cli/v2025/platform/commoncontext" + "github.com/JetBrains/qodana-cli/v2025/platform/effectiveconfig" "github.com/JetBrains/qodana-cli/v2025/platform/msg" "github.com/JetBrains/qodana-cli/v2025/platform/qdenv" "github.com/JetBrains/qodana-cli/v2025/platform/qdyaml" @@ -76,10 +77,36 @@ func RunThirdPartyLinterAnalysis( tempMountPath, mountInfo := extractUtils(linter, commonCtx.CacheDir, isCommunity) defer cleanupUtils(tempMountPath) - qodanaYamlPath := qdyaml.GetQodanaYamlPathWithProject(commonCtx.ProjectDir, cliOptions.ConfigName) - yaml := qdyaml.LoadQodanaYamlByFullPath(qodanaYamlPath) + localQodanaYamlFullPath := qdyaml.GetLocalNotEffectiveQodanaYamlFullPath( + commonCtx.ProjectDir, + cliOptions.ConfigName, + ) + qodanaConfigEffectiveFiles, err := effectiveconfig.CreateEffectiveConfigFiles( + localQodanaYamlFullPath, + cliOptions.GlobalConfigurationsFile, + cliOptions.GlobalConfigurationId, + mountInfo.JavaPath, + commonCtx.QodanaSystemDir, + "qdconfig", + commonCtx.LogDir(), + ) + if err != nil { + log.Fatalf("Failed to load Qodana configuration %s", err) + } + qodanaYamlConfig := thirdpartyscan.QodanaYamlConfig{} + if qodanaConfigEffectiveFiles.EffectiveQodanaYamlPath != "" { + yaml := qdyaml.LoadQodanaYamlByFullPath(qodanaConfigEffectiveFiles.EffectiveQodanaYamlPath) + qodanaYamlConfig = thirdpartyscan.YamlConfig(yaml) + } - context := thirdpartyscan.ComputeContext(cliOptions, commonCtx, linterInfo, mountInfo, thirdPartyCloudData, yaml) + context := thirdpartyscan.ComputeContext( + cliOptions, + commonCtx, + linterInfo, + mountInfo, + thirdPartyCloudData, + qodanaYamlConfig, + ) LogContext(&context) @@ -115,9 +142,12 @@ func RunThirdPartyLinterAnalysis( return 1, err } resultsPath := ReportResultsPath(context.ResultsDir()) - if err = copyQodanaYamlToReportPath(qodanaYamlPath, resultsPath); err != nil { - msg.ErrorMessage(err.Error()) - return 1, err + if qodanaConfigEffectiveFiles.LocalQodanaYamlPath != "" { + err = copyQodanaYamlToReportPath(qodanaConfigEffectiveFiles.LocalQodanaYamlPath, resultsPath) + if err != nil { + msg.ErrorMessage(err.Error()) + return 1, err + } } sendReportToQodanaServer(context) return analysisResult, nil diff --git a/platform/thirdpartyscan/compute.go b/platform/thirdpartyscan/compute.go index 2cd2ea16..a681fd63 100644 --- a/platform/thirdpartyscan/compute.go +++ b/platform/thirdpartyscan/compute.go @@ -19,7 +19,6 @@ package thirdpartyscan import ( "github.com/JetBrains/qodana-cli/v2025/platform/cmd" "github.com/JetBrains/qodana-cli/v2025/platform/commoncontext" - "github.com/JetBrains/qodana-cli/v2025/platform/qdyaml" "path/filepath" "strings" ) @@ -30,7 +29,7 @@ func ComputeContext( linterInfo LinterInfo, mountInfo MountInfo, cloudData ThirdPartyStartupCloudData, - qodanaYaml qdyaml.QodanaYaml, + qodanaYamlConfig QodanaYamlConfig, ) Context { projectDir := initArgs.ProjectDir @@ -66,6 +65,6 @@ func ComputeContext( Baseline: cliOptions.Baseline, BaselineIncludeAbsent: cliOptions.BaselineIncludeAbsent, FailThreshold: cliOptions.FailThreshold, - QodanaYaml: qodanaYaml, + QodanaYamlConfig: qodanaYamlConfig, }.Build() } diff --git a/platform/thirdpartyscan/context.go b/platform/thirdpartyscan/context.go index 4dd3d69e..c5060df6 100644 --- a/platform/thirdpartyscan/context.go +++ b/platform/thirdpartyscan/context.go @@ -88,6 +88,29 @@ type Context struct { baselineIncludeAbsent bool failThreshold string qodanaYaml qdyaml.QodanaYaml + qodanaYamlConfig QodanaYamlConfig +} + +type QodanaYamlConfig struct { + Bootstrap string + Version string + DotNet qdyaml.DotNet + Includes []qdyaml.Clude + Excludes []qdyaml.Clude + FailThreshold *int + FailureConditions qdyaml.FailureConditions +} + +func YamlConfig(yaml qdyaml.QodanaYaml) QodanaYamlConfig { + return QodanaYamlConfig{ + Bootstrap: yaml.Bootstrap, + Version: yaml.Version, + DotNet: yaml.DotNet, + Includes: yaml.Includes, + Excludes: yaml.Excludes, + FailThreshold: yaml.FailThreshold, + FailureConditions: yaml.FailureConditions, + } } type ContextBuilder struct { @@ -111,7 +134,7 @@ type ContextBuilder struct { Baseline string BaselineIncludeAbsent bool FailThreshold string - QodanaYaml qdyaml.QodanaYaml + QodanaYamlConfig QodanaYamlConfig } func (b ContextBuilder) Build() Context { @@ -136,7 +159,7 @@ func (b ContextBuilder) Build() Context { baseline: b.Baseline, baselineIncludeAbsent: b.BaselineIncludeAbsent, failThreshold: b.FailThreshold, - qodanaYaml: b.QodanaYaml, + qodanaYamlConfig: b.QodanaYamlConfig, } } @@ -159,7 +182,7 @@ func (c Context) AnalysisId() string { return c.analysisId } func (c Context) Baseline() string { return c.baseline } func (c Context) BaselineIncludeAbsent() bool { return c.baselineIncludeAbsent } func (c Context) FailThreshold() string { return c.failThreshold } -func (c Context) QodanaYaml() qdyaml.QodanaYaml { return c.qodanaYaml } +func (c Context) QodanaYamlConfig() QodanaYamlConfig { return c.qodanaYamlConfig } func (c Context) Property() []string { props := make([]string, len(c.property)) diff --git a/platform/thresholds.go b/platform/thresholds.go index f5f009bb..e09fed6f 100644 --- a/platform/thresholds.go +++ b/platform/thresholds.go @@ -30,7 +30,7 @@ const severityLow = "low" const severityInfo = "info" func getFailureThresholds(c thirdpartyscan.Context) map[string]string { - yaml := c.QodanaYaml() + yaml := c.QodanaYamlConfig() ret := make(map[string]string) if yaml.FailThreshold != nil { ret[severityAny] = strconv.Itoa(*yaml.FailThreshold) diff --git a/platform/thresholds_test.go b/platform/thresholds_test.go index c4b68392..14decf50 100644 --- a/platform/thresholds_test.go +++ b/platform/thresholds_test.go @@ -115,10 +115,10 @@ failThreshold: 123 t.Fatal(err) } } - yaml := qdyaml.LoadQodanaYaml(tempDir, "qodana.yaml") + yaml := qdyaml.TestOnlyLoadLocalNotEffectiveQodanaYaml(tempDir, "qodana.yaml") c := thirdpartyscan.ContextBuilder{ - FailThreshold: testData.option, - QodanaYaml: yaml, + FailThreshold: testData.option, + QodanaYamlConfig: thirdpartyscan.YamlConfig(yaml), }.Build() thresholds := getFailureThresholds(c) thresholdArgs := thresholdsToArgs(thresholds) diff --git a/platform/utils/cmd.go b/platform/utils/cmd.go index b1de6bfe..905b730e 100644 --- a/platform/utils/cmd.go +++ b/platform/utils/cmd.go @@ -48,22 +48,23 @@ const ( // Bootstrap takes the given command (from CLI or qodana.yaml) and runs it. func Bootstrap(command string, project string) { - if command != "" { - var executor string - var flag string - switch runtime.GOOS { - case "windows": - executor = "cmd" - flag = "/c" - default: - executor = "sh" - flag = "-c" - } + if command == "" { + return + } + var executor string + var flag string + switch runtime.GOOS { + case "windows": + executor = "cmd" + flag = "/c" + default: + executor = "sh" + flag = "-c" + } - if res, err := RunCmd(project, executor, flag, "\""+command+"\""); res > 0 || err != nil { - log.Printf("Provided bootstrap command finished with error: %d. Exiting...", res) - os.Exit(res) - } + if res, err := RunCmd(project, executor, flag, "\""+command+"\""); res > 0 || err != nil { + log.Printf("Provided bootstrap command finished with error: %d. Exiting...", res) + os.Exit(res) } } @@ -73,7 +74,14 @@ func RunCmd(cwd string, args ...string) (int, error) { } // RunCmdWithTimeout executes subprocess with forwarding of signals, and returns its exit code. -func RunCmdWithTimeout(cwd string, stdout *os.File, stderr *os.File, timeout time.Duration, timeoutExitCode int, args ...string) (int, error) { +func RunCmdWithTimeout( + cwd string, + stdout *os.File, + stderr *os.File, + timeout time.Duration, + timeoutExitCode int, + args ...string, +) (int, error) { log.Debugf("Running command: %v", args) cmd := exec.Command("bash", "-c", strings.Join(args, " ")) // TODO : Viktor told about set -e var stdoutPipe, stderrPipe io.ReadCloser @@ -198,7 +206,10 @@ func handleSignals(cmd *exec.Cmd, waitCh <-chan error, timeout time.Duration, ti for { select { case sig := <-sigChan: - if err := cmd.Process.Signal(sig); err != nil && !errors.Is(err, os.ErrProcessDone) { // Use errors.Is for semantic comparison + if err := cmd.Process.Signal(sig); err != nil && !errors.Is( + err, + os.ErrProcessDone, + ) { // Use errors.Is for semantic comparison log.Error("Error sending signal: ", sig, err) } case <-timeoutCh: diff --git a/platform/utils/utils.go b/platform/utils/utils.go index 439f9258..2226e71d 100644 --- a/platform/utils/utils.go +++ b/platform/utils/utils.go @@ -101,6 +101,9 @@ func FindFiles(root string, extensions []string) []string { // QuoteIfSpace wraps in '"' if '`s`' Contains space. func QuoteIfSpace(s string) string { + if isStringQuoted(s) { + return s + } if strings.Contains(s, " ") { return "\"" + s + "\"" } else { @@ -110,6 +113,9 @@ func QuoteIfSpace(s string) string { // QuoteForWindows wraps in '"' if '`s`' contains space on windows. func QuoteForWindows(s string) string { + if isStringQuoted(s) { + return s + } if //goland:noinspection GoBoolExpressions strings.Contains(s, " ") && runtime.GOOS == "windows" { return "\"" + s + "\"" @@ -118,6 +124,10 @@ func QuoteForWindows(s string) string { } } +func isStringQuoted(s string) bool { + return strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"") +} + func GetJavaExecutablePath() (string, error) { var java string var err error diff --git a/tooling/config-loader-cli-0.0.6.jar.sha256 b/tooling/config-loader-cli-0.0.6.jar.sha256 new file mode 100644 index 00000000..cc51ad11 --- /dev/null +++ b/tooling/config-loader-cli-0.0.6.jar.sha256 @@ -0,0 +1 @@ +14ff4953d7e97cc0435d27d65129a48c8015d32edc83e1766ee58b0200bdb3ce \ No newline at end of file diff --git a/tooling/config_loader_cli.go b/tooling/config_loader_cli.go new file mode 100644 index 00000000..2335b41e --- /dev/null +++ b/tooling/config_loader_cli.go @@ -0,0 +1,22 @@ +/* + * Copyright 2021-2024 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tooling + +import _ "embed" + +//go:embed config-loader-cli.jar +var ConfigLoaderCli []byte