Skip to content

Commit

Permalink
Add TypeScript definition (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
BendingBender authored and sindresorhus committed Apr 22, 2019
1 parent 3a5a948 commit 5ed34b1
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 44 deletions.
72 changes: 72 additions & 0 deletions index.d.ts
@@ -0,0 +1,72 @@
declare namespace matcher {
interface Options {
/**
Treat uppercase and lowercase characters as being the same.
Ensure you use this correctly. For example, files and directories should be matched case-insensitively, while most often, object keys should be matched case-sensitively.
@default false
*/
readonly caseSensitive?: boolean;
}
}

declare const matcher: {
/**
Simple [wildcard](https://en.wikipedia.org/wiki/Wildcard_character) matching.
@param inputs - Strings to match.
@param patterns - Use `*` to match zero or more characters. A pattern starting with `!` will be negated.
@returns `inputs` filtered based on the `patterns`.
@example
```
import matcher = require('matcher');
matcher(['foo', 'bar', 'moo'], ['*oo', '!foo']);
//=> ['moo']
matcher(['foo', 'bar', 'moo'], ['!*oo']);
//=> ['bar']
```
*/
(inputs: readonly string[], patterns: readonly string[], options?: matcher.Options): string[];

/**
@param input - String to match.
@param pattern - Use `*` to match zero or more characters. A pattern starting with `!` will be negated.
@returns Whether the `input` matches the `pattern`.
@example
```
import matcher = require('matcher');
matcher.isMatch('unicorn', 'uni*');
//=> true
matcher.isMatch('unicorn', '*corn');
//=> true
matcher.isMatch('unicorn', 'un*rn');
//=> true
matcher.isMatch('rainbow', '!unicorn');
//=> true
matcher.isMatch('foo bar baz', 'foo b* b*');
//=> true
matcher.isMatch('unicorn', 'uni\\*');
//=> false
matcher.isMatch('UNICORN', 'UNI*', {caseSensitive: true});
//=> true
matcher.isMatch('UNICORN', 'unicorn', {caseSensitive: true});
//=> false
```
*/
isMatch(input: string, pattern: string, options?: matcher.Options): boolean;
};

export = matcher;
30 changes: 15 additions & 15 deletions index.js
@@ -1,17 +1,17 @@
'use strict';
const escapeStringRegexp = require('escape-string-regexp');

const reCache = new Map();
const regexpCache = new Map();

function makeRe(pattern, options) {
function makeRegexp(pattern, options) {
options = Object.assign({
caseSensitive: false
}, options);

const cacheKey = pattern + JSON.stringify(options);

if (reCache.has(cacheKey)) {
return reCache.get(cacheKey);
if (regexpCache.has(cacheKey)) {
return regexpCache.get(cacheKey);
}

const negated = pattern[0] === '!';
Expand All @@ -22,11 +22,11 @@ function makeRe(pattern, options) {

pattern = escapeStringRegexp(pattern).replace(/\\\*/g, '.*');

const re = new RegExp(`^${pattern}$`, options.caseSensitive ? '' : 'i');
re.negated = negated;
reCache.set(cacheKey, re);
const regexp = new RegExp(`^${pattern}$`, options.caseSensitive ? '' : 'i');
regexp.negated = negated;
regexpCache.set(cacheKey, regexp);

return re;
return regexp;
}

module.exports = (inputs, patterns, options) => {
Expand All @@ -40,9 +40,9 @@ module.exports = (inputs, patterns, options) => {

const firstNegated = patterns[0][0] === '!';

patterns = patterns.map(x => makeRe(x, options));
patterns = patterns.map(pattern => makeRegexp(pattern, options));

const ret = [];
const result = [];

for (const input of inputs) {
// If first pattern is negated we include everything to match user expectation
Expand All @@ -55,15 +55,15 @@ module.exports = (inputs, patterns, options) => {
}

if (matches) {
ret.push(input);
result.push(input);
}
}

return ret;
return result;
};

module.exports.isMatch = (input, pattern, options) => {
const re = makeRe(pattern, options);
const matches = re.test(input);
return re.negated ? !matches : matches;
const regexp = makeRegexp(pattern, options);
const matches = regexp.test(input);
return regexp.negated ? !matches : matches;
};
13 changes: 13 additions & 0 deletions index.test-d.ts
@@ -0,0 +1,13 @@
import {expectType} from 'tsd';
import matcher = require('.');

const options: matcher.Options = {};

expectType<string[]>(matcher(['foo', 'bar', 'moo'], ['*oo', '!foo']));
expectType<string[]>(matcher(['foo', 'bar', 'moo'], ['!*oo']));
expectType<string[]>(
matcher(['foo', 'bar', 'moo'], ['!*oo'], {caseSensitive: true})
);

expectType<boolean>(matcher.isMatch('unicorn', 'uni*'));
expectType<boolean>(matcher.isMatch('UNICORN', 'UNI*', {caseSensitive: true}));
10 changes: 6 additions & 4 deletions package.json
Expand Up @@ -13,11 +13,12 @@
"node": ">=6"
},
"scripts": {
"test": "xo && ava",
"test": "xo && ava && tsd",
"bench": "matcha bench.js"
},
"files": [
"index.js"
"index.js",
"index.d.ts"
],
"keywords": [
"matcher",
Expand All @@ -40,8 +41,9 @@
"escape-string-regexp": "^1.0.4"
},
"devDependencies": {
"ava": "*",
"ava": "^1.4.1",
"matcha": "^0.7.0",
"xo": "*"
"tsd": "^0.7.2",
"xo": "^0.24.0"
}
}
2 changes: 1 addition & 1 deletion readme.md
Expand Up @@ -69,7 +69,7 @@ String to match.

#### options

Type: `Object`
Type: `object`

##### caseSensitive

Expand Down
48 changes: 24 additions & 24 deletions test.js
@@ -1,31 +1,31 @@
import test from 'ava';
import m from '.';
import matcher from '.';

test('matcher()', t => {
t.deepEqual(m(['foo', 'bar'], ['foo']), ['foo']);
t.deepEqual(m(['foo', 'bar'], ['bar']), ['bar']);
t.deepEqual(m(['foo', 'bar'], ['fo*', 'ba*', '!bar']), ['foo']);
t.deepEqual(m(['foo', 'bar', 'moo'], ['!*o']), ['bar']);
t.deepEqual(m(['moo', 'MOO'], ['*oo'], {caseSensitive: true}), ['moo']);
t.deepEqual(m(['moo', 'MOO'], ['*oo'], {caseSensitive: false}), ['moo', 'MOO']);
t.notThrows(() => m([], []));
t.deepEqual(matcher(['foo', 'bar'], ['foo']), ['foo']);
t.deepEqual(matcher(['foo', 'bar'], ['bar']), ['bar']);
t.deepEqual(matcher(['foo', 'bar'], ['fo*', 'ba*', '!bar']), ['foo']);
t.deepEqual(matcher(['foo', 'bar', 'moo'], ['!*o']), ['bar']);
t.deepEqual(matcher(['moo', 'MOO'], ['*oo'], {caseSensitive: true}), ['moo']);
t.deepEqual(matcher(['moo', 'MOO'], ['*oo'], {caseSensitive: false}), ['moo', 'MOO']);
t.notThrows(() => matcher([], []));
});

test('matcher.isMatch()', t => {
t.true(m.isMatch('unicorn', 'unicorn'));
t.true(m.isMatch('MOO', 'MOO'));
t.true(m.isMatch('unicorn', 'uni*'));
t.true(m.isMatch('UNICORN', 'unicorn', {caseSensitive: false}));
t.true(m.isMatch('unicorn', '*corn'));
t.true(m.isMatch('unicorn', 'un*rn'));
t.true(m.isMatch('foo unicorn bar', '*unicorn*'));
t.true(m.isMatch('unicorn', '*'));
t.true(m.isMatch('UNICORN', 'UNI*', {caseSensitive: true}));
t.false(m.isMatch('UNICORN', 'unicorn', {caseSensitive: true}));
t.false(m.isMatch('unicorn', ''));
t.false(m.isMatch('unicorn', '!unicorn'));
t.false(m.isMatch('unicorn', '!uni*'));
t.false(m.isMatch('unicorn', 'uni\\*'));
t.true(m.isMatch('unicorn', '!tricorn'));
t.true(m.isMatch('unicorn', '!tri*'));
t.true(matcher.isMatch('unicorn', 'unicorn'));
t.true(matcher.isMatch('MOO', 'MOO'));
t.true(matcher.isMatch('unicorn', 'uni*'));
t.true(matcher.isMatch('UNICORN', 'unicorn', {caseSensitive: false}));
t.true(matcher.isMatch('unicorn', '*corn'));
t.true(matcher.isMatch('unicorn', 'un*rn'));
t.true(matcher.isMatch('foo unicorn bar', '*unicorn*'));
t.true(matcher.isMatch('unicorn', '*'));
t.true(matcher.isMatch('UNICORN', 'UNI*', {caseSensitive: true}));
t.false(matcher.isMatch('UNICORN', 'unicorn', {caseSensitive: true}));
t.false(matcher.isMatch('unicorn', ''));
t.false(matcher.isMatch('unicorn', '!unicorn'));
t.false(matcher.isMatch('unicorn', '!uni*'));
t.false(matcher.isMatch('unicorn', 'uni\\*'));
t.true(matcher.isMatch('unicorn', '!tricorn'));
t.true(matcher.isMatch('unicorn', '!tri*'));
});

0 comments on commit 5ed34b1

Please sign in to comment.