diff --git a/external-providers/generic-external-provider/go.mod b/external-providers/generic-external-provider/go.mod index 914d71d3..a66689c5 100644 --- a/external-providers/generic-external-provider/go.mod +++ b/external-providers/generic-external-provider/go.mod @@ -5,7 +5,7 @@ go 1.23.9 require ( github.com/bombsimon/logrusr/v3 v3.1.0 github.com/go-logr/logr v1.4.2 - github.com/konveyor/analyzer-lsp v0.9.0-alpha.1.0.20251121202139-b3f742caf596 + github.com/konveyor/analyzer-lsp v0.9.0-alpha.1.0.20251126192951-3d1bb23d760c github.com/sirupsen/logrus v1.9.3 github.com/swaggest/openapi-go v0.2.50 go.lsp.dev/uri v0.3.0 diff --git a/external-providers/generic-external-provider/go.sum b/external-providers/generic-external-provider/go.sum index a21f830d..c143af03 100644 --- a/external-providers/generic-external-provider/go.sum +++ b/external-providers/generic-external-provider/go.sum @@ -33,8 +33,8 @@ github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mO github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= -github.com/konveyor/analyzer-lsp v0.9.0-alpha.1.0.20251121202139-b3f742caf596 h1:qkA2QN9UVNFNHG6ZJk+VjHgOtQ1ZTrluF0S9IY8lIy4= -github.com/konveyor/analyzer-lsp v0.9.0-alpha.1.0.20251121202139-b3f742caf596/go.mod h1:tZkfGRokJZqtmNVeEPIFlxltZhZXXMM5g2NNdxmhTXM= +github.com/konveyor/analyzer-lsp v0.9.0-alpha.1.0.20251126192951-3d1bb23d760c h1:7KkKAnUEEaluK/MQtuMCFeutLlw1arXTQNEjkJUP/WI= +github.com/konveyor/analyzer-lsp v0.9.0-alpha.1.0.20251126192951-3d1bb23d760c/go.mod h1:tZkfGRokJZqtmNVeEPIFlxltZhZXXMM5g2NNdxmhTXM= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= diff --git a/parser/rule_parser.go b/parser/rule_parser.go index ab4308d5..1902ead8 100644 --- a/parser/rule_parser.go +++ b/parser/rule_parser.go @@ -359,7 +359,7 @@ func (r *RuleParser) LoadRule(filepath string) ([]engine.Rule, map[string]provid key, ok := k.(string) if !ok { r.Log.V(8).Info("condition key must be a string", "ruleID", ruleID, "file", filepath) - return nil, nil, nil,fmt.Errorf("condition key must be a string") + return nil, nil, nil, fmt.Errorf("condition key must be a string") } switch key { case "or": @@ -369,7 +369,7 @@ func (r *RuleParser) LoadRule(filepath string) ([]engine.Rule, map[string]provid r.Log.V(8).Info("invalid type for or clause, must be an array", "ruleID", ruleID, "file", filepath) return nil, nil, nil, fmt.Errorf("invalid type for or clause, must be an array") } - conditions, provs, err := r.getConditions(m) + conditions, provs, provConditions, err := r.getConditions(m) if err != nil { r.Log.V(8).Error(err, "failed parsing conditions in or clause", "ruleID", ruleID, "file", filepath) return nil, nil, nil, err @@ -387,6 +387,12 @@ func (r *RuleParser) LoadRule(filepath string) ([]engine.Rule, map[string]provid } providers[k] = prov } + for k, v := range provConditions { + if _, ok := providerConditions[k]; !ok { + providerConditions[k] = []provider.ConditionsByCap{} + } + providerConditions[k] = append(providerConditions[k], v...) + } if len(snippers) > 0 { rule.Snipper = provider.CodeSnipProvider{ Providers: snippers, @@ -399,7 +405,7 @@ func (r *RuleParser) LoadRule(filepath string) ([]engine.Rule, map[string]provid r.Log.V(8).Info("invalid type for and clause, must be an array", "ruleID", ruleID, "file", filepath) return nil, nil, nil, fmt.Errorf("invalid type for and clause, must be an array") } - conditions, provs, err := r.getConditions(m) + conditions, provs, provConditions, err := r.getConditions(m) if err != nil { r.Log.V(8).Error(err, "failed parsing conditions in and clause", "ruleID", ruleID, "file", filepath) return nil, nil, nil, err @@ -419,6 +425,12 @@ func (r *RuleParser) LoadRule(filepath string) ([]engine.Rule, map[string]provid } providers[k] = prov } + for k, v := range provConditions { + if _, ok := providerConditions[k]; !ok { + providerConditions[k] = []provider.ConditionsByCap{} + } + providerConditions[k] = append(providerConditions[k], v...) + } if len(snippers) > 0 { rule.Snipper = provider.CodeSnipProvider{ Providers: snippers, @@ -612,7 +624,7 @@ func (r *RuleParser) addCustomVarFields(m map[interface{}]interface{}, customVar return nil } -func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine.ConditionEntry, map[string]provider.InternalProviderClient, error) { +func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine.ConditionEntry, map[string]provider.InternalProviderClient, map[string][]provider.ConditionsByCap, error) { conditions := []engine.ConditionEntry{} providers := map[string]provider.InternalProviderClient{} providerConditions := map[string][]provider.ConditionsByCap{} @@ -622,7 +634,7 @@ func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine. // get map from interface conditionMap, ok := conditionInterface.(map[interface{}]interface{}) if !ok { - return nil, nil, fmt.Errorf("conditions must be an object") + return nil, nil, nil, fmt.Errorf("conditions must be an object") } var from string var as string @@ -633,7 +645,7 @@ func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine. delete(conditionMap, "from") from, ok = fromRaw.(string) if !ok { - return nil, nil, fmt.Errorf("from must be a string literal, not %v", fromRaw) + return nil, nil, nil, fmt.Errorf("from must be a string literal, not %v", fromRaw) } } asRaw, ok := conditionMap["as"] @@ -641,7 +653,7 @@ func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine. delete(conditionMap, "as") as, ok = asRaw.(string) if !ok { - return nil, nil, fmt.Errorf("as must be a string literal, not %v", asRaw) + return nil, nil, nil, fmt.Errorf("as must be a string literal, not %v", asRaw) } } ignorableRaw, ok := conditionMap["ignore"] @@ -649,7 +661,7 @@ func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine. delete(conditionMap, "ignore") ignorable, ok = ignorableRaw.(bool) if !ok { - return nil, nil, fmt.Errorf("ignore must be a boolean, not %v", ignorableRaw) + return nil, nil, nil, fmt.Errorf("ignore must be a boolean, not %v", ignorableRaw) } } notKeywordRaw, ok := conditionMap["not"] @@ -657,24 +669,24 @@ func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine. delete(conditionMap, "not") not, ok = notKeywordRaw.(bool) if !ok { - return nil, nil, fmt.Errorf("not must be a boolean, not %v", notKeywordRaw) + return nil, nil, nil, fmt.Errorf("not must be a boolean, not %v", notKeywordRaw) } } for k, v := range conditionMap { key, ok := k.(string) if !ok { - return nil, nil, fmt.Errorf("condition key must be string") + return nil, nil, nil, fmt.Errorf("condition key must be string") } var ce engine.ConditionEntry switch key { case "and": iConditions, ok := v.([]interface{}) if !ok { - return nil, nil, fmt.Errorf("inner condition for and is not array") + return nil, nil, nil, fmt.Errorf("inner condition for and is not array") } - conds, provs, err := r.getConditions(iConditions) + conds, provs, provConditions, err := r.getConditions(iConditions) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if len(conds) != len(iConditions) { continue @@ -691,14 +703,20 @@ func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine. for k, prov := range provs { providers[k] = prov } + for k, v := range provConditions { + if _, ok := providerConditions[k]; !ok { + providerConditions[k] = []provider.ConditionsByCap{} + } + providerConditions[k] = append(providerConditions[k], v...) + } case "or": iConditions, ok := v.([]interface{}) if !ok { - return nil, nil, fmt.Errorf("inner condition for and is not array") + return nil, nil, nil, fmt.Errorf("inner condition for and is not array") } - conds, provs, err := r.getConditions(iConditions) + conds, provs, provConditions, err := r.getConditions(iConditions) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if len(conds) == 0 { continue @@ -715,14 +733,20 @@ func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine. for k, prov := range provs { providers[k] = prov } + for k, v := range provConditions { + if _, ok := providerConditions[k]; !ok { + providerConditions[k] = []provider.ConditionsByCap{} + } + providerConditions[k] = append(providerConditions[k], v...) + } case "": - return nil, nil, fmt.Errorf("must have at least one condition") + return nil, nil, nil, fmt.Errorf("must have at least one condition") default: // Need to get condition from provider // Handle provider s := strings.Split(key, ".") if len(s) != 2 { - return nil, nil, fmt.Errorf("condition must be of the form {provider}.{capability}") + return nil, nil, nil, fmt.Errorf("condition must be of the form {provider}.{capability}") } providerKey, capability := s[0], s[1] @@ -734,7 +758,7 @@ func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine. continue } // For other errors, return as before - return nil, nil, err + return nil, nil, nil, err } if condition == nil { continue @@ -753,11 +777,11 @@ func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine. } } if ce.From != "" && ce.As != "" && ce.From == ce.As { - return nil, nil, fmt.Errorf("condition cannot have the same value for fields 'from' and 'as'") + return nil, nil, nil, fmt.Errorf("condition cannot have the same value for fields 'from' and 'as'") } else if ce.As != "" { for _, as := range asFound { if as == ce.As { - return nil, nil, fmt.Errorf("condition cannot have multiple 'as' fields with the same name") + return nil, nil, nil, fmt.Errorf("condition cannot have multiple 'as' fields with the same name") } } asFound = append(asFound, ce.As) @@ -780,7 +804,7 @@ func (r *RuleParser) getConditions(conditionsInterface []interface{}) ([]engine. } } - return conditions, providers, nil + return conditions, providers, providerConditions, nil } func (r *RuleParser) getConditionForProvider(langProvider, capability string, value interface{}) (engine.Conditional, provider.InternalProviderClient, error) { @@ -845,7 +869,7 @@ func (r *RuleParser) getConditionForProvider(langProvider, capability string, va if depCondition.Upperbound == "" && depCondition.Lowerbound == "" { return nil, nil, fmt.Errorf("unable to parse dependency condition for %s (one of upperbound or lowerbound is required)", langProvider) } - + return &depCondition, client, nil } else if capability == "dependency" && r.NoDependencyRules { r.Log.V(5).Info(fmt.Sprintf("not evaluating dependency condition - %s.%s for %#v", langProvider, capability, value)) @@ -871,7 +895,7 @@ func (r *RuleParser) getConditionForProvider(langProvider, capability string, va // mergeProviderConditions stores all conditions for a given provider, used to send to providers in Prepare() func mergeProviderConditions(providerConditions map[string][]provider.ConditionsByCap, providerKey, capability string, value interface{}) (map[string][]provider.ConditionsByCap, error) { conditionInfo := struct { - Capability map[string]interface{} `yaml:",inline"` + Capability map[string]interface{} `yaml:",inline"` }{ Capability: map[string]interface{}{ capability: value, @@ -885,8 +909,8 @@ func mergeProviderConditions(providerConditions map[string][]provider.Conditions providerConditions[providerKey] = []provider.ConditionsByCap{} } providerConditions[providerKey] = append(providerConditions[providerKey], provider.ConditionsByCap{ - Cap: capability, + Cap: capability, Conditions: [][]byte{conditionInfoBytes}, }) return providerConditions, nil -} \ No newline at end of file +} diff --git a/provider/lib.go b/provider/lib.go index d71ec719..9756a6d4 100644 --- a/provider/lib.go +++ b/provider/lib.go @@ -298,6 +298,12 @@ func newCachedWalkDir() cachedWalkDir { return err } if !d.IsDir() { + // Check if it is a symlink to a directory + if d.Type()&fs.ModeSymlink != 0 { + if stat, err := os.Stat(path); err == nil && stat.IsDir() { + return nil + } + } files = append(files, path) return nil } diff --git a/rule-example.yaml b/rule-example.yaml index d128e467..4b8f991b 100644 --- a/rule-example.yaml +++ b/rule-example.yaml @@ -474,8 +474,13 @@ effort: 1 category: potential when: - nodejs.referenced: - pattern: "React" + and: + - nodejs.referenced: + pattern: "React" + - builtin.filecontent: + filePattern: "\\.tsx$" + pattern: "import.*React" + ignore: true - message: Found React reference using regex pattern ruleID: test-regex-pattern-00000 description: Test regex pattern for TypeScript/JavaScript files