From 9ca19fc56b761e691c198af80db5ee89318aff50 Mon Sep 17 00:00:00 2001 From: Richardas Kuchinskas <36065987+Hidanio@users.noreply.github.com> Date: Sun, 2 Feb 2025 19:30:38 +0300 Subject: [PATCH] linter: mask filtering rules path (#1244) --- src/linter/root.go | 74 ++++++++++++++++++--- src/tests/infra/pathRulesSet_config_test.go | 58 ++++++++++++++++ 2 files changed, 124 insertions(+), 8 deletions(-) diff --git a/src/linter/root.go b/src/linter/root.go index 8f36140e..10d69a3c 100644 --- a/src/linter/root.go +++ b/src/linter/root.go @@ -1526,21 +1526,79 @@ func IsRuleEnabledForPath(root *RuleNode, filePath string, checkRule string) boo // 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 + } + AfterStarPart := parts[i] + + // 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 AfterStarPart == "" { + continue + } + + _, hasLiteral := currentNode.Children[AfterStarPart] + 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 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 := `