diff --git a/README.md b/README.md index e76774e3..5b4f9e7a 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,6 @@ Suppress the behavior of treating a leading `!` character as negation. Returns from negate expressions the same as if they were not negated. (Ie, true on a hit, false on a miss.) - ## Comparisons to other fnmatch/glob implementations While strict compliance with the existing standards is a worthwhile diff --git a/minimatch.js b/minimatch.js index 084486f4..850ad0ee 100644 --- a/minimatch.js +++ b/minimatch.js @@ -56,7 +56,6 @@ function filter (pattern, options) { } function ext (a, b) { - a = a || {} b = b || {} const t = {} Object.keys(a).forEach(function (k) { @@ -123,9 +122,6 @@ function minimatch (p, pattern, options) { return false } - // "" only matches "" - if (pattern.trim() === '') return p === '' - return new Minimatch(pattern, options).match(p) } @@ -137,7 +133,6 @@ function Minimatch (pattern, options) { assertValidPattern(pattern) if (!options) options = {} - pattern = pattern.trim() // windows support: need to use /, not \ if (path.sep !== '/') { @@ -160,9 +155,6 @@ Minimatch.prototype.debug = function () {} Minimatch.prototype.make = make function make () { - // don't do it more than once. - if (this._made) return - var pattern = this.pattern var options = this.options @@ -182,7 +174,7 @@ function make () { // step 2: expand braces var set = this.globSet = this.braceExpand() - if (options.debug) this.debug = console.error + if (options.debug) this.debug = (...args) => console.error(...args) this.debug(this.pattern, set) @@ -304,7 +296,12 @@ function parse (pattern, isSub) { var options = this.options // shortcuts - if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '**') { + if (!options.noglobstar) + return GLOBSTAR + else + pattern = '*' + } if (pattern === '') return '' var re = '' @@ -360,7 +357,8 @@ function parse (pattern, isSub) { } switch (c) { - case '/': /* istanbul ignore next */ { + /* istanbul ignore next */ + case '/': { // completely not allowed, even escaped. // Should already be path-split by now. return false @@ -483,25 +481,23 @@ function parse (pattern, isSub) { // handle the case where we left a class open. // "[z-a]" is valid, equivalent to "\[z-a\]" - if (inClass) { - // split where the last [ was, make sure we don't have - // an invalid re. if so, re-walk the contents of the - // would-be class to re-translate any characters that - // were passed through as-is - // TODO: It would probably be faster to determine this - // without a try/catch and a new RegExp, but it's tricky - // to do safely. For now, this is safe and works. - var cs = pattern.substring(classStart + 1, i) - try { - RegExp('[' + cs + ']') - } catch (er) { - // not a valid class! - var sp = this.parse(cs, SUBPARSE) - re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' - hasMagic = hasMagic || sp[1] - inClass = false - continue - } + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue } // finish up the class. @@ -585,9 +581,7 @@ function parse (pattern, isSub) { // something that could conceivably capture a dot var addPatternStart = false switch (re.charAt(0)) { - case '.': - case '[': - case '(': addPatternStart = true + case '[': case '.': case '(': addPatternStart = true } // Hack to work around lack of negative lookbehind in JS @@ -725,16 +719,13 @@ minimatch.match = function (list, pattern, options) { return list } -Minimatch.prototype.match = match -function match (f, partial) { +Minimatch.prototype.match = function match (f) { this.debug('match', f, this.pattern) // short-circuit in the case of busted things. // comments, etc. if (this.comment) return false if (this.empty) return f === '' - if (f === '/' && partial) return true - var options = this.options // windows: need to use /, not \ @@ -768,7 +759,7 @@ function match (f, partial) { if (options.matchBase && pattern.length === 1) { file = [filename] } - var hit = this.matchOne(file, pattern, partial) + var hit = this.matchOne(file, pattern, false) if (hit) { if (options.flipNegate) return true return !this.negate diff --git a/test/basic.js b/test/basic.js index 36c6a1e5..5efa68f0 100644 --- a/test/basic.js +++ b/test/basic.js @@ -3,14 +3,14 @@ // TODO: Some of these tests do very bad things with backslashes, and will // most likely fail badly on windows. They should probably be skipped. -var tap = require('tap') -var globalBefore = Object.keys(global) -var mm = require('../') -var patterns = require('./patterns.js') -var regexps = patterns.regexps -var re = 0 - -tap.test('basic tests', function (t) { +const t = require('tap') +const globalBefore = Object.keys(global) +const mm = require('../') +const patterns = require('./patterns.js') +const regexps = patterns.regexps +let re = 0 + +t.test('basic tests', function (t) { var start = Date.now() // [ pattern, [matches], MM opts, files, TAP opts] @@ -58,7 +58,7 @@ tap.test('basic tests', function (t) { t.end() }) -tap.test('global leak test', function (t) { +t.test('global leak test', function (t) { var globalAfter = Object.keys(global).filter(function (k) { return (k !== '__coverage__' && k !== '__core-js_shared__') }) @@ -66,7 +66,7 @@ tap.test('global leak test', function (t) { t.end() }) -tap.test('invalid patterns', t => { +t.test('invalid patterns', t => { const toolong = 'x'.repeat(64 * 1024) + 'y' const expectTooLong = { message: 'pattern is too long' } t.throws(() => mm.braceExpand(toolong), expectTooLong) @@ -95,19 +95,75 @@ tap.test('invalid patterns', t => { t.end() }) -tap.test('ctor is generator', t => { +t.test('ctor is generator', t => { const m = mm.Minimatch('asdf') t.type(m, mm.Minimatch) t.equal(m.pattern, 'asdf') t.end() }) -tap.test('nocomment matches nothing', t => { +t.test('nocomment matches nothing', t => { t.equal(mm('#comment', '#comment', { nocomment: false }), false) t.equal(mm('#comment', '#comment', { nocomment: true }), true) t.end() }) +t.test('filter function', t => { + t.same(['a', 'b', 'c'].filter(mm.filter('@(a|b)')), ['a', 'b']) + t.end() +}) + +t.test('whitespace handling', t => { + t.equal(mm('x/y', 'y'), false) + t.equal(mm('x/y', 'y', { matchBase: true }), true) + t.equal(mm('x/ ', ' '), false) + t.equal(mm('x/ ', ' ', { matchBase: true }), true) + t.equal(mm('x/', ''), false) + t.equal(mm('x/', '', { matchBase: true }), false) + t.equal(mm('', ''), true) + t.equal(mm(' ', ''), false) + t.equal(mm('', ' '), false) + t.equal(mm(' ', ' '), true) + t.end() +}) + +t.test('mm debug', t => { + const { error } = console + t.teardown(() => console.error = error) + const errs = [] + console.error = (...msg) => errs.push(msg) + t.equal(mm('a/b/c', 'a/**/@(b|c)/**', { debug: true }), true) + t.not(errs.length, 0) + t.end() +}) + +t.test('makeRe repeated', t => { + const mmRE = mm.makeRe('a/*/*') + const m = new mm.Minimatch('a/*/*') + const mRE = m.makeRe() + const mRE2 = m.makeRe() + t.equal(mRE, mRE2) + t.same(mmRE, mRE) + t.end() +}) + +t.test('in noglobstar mode, ** is equivalent to *', t => { + const re2s = mm.makeRe('**', { noglobstar: true }) + const re1s = mm.makeRe('*', { noglobstar: true }) + t.same(re2s, re1s) + t.end() +}) + +t.test('flipNegate', t => { + t.equal(mm('x', '!x', { flipNegate: true }), true) + t.equal(mm('x', '!!x', { flipNegate: true }), true) + t.equal(mm('x', 'x', { flipNegate: true }), true) + + t.equal(mm('x', '!y', { flipNegate: true }), false) + t.equal(mm('x', '!!y', { flipNegate: true }), false) + t.equal(mm('x', 'y', { flipNegate: true }), false) + t.end() +}) function alpha (a, b) { return a > b ? 1 : -1 diff --git a/test/win-path-sep.js b/test/win-path-sep.js index 82f53e1e..6b2c9e66 100644 --- a/test/win-path-sep.js +++ b/test/win-path-sep.js @@ -1,5 +1,17 @@ const t = require('tap') -const mm = t.mock('../', { path: { sep: '\\' }}) +t.test('path separator \\', t => { + const mm = t.mock('../', { path: { sep: '\\' }}) -t.equal(mm('x\\y\\z', 'x/y/*/z'), false) -t.equal(mm('x\\y\\w\\z', 'x/y/*/z'), true) + t.equal(mm('x\\y\\z', 'x/y/*/z'), false) + t.equal(mm('x\\y\\w\\z', 'x/y/*/z'), true) + t.end() +}) + +// just in case Node every adds Mac OS 9 support 😅 +t.test('path separator :', t => { + const mm = t.mock('../', { path: { sep: ':' }}) + + t.equal(mm('x:y:z', 'x/y/*/z'), false) + t.equal(mm('x:y:w:z', 'x/y/*/z'), true) + t.end() +})