neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

glob_spec.lua (11045B)


      1 local t = require('test.testutil')
      2 
      3 local eq = t.eq
      4 
      5 describe('glob', function()
      6  local match = function(pattern, str)
      7    return require('vim.glob').to_lpeg(pattern):match(str) ~= nil
      8  end
      9 
     10  describe('glob matching', function()
     11    it('should match literal strings', function()
     12      eq(true, match('', ''))
     13      eq(false, match('', 'a'))
     14      eq(true, match('a', 'a'))
     15      eq(true, match('.', '.'))
     16      eq(true, match('/', '/'))
     17      eq(true, match('abc', 'abc'))
     18      eq(false, match('abc', 'abcdef'))
     19      eq(false, match('abc', 'a'))
     20      eq(false, match('abc', 'bc'))
     21      eq(false, match('a', 'b'))
     22      eq(false, match('.', 'a'))
     23      eq(true, match('$', '$'))
     24      eq(true, match('a,b', 'a,b'))
     25      eq(true, match('/dir', '/dir'))
     26      eq(true, match('dir/', 'dir/'))
     27      eq(true, match('dir/subdir', 'dir/subdir'))
     28      eq(false, match('dir/subdir', 'subdir'))
     29      eq(false, match('dir/subdir', 'dir/subdir/file'))
     30      eq(true, match('🤠', '🤠'))
     31    end)
     32 
     33    it('should match * wildcards', function()
     34      eq(true, match('*', ''))
     35      eq(true, match('*', '   '))
     36      eq(true, match('*', 'a'))
     37      eq(false, match('*', '/'))
     38      eq(false, match('*', '/a'))
     39      eq(false, match('*', 'a/'))
     40      eq(true, match('*', 'aaa'))
     41      eq(true, match('*a', 'aa'))
     42      eq(true, match('*a', 'abca'))
     43      eq(true, match('*.ts', '.ts'))
     44      eq(true, match('*.txt', 'file.txt'))
     45      eq(false, match('*.txt', 'file.txtxt'))
     46      eq(false, match('*.txt', 'dir/file.txt'))
     47      eq(false, match('*.txt', '/dir/file.txt'))
     48      eq(false, match('*.txt', 'C:/dir/file.txt'))
     49      eq(false, match('*.dir', 'test.dir/file'))
     50      eq(true, match('file.*', 'file.txt'))
     51      eq(false, match('file.*', 'not-file.txt'))
     52      eq(true, match('*/file.txt', 'dir/file.txt'))
     53      eq(false, match('*/file.txt', 'dir/subdir/file.txt'))
     54      eq(false, match('*/file.txt', '/dir/file.txt'))
     55      eq(true, match('dir/*', 'dir/file.txt'))
     56      eq(false, match('dir/*', 'dir'))
     57      eq(false, match('dir/*.txt', 'file.txt'))
     58      eq(true, match('dir/*.txt', 'dir/file.txt'))
     59      eq(false, match('dir/*.txt', 'dir/subdir/file.txt'))
     60      eq(false, match('dir/*/file.txt', 'dir/file.txt'))
     61      eq(true, match('dir/*/file.txt', 'dir/subdir/file.txt'))
     62      eq(false, match('dir/*/file.txt', 'dir/subdir/subdir/file.txt'))
     63      eq(true, match('a*b*c*d*e*', 'axbxcxdxe'))
     64      eq(true, match('a*b*c*d*e*', 'axbxcxdxexxx'))
     65      eq(true, match('a*b?c*x', 'abxbbxdbxebxczzx'))
     66      eq(false, match('a*b?c*x', 'abxbbxdbxebxczzy'))
     67      eq(true, match('a*b*[cy]*d*e*', 'axbxcxdxexxx'))
     68      eq(true, match('a*b*[cy]*d*e*', 'axbxyxdxexxx'))
     69      eq(true, match('a*b*[cy]*d*e*', 'axbxxxyxdxexxx'))
     70      eq(true, match('.ps*1', '.ps1'))
     71      eq(true, match('.ps*1', '.psaa1'))
     72      eq(false, match('.ps*1', '.ps1a'))
     73    end)
     74 
     75    it('should match ? wildcards', function()
     76      eq(false, match('?', ''))
     77      eq(true, match('?', 'a'))
     78      eq(false, match('??', 'a'))
     79      eq(false, match('?', 'ab'))
     80      eq(true, match('??', 'ab'))
     81      eq(true, match('a?c', 'abc'))
     82      eq(false, match('a?c', 'a/c'))
     83      eq(false, match('a/', 'a/.b'))
     84      eq(true, match('?/?', 'a/b'))
     85      eq(true, match('/??', '/ab'))
     86      eq(true, match('/?b', '/ab'))
     87      eq(false, match('foo?bar', 'foo/bar'))
     88    end)
     89 
     90    it('should match ** wildcards', function()
     91      eq(true, match('**', ''))
     92      eq(true, match('**', 'a'))
     93      eq(true, match('**', '/'))
     94      eq(true, match('**', 'a/'))
     95      eq(true, match('**', '/a'))
     96      eq(true, match('**', 'C:/a'))
     97      eq(true, match('**', 'a/a'))
     98      eq(true, match('**', 'a/a/a'))
     99      eq(false, match('/**', '')) -- /** matches leading / literally
    100      eq(true, match('/**', '/'))
    101      eq(true, match('/**', '/a/b/c'))
    102      eq(true, match('**/', '')) -- **/ absorbs trailing /
    103      eq(false, match('**/', '/a/b/c'))
    104      eq(true, match('**/**', ''))
    105      eq(true, match('**/**', 'a'))
    106      eq(false, match('a/**', ''))
    107      eq(false, match('a/**', 'a'))
    108      eq(true, match('a/**', 'a/b'))
    109      eq(true, match('a/**', 'a/b/c'))
    110      eq(false, match('a/**', 'b/a'))
    111      eq(false, match('a/**', '/a'))
    112      eq(false, match('**/a', ''))
    113      eq(true, match('**/a', 'a'))
    114      eq(false, match('**/a', 'a/b'))
    115      eq(true, match('**/a', '/a'))
    116      eq(true, match('**/a', '/b/a'))
    117      eq(true, match('**/a', '/c/b/a'))
    118      eq(true, match('**/a', '/a/a'))
    119      eq(true, match('**/a', '/abc/a'))
    120      eq(false, match('a/**/c', 'a'))
    121      eq(false, match('a/**/c', 'c'))
    122      eq(true, match('a/**/c', 'a/c'))
    123      eq(true, match('a/**/c', 'a/b/c'))
    124      eq(true, match('a/**/c', 'a/b/b/c'))
    125      eq(false, match('**/a/**', 'a'))
    126      eq(true, match('**/a/**', 'a/'))
    127      eq(false, match('**/a/**', '/dir/a'))
    128      eq(false, match('**/a/**', 'dir/a'))
    129      eq(true, match('**/a/**', 'dir/a/'))
    130      eq(true, match('**/a/**', 'a/dir'))
    131      eq(true, match('**/a/**', 'dir/a/dir'))
    132      eq(true, match('**/a/**', '/a/dir'))
    133      eq(true, match('**/a/**', 'C:/a/dir'))
    134      eq(false, match('**/a/**', 'a.txt'))
    135    end)
    136 
    137    it('should match {} groups', function()
    138      eq(true, match('{,}', ''))
    139      eq(true, match('{a,}', ''))
    140      eq(true, match('{a,}', 'a'))
    141      eq(true, match('{a,b}', 'a'))
    142      eq(true, match('{a,b}', 'b'))
    143      eq(false, match('{a,b}', 'ab'))
    144      eq(true, match('{ab,cd}', 'ab'))
    145      eq(false, match('{ab,cd}', 'a'))
    146      eq(true, match('{ab,cd}', 'cd'))
    147      eq(true, match('{a,b,c}', 'c'))
    148      eq(true, match('{a,{b,c}}', 'c'))
    149      eq(true, match('a{,/}*.txt', 'a.txt'))
    150      eq(true, match('a{,/}*.txt', 'ab.txt'))
    151      eq(true, match('a{,/}*.txt', 'a/b.txt'))
    152      eq(true, match('a{,/}*.txt', 'a/ab.txt'))
    153      eq(true, match('a/{a{a,b},b}', 'a/aa'))
    154      eq(true, match('a/{a{a,b},b}', 'a/ab'))
    155      eq(false, match('a/{a{a,b},b}', 'a/ac'))
    156      eq(true, match('a/{a{a,b},b}', 'a/b'))
    157      eq(false, match('a/{a{a,b},b}', 'a/c'))
    158      eq(true, match('foo{bar,b*z}', 'foobar'))
    159      eq(true, match('foo{bar,b*z}', 'foobuzz'))
    160      eq(true, match('foo{bar,b*z}', 'foobarz'))
    161      eq(true, match('{a,b}/c/{d,e}/**/*est.ts', 'a/c/d/one/two/three.test.ts'))
    162      eq(true, match('{a,{d,e}b}/c', 'a/c'))
    163      eq(true, match('{**/a,**/b}', 'b'))
    164    end)
    165 
    166    it('should match [] groups', function()
    167      eq(true, match('[]', '[]')) -- empty [] is a literal
    168      eq(false, match('[a-z]', ''))
    169      eq(true, match('[a-z]', 'a'))
    170      eq(false, match('[a-z]', 'ab'))
    171      eq(true, match('[a-z]', 'z'))
    172      eq(true, match('[a-z]', 'j'))
    173      eq(false, match('[a-f]', 'j'))
    174      eq(false, match('[a-z]', '`')) -- 'a' - 1
    175      eq(false, match('[a-z]', '{')) -- 'z' + 1
    176      eq(false, match('[a-z]', 'A'))
    177      eq(false, match('[a-z]', '5'))
    178      eq(true, match('[A-Z]', 'A'))
    179      eq(true, match('[A-Z]', 'Z'))
    180      eq(true, match('[A-Z]', 'J'))
    181      eq(false, match('[A-Z]', '@')) -- 'A' - 1
    182      eq(false, match('[A-Z]', '[')) -- 'Z' + 1
    183      eq(false, match('[A-Z]', 'a'))
    184      eq(false, match('[A-Z]', '5'))
    185      eq(true, match('[a-zA-Z0-9]', 'z'))
    186      eq(true, match('[a-zA-Z0-9]', 'Z'))
    187      eq(true, match('[a-zA-Z0-9]', '9'))
    188      eq(false, match('[a-zA-Z0-9]', '&'))
    189      eq(true, match('[?]', '?'))
    190      eq(false, match('[?]', 'a'))
    191      eq(true, match('[*]', '*'))
    192      eq(false, match('[*]', 'a'))
    193      eq(true, match('[\\!]', '!'))
    194      eq(true, match('a\\*b', 'a*b'))
    195      eq(false, match('a\\*b', 'axb'))
    196    end)
    197 
    198    it('should match [!...] groups', function()
    199      eq(true, match('[!]', '[!]')) -- [!] is a literal
    200      eq(false, match('[!a-z]', ''))
    201      eq(false, match('[!a-z]', 'a'))
    202      eq(false, match('[!a-z]', 'z'))
    203      eq(false, match('[!a-z]', 'j'))
    204      eq(true, match('[!a-f]', 'j'))
    205      eq(false, match('[!a-f]', 'jj'))
    206      eq(true, match('[!a-z]', '`')) -- 'a' - 1
    207      eq(true, match('[!a-z]', '{')) -- 'z' + 1
    208      eq(false, match('[!a-zA-Z0-9]', 'a'))
    209      eq(false, match('[!a-zA-Z0-9]', 'A'))
    210      eq(false, match('[!a-zA-Z0-9]', '0'))
    211      eq(true, match('[!a-zA-Z0-9]', '!'))
    212    end)
    213 
    214    it('should handle long patterns', function()
    215      -- lpeg has a recursion limit of 200 by default, make sure the grammar does trigger it on
    216      -- strings longer than that
    217      local fill_200 = ('a'):rep(200)
    218      eq(200, fill_200:len())
    219      local long_lit = fill_200 .. 'a'
    220      eq(false, match(long_lit, 'b'))
    221      eq(true, match(long_lit, long_lit))
    222      local long_pat = fill_200 .. 'a/**/*.c'
    223      eq(true, match(long_pat, fill_200 .. 'a/b/c/d.c'))
    224    end)
    225 
    226    -- New test for unicode patterns from assets
    227    it('should match unicode patterns', function()
    228      eq(true, match('😎/¢£.{ts,tsx,js,jsx}', '😎/¢£.ts'))
    229      eq(true, match('😎/¢£.{ts,tsx,js,jsx}', '😎/¢£.tsx'))
    230      eq(true, match('😎/¢£.{ts,tsx,js,jsx}', '😎/¢£.js'))
    231      eq(true, match('😎/¢£.{ts,tsx,js,jsx}', '😎/¢£.jsx'))
    232      eq(false, match('😎/¢£.{ts,tsx,js,jsx}', '😎/¢£.jsxxxxxxxx'))
    233      eq(true, match('*é*', 'café noir'))
    234      eq(true, match('caf*noir', 'café noir'))
    235      eq(true, match('caf*noir', 'cafeenoir'))
    236      eq(true, match('F[ë£a]', 'Fë'))
    237      eq(true, match('F[ë£a]', 'F£'))
    238      eq(true, match('F[ë£a]', 'Fa'))
    239    end)
    240 
    241    it('should match complex patterns', function()
    242      eq(false, match('**/*.{c,h}', ''))
    243      eq(false, match('**/*.{c,h}', 'c'))
    244      eq(false, match('**/*.{c,h}', 'file.m'))
    245      eq(true, match('**/*.{c,h}', 'file.c'))
    246      eq(true, match('**/*.{c,h}', 'file.h'))
    247      eq(true, match('**/*.{c,h}', '/file.c'))
    248      eq(true, match('**/*.{c,h}', 'dir/subdir/file.c'))
    249      eq(true, match('**/*.{c,h}', 'dir/subdir/file.h'))
    250      eq(true, match('**/*.{c,h}', '/dir/subdir/file.c'))
    251      eq(true, match('**/*.{c,h}', 'C:/dir/subdir/file.c'))
    252      eq(true, match('/dir/**/*.{c,h}', '/dir/file.c'))
    253      eq(false, match('/dir/**/*.{c,h}', 'dir/file.c'))
    254      eq(true, match('/dir/**/*.{c,h}', '/dir/subdir/subdir/file.c'))
    255 
    256      eq(true, match('{[0-9],[a-z]}', '0'))
    257      eq(true, match('{[0-9],[a-z]}', 'a'))
    258      eq(false, match('{[0-9],[a-z]}', 'A'))
    259 
    260      -- glob is from willRename filter in typescript-language-server
    261      -- https://github.com/typescript-language-server/typescript-language-server/blob/b224b878652438bcdd639137a6b1d1a6630129e4/src/lsp-server.ts#L266
    262      eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.js'))
    263      eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.ts'))
    264      eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.mts'))
    265      eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.mjs'))
    266      eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.cjs'))
    267      eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.cts'))
    268      eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.jsx'))
    269      eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.tsx'))
    270    end)
    271  end)
    272 end)