Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 9 additions & 22 deletions application/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,29 +180,22 @@ func ParseOAuthToken(token string, logger *zerolog.Logger) (oauth2.Token, error)
return *oauthToken, nil
}

// GetFolderConfigFromEngine retrieves or creates a folder config and attaches the engine and resolver.
// GetFolderConfigFromEngine creates a folder config and attaches the engine and resolver. Enriches from Git.
func GetFolderConfigFromEngine(engine workflow.Engine, resolver types.ConfigResolverInterface, path types.FilePath, logger *zerolog.Logger) *types.FolderConfig {
conf := engine.GetConfiguration()
folderConfig, err := folderconfig.GetOrCreateFolderConfig(conf, path, logger)
if err != nil {
logger.Err(err).Msg("unable to get or create folder config")
folderConfig = &types.FolderConfig{FolderPath: path}
}
folderConfig := folderconfig.GetFolderConfigWithOptions(conf, path, logger, folderconfig.GetFolderConfigOptions{
EnrichFromGit: true,
})
wireConfigResolver(folderConfig, engine, resolver)
return folderConfig
}

// GetUnenrichedFolderConfigFromEngine returns a read-only folder config without writing to storage.
// GetUnenrichedFolderConfigFromEngine creates a folder config and attaches the engine and resolver. Does not enrich from Git.
func GetUnenrichedFolderConfigFromEngine(engine workflow.Engine, resolver types.ConfigResolverInterface, path types.FilePath, logger *zerolog.Logger) *types.FolderConfig {
conf := engine.GetConfiguration()
folderConfig, err := folderconfig.GetFolderConfigWithOptions(conf, path, logger, folderconfig.GetFolderConfigOptions{
CreateIfNotExist: true,
EnrichFromGit: false,
folderConfig := folderconfig.GetFolderConfigWithOptions(conf, path, logger, folderconfig.GetFolderConfigOptions{
EnrichFromGit: false,
})
if err != nil {
logger.Err(err).Msg("unable to get or create folder config")
folderConfig = &types.FolderConfig{FolderPath: path}
}
wireConfigResolver(folderConfig, engine, resolver)
return folderConfig
}
Expand Down Expand Up @@ -730,15 +723,9 @@ func FolderOrganization(conf configuration.Configuration, path types.FilePath, l
return globalOrg
}

fc, err := folderconfig.GetFolderConfigWithOptions(conf, path, logger, folderconfig.GetFolderConfigOptions{
CreateIfNotExist: false,
EnrichFromGit: false,
fc := folderconfig.GetFolderConfigWithOptions(conf, path, logger, folderconfig.GetFolderConfigOptions{
EnrichFromGit: false,
})
if err != nil {
globalOrg := types.GetGlobalOrganization(conf)
ctxLogger.Warn().Err(err).Str("globalOrg", globalOrg).Msg("error getting folder config, falling back to global organization")
return globalOrg
}

fcConf := fc.Conf()
if fcConf == nil {
Expand Down
31 changes: 9 additions & 22 deletions application/server/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,18 @@ import (
"github.com/creachadair/jrpc2"
"github.com/creachadair/jrpc2/handler"
"github.com/rs/zerolog"
sglsp "github.com/sourcegraph/go-lsp"

"github.com/snyk/go-application-framework/pkg/configuration"
"github.com/snyk/go-application-framework/pkg/configuration/configresolver"
"github.com/snyk/go-application-framework/pkg/workflow"
sglsp "github.com/sourcegraph/go-lsp"

"github.com/snyk/snyk-ls/internal/folderconfig"
mcpWorkflow "github.com/snyk/snyk-ls/internal/mcp"

"github.com/snyk/snyk-ls/application/config"
"github.com/snyk/snyk-ls/application/di"
"github.com/snyk/snyk-ls/domain/ide/command"
"github.com/snyk/snyk-ls/infrastructure/analytics"
ctx2 "github.com/snyk/snyk-ls/internal/context"
mcpWorkflow "github.com/snyk/snyk-ls/internal/mcp"
"github.com/snyk/snyk-ls/internal/notification"
"github.com/snyk/snyk-ls/internal/product"
"github.com/snyk/snyk-ls/internal/types"
Expand Down Expand Up @@ -290,27 +289,16 @@ func processFolderConfigs(conf configuration.Configuration, engine workflow.Engi
Msg("processFolderConfigs - processing folder configs")

var processedConfigs []types.FolderConfig
var changedConfigs []*types.FolderConfig
// Always notify when the client explicitly sends folder configs — it expects the resolved state back.
needsToSendUpdateToClient := len(incomingMap) > 0

for path := range allPaths {
folderConfig, oldSnapshot, newSnapshot, configChanged := processSingleLspFolderConfig(conf, engine, logger, path, incomingMap, notifier)

if configChanged {
changedConfigs = append(changedConfigs, &folderConfig)
}
folderConfig, oldSnapshot, newSnapshot := processSingleLspFolderConfig(conf, engine, logger, path, incomingMap, notifier)

handleFolderCacheClearing(conf, engine, logger, path, oldSnapshot, newSnapshot, triggerSource, configResolver)
processedConfigs = append(processedConfigs, folderConfig)
}

if len(changedConfigs) > 0 {
if err := folderconfig.BatchUpdateFolderConfigs(conf, changedConfigs, logger); err != nil {
logger.Err(err).Int("count", len(changedConfigs)).Msg("failed to batch update folder configs")
}
}

sendFolderConfigUpdateIfNeeded(conf, engine, logger, notifier, processedConfigs, needsToSendUpdateToClient, triggerSource, configResolver)
}

Expand Down Expand Up @@ -859,8 +847,8 @@ func gatherAllFolderPathsFromLspConfigs(incomingMap map[types.FilePath]types.Lsp
// - For *LocalConfigField: nil = don't change, Changed+Value = set, Changed+nil = reset
// It loads the existing FolderConfig (unenriched), applies the LspFolderConfig updates, and returns
// the processed config without persisting. The caller is responsible for batch-persisting all changes.
// Returns: (processedConfig, oldSnapshot, newSnapshot, configChanged)
func processSingleLspFolderConfig(conf configuration.Configuration, engine workflow.Engine, logger *zerolog.Logger, path types.FilePath, incomingMap map[types.FilePath]types.LspFolderConfig, notifier notification.Notifier) (types.FolderConfig, types.FolderConfigSnapshot, types.FolderConfigSnapshot, bool) {
// Returns: (processedConfig, oldSnapshot, newSnapshot)
func processSingleLspFolderConfig(conf configuration.Configuration, engine workflow.Engine, logger *zerolog.Logger, path types.FilePath, incomingMap map[types.FilePath]types.LspFolderConfig, notifier notification.Notifier) (types.FolderConfig, types.FolderConfigSnapshot, types.FolderConfigSnapshot) {
subLogger := logger.With().Str("method", "processSingleLspFolderConfig").Str("path", string(path)).Logger()
resolver := di.ConfigResolver()
fc := config.GetUnenrichedFolderConfigFromEngine(engine, resolver, path, logger)
Expand All @@ -870,7 +858,6 @@ func processSingleLspFolderConfig(conf configuration.Configuration, engine workf

// Validate that the changes are allowed, then apply the new config.
normalizedPath := fc.FolderPath
applyChanged := false
if incoming, hasIncoming := incomingMap[normalizedPath]; hasIncoming {
hasLockedFieldRejections := validateLockedFields(conf, fc, &incoming, &subLogger)
if hasLockedFieldRejections {
Expand All @@ -879,16 +866,16 @@ func processSingleLspFolderConfig(conf configuration.Configuration, engine workf
fmt.Sprintf("Failed to update %s: Some settings are locked by your organization's policy", folderName))
}

applyChanged = fc.ApplyLspUpdate(&incoming)
// Apply the update and don't care if it has changed or not.
_ = fc.ApplyLspUpdate(&incoming)
}

updateFolderOrgIfNeeded(conf, engine, logger, fc, fc, oldSnapshot, notifier)
di.FeatureFlagService().PopulateFolderConfig(fc)

newSnapshot := types.ReadFolderConfigSnapshot(conf, normalizedPath)
configChanged := applyChanged

return *fc, oldSnapshot, newSnapshot, configChanged
return *fc, oldSnapshot, newSnapshot
}

// validateLockedFields checks if any fields in the incoming LspFolderConfig are locked by LDX-Sync.
Expand Down
10 changes: 6 additions & 4 deletions application/server/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -722,8 +722,9 @@ func (s *folderConfigTestSetup) createStoredConfig(org string, userSet bool) {
}

func (s *folderConfigTestSetup) getUpdatedConfig() *types.FolderConfig {
updatedConfig, err := folderconfig.GetOrCreateFolderConfig(s.engineConfig, s.folderPath, s.logger)
require.NoError(s.t, err)
updatedConfig := folderconfig.GetFolderConfigWithOptions(s.engineConfig, s.folderPath, s.logger, folderconfig.GetFolderConfigOptions{
EnrichFromGit: false,
})
updatedConfig.ConfigResolver = types.NewMinimalConfigResolver(s.engineConfig)
return updatedConfig
}
Expand Down Expand Up @@ -1150,8 +1151,9 @@ func Test_updateFolderConfig_Unauthenticated_UserSetsPreferredOrg(t *testing.T)
}
UpdateSettings(engine.GetConfiguration(), engine, engine.GetLogger(), nil, folderConfigs, analytics.TriggerSourceTest, testutil.DefaultConfigResolver(engine))

updatedConfig, err := folderconfig.GetOrCreateFolderConfig(engineConfig, folderPath, engine.GetLogger())
require.NoError(t, err)
updatedConfig := folderconfig.GetFolderConfigWithOptions(engineConfig, folderPath, engine.GetLogger(), folderconfig.GetFolderConfigOptions{
EnrichFromGit: false,
})
updatedConfig.ConfigResolver = types.NewMinimalConfigResolver(engineConfig)
assert.Equal(t, "user-chosen-org", updatedConfig.PreferredOrg(), "PreferredOrg should be set")
assert.True(t, updatedConfig.OrgSetByUser(), "OrgSetByUser should be true when user chose org")
Expand Down
13 changes: 3 additions & 10 deletions domain/ide/command/folder_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,9 @@ func buildLspFolderConfigs(conf configuration.Configuration, engine workflow.Eng
var lspFolderConfigs []types.LspFolderConfig

for _, folder := range ws.Folders() {
fc, err := folderconfig.GetOrCreateFolderConfig(engineConfig, folder.Path(), &log)
if err != nil {
log.Err(err).Msg("unable to load folderConfig")
continue
}

if fc == nil {
log.Warn().Str("path", string(folder.Path())).Msg("folder config is nil, skipping")
continue
}
fc := folderconfig.GetFolderConfigWithOptions(engineConfig, folder.Path(), &log, folderconfig.GetFolderConfigOptions{
EnrichFromGit: true,
})
folderConfig := fc.Clone()

if featureFlagService != nil {
Expand Down
12 changes: 4 additions & 8 deletions domain/ide/command/ldx_sync_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,11 @@ func (s *DefaultLdxSyncService) RefreshConfigFromLdxSync(ctx context.Context, co
}

// Get PreferredOrg from folder config (or empty string if missing)
folderConfig, err := folderconfig.GetFolderConfigWithOptions(prefixKeyConfig, f.Path(), &log, folderconfig.GetFolderConfigOptions{
CreateIfNotExist: false,
EnrichFromGit: false,
folderConfig := folderconfig.GetFolderConfigWithOptions(prefixKeyConfig, f.Path(), &log, folderconfig.GetFolderConfigOptions{
EnrichFromGit: false,
})
preferredOrg := ""
if err == nil && folderConfig != nil {
snapshot := types.ReadFolderConfigSnapshot(prefixKeyConfig, folderConfig.FolderPath)
preferredOrg = snapshot.PreferredOrg
}
snapshot := types.ReadFolderConfigSnapshot(prefixKeyConfig, folderConfig.FolderPath)
preferredOrg := snapshot.PreferredOrg

log.Debug().
Str("projectPath", string(f.Path())).
Expand Down
5 changes: 2 additions & 3 deletions internal/folderconfig/cross_platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
"github.com/snyk/snyk-ls/internal/types"
)

func Test_GetOrCreateFolderConfig_CrossPlatformPaths(t *testing.T) {
func Test_GetFolderConfigWithOptions_CrossPlatformPaths(t *testing.T) {
// Create one temporary directory for testing
tempDir := t.TempDir()

Expand Down Expand Up @@ -76,10 +76,9 @@ func Test_GetOrCreateFolderConfig_CrossPlatformPaths(t *testing.T) {
logger := zerolog.New(zerolog.NewTestWriter(t))

// Act
folderConfig, err := GetOrCreateFolderConfig(conf, tt.inputPath, &logger)
folderConfig := GetFolderConfigWithOptions(conf, tt.inputPath, &logger, GetFolderConfigOptions{})

// Assert
require.NoError(t, err)
require.NotNil(t, folderConfig)

// Calculate the expected normalized path for this specific input
Expand Down
33 changes: 6 additions & 27 deletions internal/folderconfig/folder_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,44 +28,23 @@ import (

// GetFolderConfigOptions controls the behavior of folder config retrieval
type GetFolderConfigOptions struct {
// CreateIfNotExist creates a new folder config if one doesn't exist.
// When ReadOnly=false: creates and saves to storage.
// When ReadOnly=true: creates in-memory but doesn't save.
CreateIfNotExist bool

// EnrichFromGit enriches the folder config with Git branch information.
EnrichFromGit bool
}

// GetFolderConfigWithOptions retrieves folder config from storage with specified behaviors
func GetFolderConfigWithOptions(conf configuration.Configuration, path types.FilePath, logger *zerolog.Logger, opts GetFolderConfigOptions) (*types.FolderConfig, error) {
func GetFolderConfigWithOptions(conf configuration.Configuration, path types.FilePath, logger *zerolog.Logger, opts GetFolderConfigOptions) *types.FolderConfig {
l := logger.With().Str("method", "GetFolderConfigWithOptions").Logger()

folderConfig, err := newFolderConfig(path, &l)
if err != nil {
return nil, err
}

// If folder config doesn't exist and we're not creating, return nil
if folderConfig == nil && !opts.CreateIfNotExist {
return nil, nil
}
normalizedPath := types.PathKey(path)
folderConfig := &types.FolderConfig{FolderPath: normalizedPath}

// Enrich from git if requested
if opts.EnrichFromGit && folderConfig != nil {
folderConfig = enrichFromGit(conf, &l, folderConfig)
if opts.EnrichFromGit {
enrichFromGit(conf, &l, folderConfig)
}

return folderConfig, nil
}

// GetOrCreateFolderConfig gets folder config from storage and merges it with Git data.
// Creates the config if it doesn't exist and writes back to storage.
func GetOrCreateFolderConfig(conf configuration.Configuration, path types.FilePath, logger *zerolog.Logger) (*types.FolderConfig, error) {
return GetFolderConfigWithOptions(conf, path, logger, GetFolderConfigOptions{
CreateIfNotExist: true,
EnrichFromGit: true,
})
return folderConfig
}

// SliceContainsParam checks if the parameter name is equal by splitting the given
Expand Down
Loading
Loading