usmanyunusov / nano-staged

Tiny tool to run commands for modified, staged, and committed files in a GIT repository.
https://npm.im/nano-staged
MIT License
468 stars 14 forks source link

glob-to-regex not correctly converting ! in combination with globstars #20

Closed Mitsunee closed 2 years ago

Mitsunee commented 2 years ago

Use case

I have a codebase with some customized svg icons and would like to automatically run *.svg through svgo, but not touch *.inkscape.svg files. With lint-staged I solved this with the pattern "**/!(*inkscape).svg".

Expected behaviour

pattern matches any string unless it contains inkscape immediatly before .svg

Actual behaviour

foo.bar.svg is not matched.

Reproduction Steps:

I wrote the following test file to simulate my use-case:

import { is } from 'uvu/assert'
import { test } from 'uvu'

import { globToRegex } from '../lib/glob-to-regex.js' //nano-staged@git

function match(glob, path, opts = {}) {
  let regex = globToRegex(glob, opts)
  return regex.regex.test(path)
}

test('mitsunee use-case', () => {
  const pattern = '**/!(*foo).txt' // match all *.txt files other than *foo.txt
  is(match(pattern, 'bar.foo.txt', { extended: true }), false, "Doesn't match *.foo.txt")
  is(match(pattern, 'bar.baz.txt', { extended: true }), true, 'Does match *.baz.txt') // FAILS
  is(match(pattern, 'bar.txt', { extended: true }), true, 'Does match *.txt') // FAILS
  is(match(pattern, 'foo.bar.txt', { extended: true }), true, 'Does match foo*.txt') // FAILS
})

test.run()

I tried adjusting the pattern by (re)moving the single star and got the same result each time.

In addition I cloned the repository of micromatch (which lint-staged uses for glob pattern matching) and wrote the following tests to confirm my existing setup works as intended (all four tests passed):

'use strict';

require('mocha');
const { equal } = require('assert');
const { isMatch } = require('..'); //micromatch@git

describe('mitsunee use-case', () => {
  const pattern = '**/!(*foo).txt';

  it("Doesn't match *.foo.txt", () => {
    equal(isMatch('bar.foo.txt', pattern), false);
  });
  it('Does match *.baz.txt', () => {
    equal(isMatch('bar.baz.txt', pattern), true);
  });
  it('Does match *.txt', () => {
    equal(isMatch('bar.txt', pattern), true);
  });
  it('Does match foo.*.txt', () => {
    equal(isMatch('foo.bar.txt', pattern), true);
  });
});
usmanyunusov commented 2 years ago

@Mitsunee I will improve the glob one of these days

usmanyunusov commented 2 years ago

@Mitsunee, in Nano Staged glob-parser using globstars options if pattern includes slash: https://github.com/usmanyunusov/nano-staged/blob/238d3eccab07c3a59ce3943fa732db8db225a512/lib/cmd-runner.js#L22

Example: nano-staged glob - https://stackblitz.com/edit/node-fdhfkh micromatch - https://stackblitz.com/edit/node-7gtp1t

Mitsunee commented 2 years ago

Looks good to me, thanks šŸ‘šŸ»