Skip to content

Commit

Permalink
improve matching
Browse files Browse the repository at this point in the history
matches against escaped characters and edge cases are more accurate with these changes.
  • Loading branch information
jonschlinkert committed Dec 11, 2016
1 parent da40c38 commit 44d33de
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 67 deletions.
29 changes: 25 additions & 4 deletions index.js
Expand Up @@ -6,20 +6,41 @@
*/

var isExtglob = require('is-extglob');
var chars = { '{': '}', '(': ')', '[': ']'};

module.exports = function isGlob(str) {
module.exports = function isGlob(str, options) {
if (typeof str !== 'string' || str === '') {
return false;
}

if (isExtglob(str)) return true;
if (isExtglob(str)) {
return true;
}

var regex = /(\\).|([*?]|\[.*\]|\{.*\}|\(.*\|.*\)|^!)/;
var regex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/;
var match;

// optionally relax regex
if (options && options.strict === false) {
regex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/;
}

while ((match = regex.exec(str))) {
if (match[2]) return true;
str = str.slice(match.index + match[0].length);
var idx = match.index + match[0].length;

// if an open bracket/brace/paren is escaped,
// set the index to the next closing character
var open = match[1];
var close = open ? chars[open] : null;
if (open && close) {
var n = str.indexOf(close, idx);
if (n !== -1) {
idx = n + 1;
}
}

str = str.slice(idx);
}
return false;
};
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -24,7 +24,7 @@
"test": "mocha"
},
"dependencies": {
"is-extglob": "^2.1.0"
"is-extglob": "^2.1.1"
},
"devDependencies": {
"gulp-format-md": "^0.1.10",
Expand Down
158 changes: 96 additions & 62 deletions test.js
Expand Up @@ -13,13 +13,16 @@ var isGlob = require('./');

describe('isGlob', function() {
describe('glob patterns', function() {
it('should return `true` if it is a glob pattern:', function() {
it('should be true if it is a glob pattern:', function() {
assert(!isGlob('@.(?abc)'), 'invalid pattern');
assert(isGlob('*.js'));
assert(isGlob('!*.js'));
assert(isGlob('!foo'));
assert(isGlob('!foo.js'));
assert(isGlob('**/abc.js'));
assert(isGlob('abc/*.js'));
assert(isGlob('@.(?:abc)'));
assert(isGlob('@.(?!abc)'));
});

it('should not match escaped globs', function() {
Expand All @@ -32,14 +35,14 @@ describe('isGlob', function() {
assert(!isGlob('abc/\\*.js'));
});

it('should return `false` if the value is not a string:', function() {
it('should be false if the value is not a string:', function() {
assert(!isGlob());
assert(!isGlob(null));
assert(!isGlob(['**/*.js']));
assert(!isGlob(['foo.js']));
});

it('should return `false` if it is not a glob pattern:', function() {
it('should be false if it is not a glob pattern:', function() {
assert(!isGlob(''));
assert(!isGlob('~/abc'));
assert(!isGlob('~/abc'));
Expand All @@ -48,167 +51,198 @@ describe('isGlob', function() {
assert(!isGlob('.'));
assert(!isGlob('@.(abc)'));
assert(!isGlob('aa'));
assert(!isGlob('who?'));
assert(!isGlob('why!?'));
assert(!isGlob('where???'));
assert(!isGlob('abc!/def/!ghi.js'));
assert(!isGlob('abc.js'));
assert(!isGlob('abc/def/!ghi.js'));
assert(!isGlob('abc/def/ghi.js'));
assert(isGlob('@.(?abc)'));
assert(isGlob('@.(?:abc)'));
assert(isGlob('@.(?!abc)'));
});
});

describe('regex capture groups', function() {
it('should return `true` if the path has a capture group (parens):', function() {
it('should be true if the path has a regex capture group:', function() {
assert(isGlob('abc/(?!foo).js'));
assert(isGlob('abc/(?:foo).js'));
assert(isGlob('abc/(?=foo).js'));
assert(isGlob('abc/(a|b).js'));
assert(isGlob('abc/(a|b|c).js'));
assert(isGlob('abc/(foo bar)/*.js'), 'not a capture group but has a glob');
});

assert(!isGlob('abc/(abc).js'), 'not a capture group');
assert(!isGlob('abc/(foo bar).js'), 'not a capture group');
assert(isGlob('abc/(foo bar)/*.js'), 'not a capture group');
it('should be true if the path has parens but is not a valid capture group', function() {
assert(!isGlob('abc/(?foo).js'), 'invalid capture group');
assert(!isGlob('abc/(a b c).js'), 'unlikely to be a capture group');
assert(!isGlob('abc/(ab).js'), 'unlikely to be a capture group');
assert(!isGlob('abc/(abc).js'), 'unlikely to be a capture group');
assert(!isGlob('abc/(foo bar).js'), 'unlikely to be a capture group');
});

it('should return `false` if the group is not balanced:', function() {
it('should be false if the capture group is imbalanced:', function() {
assert(!isGlob('abc/(?ab.js'));
assert(!isGlob('abc/(ab.js'));
assert(!isGlob('abc/(a|b.js'));
assert(!isGlob('abc/(a|b|c.js'));
});

it('should return `false` if the group is escaped:', function() {
it('should be false if the group is escaped:', function() {
assert(!isGlob('abc/\\(a|b).js'));
assert(!isGlob('abc/\\(a|b|c).js'));
});

it('should be true if glob chars exist and `options.strict` is false', function() {
assert(isGlob('$(abc)', {strict: false}));
assert(isGlob('&(abc)', {strict: false}));
assert(isGlob('? (abc)', {strict: false}));
assert(isGlob('?.js', {strict: false}));
assert(isGlob('abc/(?ab.js', {strict: false}));
assert(isGlob('abc/(ab.js', {strict: false}));
assert(isGlob('abc/(a|b.js', {strict: false}));
assert(isGlob('abc/(a|b|c.js', {strict: false}));
assert(isGlob('abc/(foo).js', {strict: false}));
assert(isGlob('abc/?.js', {strict: false}));
assert(isGlob('abc/[1-3.js', {strict: false}));
assert(isGlob('abc/[^abc.js', {strict: false}));
assert(isGlob('abc/[abc.js', {strict: false}));
assert(isGlob('abc/foo?.js', {strict: false}));
assert(isGlob('abc/{abc.js', {strict: false}));
assert(isGlob('Who?.js', {strict: false}));
});

it('should be false if the first delim is escaped and options.strict is false:', function() {
assert(!isGlob('abc/\\(a|b).js', {strict: false}));
assert(!isGlob('abc/(a|b\\).js'));
assert(!isGlob('abc/\\(a|b|c).js', {strict: false}));
assert(!isGlob('abc/\\(a|b|c.js', {strict: false}));
assert(!isGlob('abc/\\[abc].js', {strict: false}));
assert(!isGlob('abc/\\[abc.js', {strict: false}));

assert(isGlob('abc/(a|b\\).js', {strict: false}));
});
});

describe('regex character classes', function() {
it('should return `true` if the path has a regex character class:', function() {
it('should be true if the path has a regex character class:', function() {
assert(isGlob('abc/[abc].js'));
assert(isGlob('abc/[^abc].js'));
assert(isGlob('abc/[1-3].js'));
});

it('should return `false` if the character class is not balanced:', function() {
it('should be false if the character class is not balanced:', function() {
assert(!isGlob('abc/[abc.js'));
assert(!isGlob('abc/[^abc.js'));
assert(!isGlob('abc/[1-3.js'));
});

it('should return `false` if the character class is escaped:', function() {
it('should be false if the character class is escaped:', function() {
assert(!isGlob('abc/\\[abc].js'));
assert(!isGlob('abc/\\[^abc].js'));
assert(!isGlob('abc/\\[1-3].js'));
});
});

describe('brace patterns', function() {
it('should return `true` if the path has brace characters:', function() {
it('should be true if the path has brace characters:', function() {
assert(isGlob('abc/{a,b}.js'));
assert(isGlob('abc/{a..z}.js'));
assert(isGlob('abc/{a..z..2}.js'));
});

it('should return `false` if (basic) braces are not balanced:', function() {
it('should be false if (basic) braces are not balanced:', function() {
assert(!isGlob('abc/\\{a,b}.js'));
assert(!isGlob('abc/\\{a..z}.js'));
assert(!isGlob('abc/\\{a..z..2}.js'));
});
});

describe('regex patterns', function() {
it('should return `true` if the path has regex characters:', function() {
it('should be true if the path has regex characters:', function() {
assert(!isGlob('$(abc)'));
assert(!isGlob('&(abc)'));
assert(!isGlob('Who?.js'));
assert(!isGlob('? (abc)'));
assert(!isGlob('?.js'));
assert(!isGlob('abc/?.js'));

assert(isGlob('!&(abc)'));
assert(isGlob('!*.js'));
assert(isGlob('!foo'));
assert(isGlob('!foo.js'));
assert(isGlob('**/abc.js'));
assert(isGlob('*.js'));
assert(isGlob('*z(abc)'));
assert(isGlob('? (abc)'));
assert(isGlob('?.js'));
assert(isGlob('[1-10].js'));
assert(isGlob('[^abc].js'));
assert(isGlob('[a-j]*[^c]b/c'));
assert(isGlob('[abc].js'));
assert(isGlob('a/b/c/[a-z].js'));
assert(isGlob('abc/(aaa|bbb).js'));
assert(isGlob('abc/*.js'));
assert(isGlob('abc/?.js'));
assert(isGlob('abc/{a,b}.js'));
assert(isGlob('abc/{a..z..2}.js'));
assert(isGlob('abc/{a..z}.js'));
});

it('should return `false` if regex characters are escaped', function() {
assert(!isGlob('abc/\\(aaa|bbb).js'));
assert(!isGlob('abc/\\?.js'));
it('should be false if regex characters are escaped', function() {
assert(!isGlob('\\?.js'));
assert(!isGlob('\\[abc\\].js'));
assert(!isGlob('\\[1-10\\].js'));
assert(!isGlob('\\[^abc\\].js'));
assert(!isGlob('\\a/b/c/\\[a-z\\].js'));
assert(!isGlob('\\[a-j\\]\\*\\[^c\\]b/c'));
assert(!isGlob('\\[abc\\].js'));
assert(!isGlob('\\a/b/c/\\[a-z\\].js'));
assert(!isGlob('abc/\\(aaa|bbb).js'));
assert(!isGlob('abc/\\?.js'));
});
});

describe('extglob patterns', function() {
it('should return `true` if it has an extglob:', function() {
assert(isGlob('abc/@(a).js'));
it('should be true if it has an extglob:', function() {
assert(isGlob('abc/!(a).js'));
assert(isGlob('abc/+(a).js'));
assert(isGlob('abc/*(a).js'));
assert(isGlob('abc/?(a).js'));
assert(isGlob('abc/@(a|b).js'));
assert(isGlob('abc/!(a|b).js'));
assert(isGlob('abc/+(a|b).js'));
assert(isGlob('abc/(ab)*.js'));
assert(isGlob('abc/(a|b).js'));
assert(isGlob('abc/*(a).js'));
assert(isGlob('abc/*(a|b).js'));
assert(isGlob('abc/+(a).js'));
assert(isGlob('abc/+(a|b).js'));
assert(isGlob('abc/?(a).js'));
assert(isGlob('abc/?(a|b).js'));
assert(isGlob('abc/(a|b).js'));
assert(isGlob('abc/(a|b).js'));
assert(isGlob('abc/(a|b).js'));
assert(isGlob('abc/(a|b).js'));
assert(isGlob('abc/(a|b).js'));
assert(!isGlob('abc/(ab).js'));
assert(!isGlob('abc/(ab).js'));
assert(!isGlob('abc/(ab).js'));
assert(!isGlob('abc/(ab).js'));
assert(!isGlob('abc/(ab).js'));
assert(isGlob('abc/(ab)*.js'));
assert(isGlob('abc/(ab)*.js'));
assert(isGlob('abc/(ab)*.js'));
assert(isGlob('abc/(ab)*.js'));
assert(isGlob('abc/(ab)*.js'));
assert(isGlob('abc/@(a).js'));
assert(isGlob('abc/@(a|b).js'));
});

it('should return `false` if extglob characters are escaped:', function() {
it('should be false if extglob characters are escaped:', function() {
assert(!isGlob('abc/\\*.js'));
assert(!isGlob('abc/\\*\\*.js'));
assert(!isGlob('abc/\\@(a).js'));
assert(!isGlob('abc/\\!(a).js'));
assert(!isGlob('abc/\\+(a).js'));
assert(!isGlob('abc/\\*(a).js'));
assert(!isGlob('abc/\\?(a).js'));
assert(isGlob('abc/\\@(a|b).js'));
assert(isGlob('abc/\\!(a|b).js'));
assert(isGlob('abc/\\+(a|b).js'));
assert(isGlob('abc/\\*(a|b).js'));
assert(isGlob('abc/\\?(a|b).js'));
assert(isGlob('abc/\\@(a\\|b).js'));
assert(isGlob('abc/\\!(a\\|b).js'));
assert(isGlob('abc/\\+(a\\|b).js'));
assert(isGlob('abc/\\*(a\\|b).js'));
assert(isGlob('abc/\\?(a\\|b).js'));
assert(isGlob('abc/\\@(a|b).js'), 'matches since extglob is not escaped');
assert(isGlob('abc/\\!(a|b).js'), 'matches since extglob is not escaped');
assert(isGlob('abc/\\+(a|b).js'), 'matches since extglob is not escaped');
assert(isGlob('abc/\\*(a|b).js'), 'matches since extglob is not escaped');
assert(isGlob('abc/\\?(a|b).js'), 'matches since extglob is not escaped');
assert(isGlob('abc/\\@(a\\|b).js'), 'matches since extglob is not escaped');
assert(isGlob('abc/\\!(a\\|b).js'), 'matches since extglob is not escaped');
assert(isGlob('abc/\\+(a\\|b).js'), 'matches since extglob is not escaped');
assert(isGlob('abc/\\*(a\\|b).js'), 'matches since extglob is not escaped');
assert(isGlob('abc/\\?(a\\|b).js'), 'matches since extglob is not escaped');
});

it('should not return true for non-extglob parens', function() {
assert(!isGlob('C:/Program Files (x86)/'));
});

it('should return `true` if it has glob characters and is not a valid path:', function() {
it('should be true if it has glob characters and is not a valid path:', function() {
assert(isGlob('abc/[*].js'));
assert(isGlob('abc/*.js'));
assert(isGlob('abc/?.js'));
});

it('should return `false` if it is a valid non-glob path:', function() {
it('should be false if it is a valid non-glob path:', function() {
assert(!isGlob('abc/?.js'));
assert(!isGlob('abc/!.js'));
assert(!isGlob('abc/@.js'));
assert(!isGlob('abc/+.js'));
Expand Down

0 comments on commit 44d33de

Please sign in to comment.