From ca6d82ae805b899794591f610a8cab7a82105e3c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 5 Apr 2026 09:46:12 +0000 Subject: [PATCH 1/3] fix: escape regex metacharacters in glob exclude patterns The isExcluded() method in codeLensProvider.ts converted glob patterns to regular expressions but did not escape regex metacharacters (. + ? ^ $ etc.) present in the literal parts of the pattern. As a result a pattern like `**/*.min.js` compiled to `^.*/[^/]*.[^/]*.[^/]*$` where each `.` acted as a wildcard instead of matching a literal dot. This meant a file such as `fooXminXjs` would incorrectly be excluded. Fix: save `**` and `*` wildcards as null-byte placeholders before calling the standard regex escape (replace special chars with their escaped form), then restore the placeholders as their intended regex tokens. This ensures only the wildcard tokens are special; all other characters are matched literally. Also correct the JSDoc on UnifiedFunctionMetrics.startLine / endLine from "1-based" to "0-based", matching the actual values returned by all language analyzers (and confirmed by the unit test assertion on line 610 of csharpAnalyzer.test.ts). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/metricsAnalyzer/metricsAnalyzerFactory.ts | 4 ++-- src/providers/codeLensProvider.ts | 20 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/metricsAnalyzer/metricsAnalyzerFactory.ts b/src/metricsAnalyzer/metricsAnalyzerFactory.ts index 0f40076..51b2662 100644 --- a/src/metricsAnalyzer/metricsAnalyzerFactory.ts +++ b/src/metricsAnalyzer/metricsAnalyzerFactory.ts @@ -42,9 +42,9 @@ export interface UnifiedFunctionMetrics { complexity: number; /** Array of individual complexity details that contribute to the total score */ details: UnifiedMetricsDetail[]; - /** Line number where the function definition starts (1-based) */ + /** Line number where the function definition starts (0-based) */ startLine: number; - /** Line number where the function definition ends (1-based) */ + /** Line number where the function definition ends (0-based) */ endLine: number; /** Column number where the function definition starts (0-based) */ startColumn: number; diff --git a/src/providers/codeLensProvider.ts b/src/providers/codeLensProvider.ts index 91917a4..9f633be 100644 --- a/src/providers/codeLensProvider.ts +++ b/src/providers/codeLensProvider.ts @@ -70,18 +70,26 @@ export class MetricsCodeLensProvider implements vscode.CodeLensProvider { const hasPathSeparators = normalizedPattern.includes("/"); if (hasPathSeparators) { - // Pattern contains path separators - match against full path + // Pattern contains path separators - match against full path. + // Step 1: preserve wildcards as null-byte placeholders so they survive + // regex escaping; Step 2: escape regex metacharacters in the + // literal portions; Step 3: restore wildcards as regex tokens. const regexPattern = normalizedPattern - .replace(/\*\*/g, "___DOUBLESTAR___") // Temporary placeholder - .replace(/\*/g, "[^/]*") // Single * matches within directory - .replace(/___DOUBLESTAR___/g, ".*"); // ** matches across directories + .replace(/\*\*/g, "\x00DS\x00") // placeholder for ** + .replace(/\*/g, "\x00S\x00") // placeholder for * + .replace(/[.+?^${}()|[\]\\]/g, "\\$&") // escape regex metacharacters + .replace(/\x00DS\x00/g, ".*") // ** matches across directories + .replace(/\x00S\x00/g, "[^/]*"); // single * matches within directory const regex = new RegExp(`^${regexPattern}$`); return regex.test(normalizedPath); } else { - // Pattern has no path separators - match against filename only + // Pattern has no path separators - match against filename only. const filename = normalizedPath.split("/").pop() || ""; - const regexPattern = normalizedPattern.replace(/\*/g, ".*"); // * matches any characters in filename + const regexPattern = normalizedPattern + .replace(/\*/g, "\x00S\x00") // placeholder for * + .replace(/[.+?^${}()|[\]\\]/g, "\\$&") // escape regex metacharacters + .replace(/\x00S\x00/g, ".*"); // * matches any characters in filename const regex = new RegExp(`^${regexPattern}$`); return regex.test(filename); From d1bfbd8ad4fbc104a0849f638790d0a2378f5d7b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 13:39:46 +0000 Subject: [PATCH 2/3] test: add unit tests for regex metacharacter escaping in glob patterns Agent-Logs-Url: https://github.com/askpt/code-metrics/sessions/0a12dec2-17a9-4391-9323-587776d4a82c Co-authored-by: askpt <2493377+askpt@users.noreply.github.com> --- src/test/providers/codeLensProvider.test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/test/providers/codeLensProvider.test.ts b/src/test/providers/codeLensProvider.test.ts index 4aceb62..5381fa2 100644 --- a/src/test/providers/codeLensProvider.test.ts +++ b/src/test/providers/codeLensProvider.test.ts @@ -320,6 +320,27 @@ suite("Metrics Code Lens Provider Tests", () => { }, { pattern: "test*", path: "/path/test.cs", shouldExclude: true }, { pattern: "test*", path: "/path/src.cs", shouldExclude: false }, + // Regex metacharacter escaping: literal dots must not act as wildcards + { + pattern: "**/*.min.js", + path: "/path/to/foo.min.js", + shouldExclude: true, + }, + { + pattern: "**/*.min.js", + path: "/path/to/fooXminXjs", + shouldExclude: false, + }, + { + pattern: "**/*.spec.*", + path: "/path/to/app.spec.ts", + shouldExclude: true, + }, + { + pattern: "**/*.spec.*", + path: "/path/to/appXspecXts", + shouldExclude: false, + }, ]; for (const testCase of testCases) { From 5f57546aa952d0523de7e73328db5a6c31b6a2b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 15:02:41 +0000 Subject: [PATCH 3/3] test: add filename-only branch coverage for regex metacharacter escaping Agent-Logs-Url: https://github.com/askpt/code-metrics/sessions/27972a80-bcb9-4394-b0f0-89773ca29bbe Co-authored-by: askpt <2493377+askpt@users.noreply.github.com> --- src/test/providers/codeLensProvider.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test/providers/codeLensProvider.test.ts b/src/test/providers/codeLensProvider.test.ts index 5381fa2..cd333ec 100644 --- a/src/test/providers/codeLensProvider.test.ts +++ b/src/test/providers/codeLensProvider.test.ts @@ -321,6 +321,7 @@ suite("Metrics Code Lens Provider Tests", () => { { pattern: "test*", path: "/path/test.cs", shouldExclude: true }, { pattern: "test*", path: "/path/src.cs", shouldExclude: false }, // Regex metacharacter escaping: literal dots must not act as wildcards + // Full-path branch (pattern contains a path separator) { pattern: "**/*.min.js", path: "/path/to/foo.min.js", @@ -341,6 +342,22 @@ suite("Metrics Code Lens Provider Tests", () => { path: "/path/to/appXspecXts", shouldExclude: false, }, + // Filename-only branch (pattern has no path separator) + { + pattern: "*.generated.*", + path: "/path/fooXgeneratedXcs", + shouldExclude: false, + }, + { + pattern: "*.min.js", + path: "/path/foo.min.js", + shouldExclude: true, + }, + { + pattern: "*.min.js", + path: "/path/fooXminXjs", + shouldExclude: false, + }, ]; for (const testCase of testCases) {