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); diff --git a/src/test/providers/codeLensProvider.test.ts b/src/test/providers/codeLensProvider.test.ts index 4aceb62..cd333ec 100644 --- a/src/test/providers/codeLensProvider.test.ts +++ b/src/test/providers/codeLensProvider.test.ts @@ -320,6 +320,44 @@ 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", + 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, + }, + // 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) {