From 7c827b0fe53372e4ec24f1b4a77cd78bd5b403b1 Mon Sep 17 00:00:00 2001 From: Richardas Kuchinskas Date: Thu, 30 Jan 2025 17:10:28 +0300 Subject: [PATCH 1/4] added wildcard algorithm for rule filtering --- src/linter/root.go | 76 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/src/linter/root.go b/src/linter/root.go index 8f36140e..d80e0189 100644 --- a/src/linter/root.go +++ b/src/linter/root.go @@ -1520,27 +1520,85 @@ func (d *rootWalker) ReportPHPDoc(phpDocLocation PHPDocLocation, level int, chec func IsRuleEnabledForPath(root *RuleNode, filePath string, checkRule string) bool { normalizedPath := filepath.ToSlash(filepath.Clean(filePath)) - parts := strings.Split(normalizedPath, "/") + parts := strings.Split(normalizedPath, "/")[1:] currentNode := root // Starting with global state. We have guarantee while parsing config that rule is `on` and exist ruleState := true - for _, part := range parts { + i := 0 + for i < len(parts) { + part := parts[i] if part == "" { + i++ continue } - if node, exists := currentNode.Children[part]; exists { - if node.Disabled[checkRule] { - ruleState = false // Disable on this path + + // 1) Try to find precision node (part) + nextNode, ok := currentNode.Children[part] + if ok { + // If found precision node: apply Enabled/Disabled + if nextNode.Disabled[checkRule] { + ruleState = false } - if node.Enabled[checkRule] { - ruleState = true // Enable on this path + if nextNode.Enabled[checkRule] { + ruleState = true } - currentNode = node - } else { + currentNode = nextNode + i++ + continue + } + + // 2) If there is no exact match, lets find node "*" (wildcard) + starNode, ok := currentNode.Children["*"] + if !ok { + // not precision matching & not "*" break } + + // Apply Enabled/Disabled for "*" + if starNode.Disabled[checkRule] { + ruleState = false + } + if starNode.Enabled[checkRule] { + ruleState = true + } + + // move to node "*" + currentNode = starNode + + // Now the logic is to "swallow" several directories: + // + // Until we meet an exact match in `Children` in the next step + // (except "*"), we can continue to "eat" directories while remaining in `starNode`. + // + // Or if we have reached the end of the path, we exit the loop. + + for { + i++ + if i >= len(parts) { + // the path ended - all remaining directories were swallowed + return ruleState + } + testPart := parts[i] + + // if starNode have Children[testPart] (except "*"), + // so we found the next "literal" node, we exit the inner loop, + // to go through the usual logic at the top level. + if testPart == "" { + continue + } + + _, hasLiteral := currentNode.Children[testPart] + if hasLiteral { + // Let's exit - let the outer loop handle it + break + } + + // There are directories left - we eat them, continuing the while loop + } + // Exit to outer loop: i points to potential literal part + // (or "*"), but the outer iteration will re-check it } return ruleState From 6de9f2ee190c9e66471347f8404602ee13faa283 Mon Sep 17 00:00:00 2001 From: Richardas Kuchinskas Date: Thu, 30 Jan 2025 17:49:35 +0300 Subject: [PATCH 2/4] renamed variable --- src/linter/root.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/linter/root.go b/src/linter/root.go index d80e0189..8a83b318 100644 --- a/src/linter/root.go +++ b/src/linter/root.go @@ -1580,16 +1580,16 @@ func IsRuleEnabledForPath(root *RuleNode, filePath string, checkRule string) boo // the path ended - all remaining directories were swallowed return ruleState } - testPart := parts[i] + AfterStarPart := parts[i] - // if starNode have Children[testPart] (except "*"), + // if starNode have Children[AfterStarPart] (except "*"), // so we found the next "literal" node, we exit the inner loop, // to go through the usual logic at the top level. - if testPart == "" { + if AfterStarPart == "" { continue } - _, hasLiteral := currentNode.Children[testPart] + _, hasLiteral := currentNode.Children[AfterStarPart] if hasLiteral { // Let's exit - let the outer loop handle it break From cdc029aa78eadca5bf8df41a823e67ce08192702 Mon Sep 17 00:00:00 2001 From: Richardas Kuchinskas Date: Fri, 31 Jan 2025 16:39:39 +0300 Subject: [PATCH 3/4] empty part fix --- src/linter/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/linter/root.go b/src/linter/root.go index 8a83b318..10d69a3c 100644 --- a/src/linter/root.go +++ b/src/linter/root.go @@ -1520,7 +1520,7 @@ func (d *rootWalker) ReportPHPDoc(phpDocLocation PHPDocLocation, level int, chec func IsRuleEnabledForPath(root *RuleNode, filePath string, checkRule string) bool { normalizedPath := filepath.ToSlash(filepath.Clean(filePath)) - parts := strings.Split(normalizedPath, "/")[1:] + parts := strings.Split(normalizedPath, "/") currentNode := root // Starting with global state. We have guarantee while parsing config that rule is `on` and exist From 4fe6e2cf5ba1989f82e5bd4b968e459dd5775759 Mon Sep 17 00:00:00 2001 From: Richardas Kuchinskas Date: Fri, 31 Jan 2025 16:53:04 +0300 Subject: [PATCH 4/4] tests --- src/tests/infra/pathRulesSet_config_test.go | 58 +++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/tests/infra/pathRulesSet_config_test.go b/src/tests/infra/pathRulesSet_config_test.go index df027b21..3632db57 100644 --- a/src/tests/infra/pathRulesSet_config_test.go +++ b/src/tests/infra/pathRulesSet_config_test.go @@ -32,6 +32,20 @@ func pathRulesSetInit(t *testing.T) *linttest.Suite { "undefinedFunction": true, }, }, + "star/*/tests": { + Enabled: map[string]bool{ + "emptyStmt": true, + }, + Disabled: map[string]bool{}, + }, + "mixed/*/tests": { + Enabled: map[string]bool{ + "emptyStmt": true, + }, + Disabled: map[string]bool{ + "undefinedFunction": true, + }, + }, }) var suite = linttest.NewSuite(t) @@ -63,6 +77,50 @@ require_once 'foo.php';; test.RunAndMatch() } +func TestStarPath(t *testing.T) { + test := pathRulesSetInit(t) + code := `