diff --git a/Extension/src/LanguageServer/editorConfig.ts b/Extension/src/LanguageServer/editorConfig.ts index 21a73673c6..aee592eefb 100644 --- a/Extension/src/LanguageServer/editorConfig.ts +++ b/Extension/src/LanguageServer/editorConfig.ts @@ -61,13 +61,408 @@ export function mapWrapToEditorConfig(value: string | undefined): string { return "never"; } -function matchesSection(filePath: string, section: string): boolean { - const fileName: string = path.basename(filePath); - // Escape all regex special characters except '*' and '?'. - // Convert wildcards '*' to '.*' and '?' to '.'. - const sectionPattern = section.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.'); - const regex: RegExp = new RegExp(`^${sectionPattern}$`); - return regex.test(fileName); +let isValid: boolean = true; +let relativeToCurrentDir: boolean = false; + +// Helper function to find matching '}' for a given '{' position +function findMatchingBrace(pattern: string, start: number): number { + let braceLevel = 0; + let i = start; + while (i < pattern.length) { + const c = pattern[i]; + switch (c) { + case "\\": + if (i === pattern.length - 1) { + return -1; + } + i += 2; + break; + case "{": + braceLevel++; + i++; + break; + case "}": + braceLevel--; + if (braceLevel === 0) { + return i; + } + i++; + break; + default: + i++; + break; + } + } + return -1; +} + +// Function to handle brace expansion for ranges and lists +function handleBraceExpansion(pattern: string): string { + const rangeMatch = pattern.match(/^\s*(-?\d+)\s*\.\.\s*(-?\d+)\s*$/); + if (rangeMatch) { + const [, start, end] = rangeMatch.map(Number); + return buildRangeRegex(start, end); + } + + const options = []; + let braceLevel = 0; + let currentOption = ''; + let i = 0; + while (i < pattern.length) { + const c = pattern[i]; + switch (c) { + case "\\": + if (i === pattern.length - 1) { + isValid = false; + return ""; + } + currentOption += escapeRegex(pattern[i + 1]); + i += 2; + break; + case "{": + braceLevel++; + i++; + break; + case "}": + braceLevel--; + if (braceLevel === 0) { + options.push(convertSectionToRegExp(currentOption.trim())); + currentOption = ''; + } + i++; + break; + case ",": + if (braceLevel === 0) { + options.push(convertSectionToRegExp(currentOption.trim())); + currentOption = ''; + } else { + currentOption += c; + } + i++; + break; + default: + currentOption += c; + i++; + break; + } + } + + if (currentOption) { + options.push(convertSectionToRegExp(currentOption.trim())); + } + + return `(${options.join('|')})`; +} + +function buildRangeRegex(start: number, end: number): string { + if (start === end) { + return start.toString(); + } + if (start < 0 && end < 0) { + return `-${buildRangeRegex(-end, -start)}`; + } + if (start > end) { + isValid = false; + return ""; + } + if (start > 0) { + return buildPositiveRangeRegex(start, end); + } + // Pattern to match one or more zeros only if not followed by a non-zero digit + const zeroPattern = "(0+(?![1-9]))"; + if (end === 0) { + // If end is 0, start must be negative. + const pattern = buildZeroToNRegex(-start); + return `(${zeroPattern}|(-${pattern}))`; + } + // end is >0. + const endPattern = buildZeroToNRegex(end); + if (start === 0) { + return `(${zeroPattern}|${endPattern})`; + } + const startPattern = buildZeroToNRegex(-start); + return `(${zeroPattern}|(-${startPattern})|${endPattern})`; +} + +function buildZeroToNRegex(n: number): string { + const nStr = n.toString(); + const length = nStr.length; + const parts: string[] = []; + + // Pattern to remove leading zeros when followed by a non-zero digit + const leadingZerosPattern = "(0*(?=[1-9]))"; + let prefix = ""; + + if (length > 1) { + // Handle numbers with fewer digits than `n` + + // Single-digit numbers from 0 to 9 + parts.push(`[0-9]`); + for (let i = 2; i < length; i++) { + // Multi-digit numbers with fewer digits than `n` + parts.push(`([1-9]\\d{0,${i - 1}})`); + } + + // Build the main pattern by comparing each digit position + for (let i = 0; i < length - 1; i++) { + const digit = parseInt(nStr[i]); + if (digit > 1) { + parts.push(`(${prefix}[0-${digit - 1}]${"\\d".repeat(length - i - 1)})`); + } + prefix += digit; + } + } + const digit = parseInt(nStr[length - 1]); + if (digit === 0) { + parts.push(`(${prefix}0)`); + } else { + parts.push(`(${prefix}[0-${digit}])`); + } + + // Combine everything without start and end anchors + return `(${leadingZerosPattern}(${parts.join("|")}))`; +} + +// start will be >0, end will be >start. +function buildPositiveRangeRegex(start: number, end: number): string { + const startStr = start.toString(); + const endStr = end.toString(); + const startLength = startStr.length; + const endLength = endStr.length; + const parts: string[] = []; + + // Pattern to remove leading zeros when followed by a non-zero digit + const leadingZerosPattern = "(0*(?=[1-9]))"; + + if (startLength === endLength) { + if (startLength === 1) { + return `(${leadingZerosPattern}([${startStr}-${endStr}]))`; + } + + // First, any identical leading digits are added to the prefix. + let sharedPrefix = ""; + let i = 0; + while (i < startLength && startStr[i] === endStr[i]) { + sharedPrefix += startStr[i]; + i++; + } + if (i === startLength - 1) { + // Special case for only 1 digit lefts) + parts.push(`(${sharedPrefix}[${startStr[i]}-${endStr[i]}])`); + } else { + + // Now we break the remaining digits into three parts: + // Part 1. With the new start digit, check any of the remaining against ranges to 9. + let prefix = sharedPrefix + startStr[i]; + for (let i2 = i + 1; i2 < startLength - 1; i2++) { + const startDigit = parseInt(startStr[i2]); + if (startDigit === 8) { + parts.push(`(${prefix}9${"\\d".repeat(startLength - i2 - 1)})`); + } else if (startDigit !== 9) { + parts.push(`(${prefix}[${startDigit + 1}-9]${"\\d".repeat(startLength - i2 - 1)})`); + } + prefix += startStr[i2]; + } + const startDigit = parseInt(startStr[startLength - 1]); + if (startDigit === 9) { + parts.push(`(${prefix}9)`); + } else { + parts.push(`(${prefix}[${startDigit}-9])`); + } + + // Part 2. Any larger start digit less than the end digit, should match the full range for the remaining digits. + let curDigit = parseInt(startStr[i]) + 1; + const firstEndDigit = parseInt(endStr[i]); + while (curDigit < firstEndDigit) { + parts.push(`(${sharedPrefix}${curDigit}${"\\d".repeat(startLength - i - 1)})`); + curDigit++; + } + + // Part 3. With the new end digit, check for any the remaining against ranges from 0. + prefix = sharedPrefix + endStr[i]; + for (let i2 = i + 1; i2 < endLength - 1; i2++) { + const endDigit = parseInt(endStr[i2]); + if (endDigit === 1) { + parts.push(`(${prefix}0${"\\d".repeat(endLength - i2 - 1)})`); + } else if (endDigit !== 0) { + parts.push(`(${prefix}[0-${endDigit - 1}]${"\\d".repeat(endLength - i2 - 1)})`); + } + prefix += endStr[i2]; + } + const endDigit = parseInt(endStr[endLength - 1]); + if (endDigit === 0) { + parts.push(`(${prefix}0)`); + } else { + parts.push(`(${prefix}[0-${endDigit}])`); + } + } + } else { + // endLength > startLength + + // Add patterns for numbers with the same number of digits as `start` + let startPrefix = ""; + for (let i = 0; i < startLength - 1; i++) { + const startDigit = parseInt(startStr[i]); + if (startDigit === 8) { + parts.push(`(${startPrefix}9\\d{${startLength - i - 1}})`); + } + else if (startDigit !== 9) { + parts.push(`(${startPrefix}[${startDigit + 1}-9]\\d{${startLength - i - 1}})`); + } + // if startDigit === 9, we don't need to add a pattern for this digit + startPrefix += startStr[i]; + } + const startDigit = parseInt(startStr[startLength - 1]); + if (startDigit === 9) { + parts.push(`(${startPrefix}9)`); + } else { + parts.push(`(${startPrefix}[${startDigit}-9])`); + } + + // Handle numbers with more digits than 'start' and fewer digits than 'end' + for (let i = startLength + 1; i < endLength; i++) { + // Multi-digit numbers with more digits than 'start' and fewer digits than 'end' + parts.push(`([1-9]\\d{${i - 1}})`); + } + + // Add patterns for numbers with the same number of digits as `end` + let endPrefix = ""; + for (let i = 0; i < endLength - 1; i++) { + const endDigit = parseInt(endStr[i]); + if (endDigit === 1) { + if (i !== 0) { + parts.push(`(${endPrefix}0\\d{${endLength - i - 1}})`); + } + } else if (endDigit !== 0) { + parts.push(`(${endPrefix}[0-${endDigit - 1}]\\d{${endLength - i - 1}})`); + } + // endDigit === 0, we don't need to add a pattern for this digit + endPrefix += endStr[i]; + } + const endDigit = parseInt(endStr[endLength - 1]); + if (endDigit === 0) { + parts.push(`(${endPrefix}0)`); + } else { + parts.push(`(${endPrefix}[0-${endDigit}])`); + } + } + + // Combine everything without start and end anchors + return `(${leadingZerosPattern}(${parts.join("|")}))`; +} + +// Utility to escape regex special characters in a string +function escapeRegex(str: string): string { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +function convertSectionToRegExp(pattern: string): string { + let regExp = ''; + let i = 0; + while (i < pattern.length) { + const c = pattern[i]; + switch (c) { + case '*': + if (i < pattern.length - 1 && pattern[i + 1] === '*') { + if (i > 0 && pattern[i - 1] !== '/') { + isValid = false; + return ""; + } + i++; + if (i < pattern.length - 1) { + if (pattern[i + 1] !== '/') { + isValid = false; + return ""; + } + i++; + regExp += '(?:(.*\\/)?|\\/)?'; + } + else { + regExp += '.*'; + } + } else { + regExp += '[^\\/]*'; + } + i++; + break; + case '?': + regExp += '.'; + i += 1; + break; + case '[': + const endBracket = pattern.indexOf(']', i); + if (endBracket === -1) { + isValid = false; + return ""; + } + const charClass = pattern.slice(i + 1, endBracket); + if (charClass.startsWith('!')) { + regExp += `[^${escapeRegex(charClass.slice(1))}]`; + } else { + regExp += `[${escapeRegex(charClass)}]`; + } + i = endBracket + 1; + break; + case '{': + const endBrace = findMatchingBrace(pattern, i); + if (endBrace === -1) { + isValid = false; + return ""; + } + const braceContent = pattern.slice(i + 1, endBrace); + regExp += handleBraceExpansion(braceContent); + if (!isValid) { + return ""; + } + i = endBrace + 1; + break; + case "/": + if (i === pattern.length - 1) { + isValid = false; + return ""; + } + relativeToCurrentDir = true; + regExp += '\\/'; + i++; + break; + case '\\': + if (i === pattern.length - 1) { + isValid = false; + return ""; + } + regExp += escapeRegex(pattern[i + 1]); + i += 2; + break; + default: + regExp += escapeRegex(c); + i++; + break; + } + } + return regExp; +} + +export function matchesSection(currentDir: string, filePath: string, section: string): boolean { + isValid = true; + relativeToCurrentDir = false; + const regExpString: string = `^${convertSectionToRegExp(section)}$`; + if (!isValid) { + return false; + } + const regexp: RegExp = new RegExp(regExpString); + let compareWith: string; + if (relativeToCurrentDir) { + if (!filePath.startsWith(currentDir)) { + return false; + } + compareWith = filePath.slice(currentDir.length); + if (compareWith.startsWith('/')) { + compareWith = compareWith.slice(1); + } + } else { + compareWith = path.basename(filePath); + } + return regexp.test(compareWith); } function parseEditorConfigContent(content: string): Record { @@ -140,7 +535,7 @@ function getEditorConfig(filePath: string): any { // Match sections and combine configurations. Object.keys(configData).forEach((section: string) => { - if (section !== '*' && matchesSection(filePath, section)) { + if (section !== '*' && matchesSection(currentDir, filePath, section)) { combinedConfig = { ...combinedConfig, ...configData[section] diff --git a/Extension/test/unit/matchesSection.test.ts b/Extension/test/unit/matchesSection.test.ts new file mode 100644 index 0000000000..9985b8498a --- /dev/null +++ b/Extension/test/unit/matchesSection.test.ts @@ -0,0 +1,361 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { ok } from 'assert'; +import { describe } from 'mocha'; +import { matchesSection } from '../../src/LanguageServer/editorConfig'; + +describe('Test editorConfig section pattern matching', () => { + const editorConfigPath = "/project"; + + it('matchesSection test: *', () => { + const pattern = "*"; + const filePath = "/project/subdir/file.cpp"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + }); + + it('matchesSection test: *.cpp', () => { + const pattern = "*.cpp"; + const filePath = "/project/subdir/file.cpp"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + }); + + it('matchesSection test: subdir/*.c', () => { + const pattern: string = "subdir/*.c"; + + let filePath: string = "/project/subdir/file.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/file.cpp"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/file.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/other/subdir/file.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: ????.cpp', () => { + const pattern = "????.cpp"; + + let filePath: string = "/project/subdir/file.cpp"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/file2.cpp"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/x.cpp"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: [abc].c', () => { + const pattern = "[abc].c"; + + let filePath: string = "/project/subdir/a.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/z.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: [!abc].c', () => { + const pattern = "[!abc].c"; + + let filePath: string = "/project/subdir/a.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/z.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + }); + + it('matchesSection test: test.{c, h, cpp}', () => { + const pattern = "test.{c, h, cpp}"; + + let filePath: string = "/project/subdir/test.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test.h"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test.cpp"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test.hpp"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + }); + + it('matchesSection test: test{1..3}.c', () => { + const pattern = "test{1..3}.c"; + + let filePath: string = "/project/subdir/test1.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test2.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test3.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test4.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test0.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test01.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test00.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: test{0..100}.c', () => { + const pattern = "test{0..100}.c"; + + let filePath: string = "/project/subdir/test0.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test100.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test5.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test50.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test00.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test050.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test101.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test-1.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test1000.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test500.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: test{10..1000}.c', () => { + const pattern = "test{10..1000}.c"; + + let filePath: string = "/project/subdir/test10.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test100.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test100.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test1001.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: test{0..101}.c', () => { + const pattern = "test{0..101}.c"; + + let filePath: string = "/project/subdir/test0.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test10.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test100.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test100.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test101.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test102.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: test{0..456}.c', () => { + const pattern = "test{0..456}.c"; + + let filePath: string = "/project/subdir/test0.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test400.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test450.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test456.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test457.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test460.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test500.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: test{123..456}.c', () => { + const pattern = "test{123..456}.c"; + + let filePath: string = "/project/subdir/test123.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test299.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test456.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test12.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test122.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test457.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test-123.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: test{123..456}0.c', () => { + const pattern = "test{123..456}0.c"; + + let filePath: string = "/project/subdir/test1230.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test2990.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test4560.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test12.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test120.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test1220.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test4570.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test-1230.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test123.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: test{123..45678}.c', () => { + const pattern = "test{123..45678}.c"; + + let filePath: string = "/project/subdir/test123.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test999.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test9999.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test45678.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/test12.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test45679.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test123x.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/subdir/test-9999.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: *.{c, h, cpp}', () => { + const pattern = "*.{c, h, cpp}"; + + let filePath: string = "/project/subdir/a.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/other/subdir/b.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/c.h"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/subdir/d.cpp"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/a.c/other"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: src/{test, lib}/**', () => { + const pattern = "src/{test, lib}/**"; + + let filePath: string = "/project/src/test/subdir/test.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/src/test/test.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/src/lib/test.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/src/other/test.cpp"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/other/src/test/test.c"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); + + it('matchesSection test: src/{test, lib}/**/*.{c, h, cpp}', () => { + const pattern = "src/{test, lib}/**/*.{c, h, cpp}"; + + let filePath: string = "/project/src/test/subdir/test.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/src/test/test.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/src/lib/test.c"; + ok(matchesSection(editorConfigPath, filePath, pattern), `${pattern} should match: ${filePath}`); + + filePath = "/project/src/other/test.cpp"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/other/src/test/test.hpp"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/src/test/subdir/test.hpp"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/src/test/test.hpp"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + + filePath = "/project/src/lib/test.hpp"; + ok(!matchesSection(editorConfigPath, filePath, pattern), `${pattern} should not match: ${filePath}`); + }); +});