Skip to content
Open
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
2 changes: 1 addition & 1 deletion external-providers/generic-external-provider/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions external-providers/generic-external-provider/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
76 changes: 50 additions & 26 deletions parser/rule_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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{}
Expand All @@ -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
Expand All @@ -633,48 +645,48 @@ 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"]
if ok {
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"]
if ok {
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"]
if ok {
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
Expand All @@ -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
Expand All @@ -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]

Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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) {
Expand Down Expand Up @@ -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))
Expand All @@ -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,
Expand All @@ -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
}
}
6 changes: 6 additions & 0 deletions provider/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
9 changes: 7 additions & 2 deletions rule-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading