Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .verb.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ The following options may be used with the main `picomatch()` function or any of
| [onMatch](#optionsonMatch) | `function` | `undefined` | Function to be called on matched items. |
| [onResult](#optionsonResult) | `function` | `undefined` | Function to be called on all items, regardless of whether or not they are matched or ignored. |
| `posix` | `boolean` | `false` | Support POSIX character classes ("posix brackets"). |
| `prepend` | `boolean` | `undefined` | String to prepend to the generated regex used for matching. |
| `prepend` | `string` | `undefined` | String to prepend to the generated regex used for matching. |
| `regex` | `boolean` | `false` | Use regular expression rules for `+` (instead of matching literal `+`), and for stars that follow closing parentheses or brackets (as in `)*` and `]*`). |
| `strictBrackets` | `boolean` | `undefined` | Throw an error if brackets, braces, or parens are imbalanced. |
| `strictSlashes` | `boolean` | `undefined` | When true, picomatch won't match trailing slashes with single stars. |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ The following options may be used with the main `picomatch()` function or any of
| [onMatch](#optionsonMatch) | `function` | `undefined` | Function to be called on matched items. |
| [onResult](#optionsonResult) | `function` | `undefined` | Function to be called on all items, regardless of whether or not they are matched or ignored. |
| `posix` | `boolean` | `false` | Support POSIX character classes ("posix brackets"). |
| `prepend` | `boolean` | `undefined` | String to prepend to the generated regex used for matching. |
| `prepend` | `string` | `undefined` | String to prepend to the generated regex used for matching. |
| `regex` | `boolean` | `false` | Use regular expression rules for `+` (instead of matching literal `+`), and for stars that follow closing parentheses or brackets (as in `)*` and `]*`). |
| `strictBrackets` | `boolean` | `undefined` | Throw an error if brackets, braces, or parens are imbalanced. |
| `strictSlashes` | `boolean` | `undefined` | When true, picomatch won't match trailing slashes with single stars. |
Expand Down
8 changes: 5 additions & 3 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ const parse = (input, options) => {
start: 0,
dot: opts.dot === true,
consumed: '',
output: '',
output: bos.output,
prefix: '',
backtrack: false,
negated: false,
Expand Down Expand Up @@ -585,7 +585,8 @@ const parse = (input, options) => {
// Suitable patterns: `/!(*.d).ts`, `/!(*.d).{ts,tsx}`, `**/!(*-dbg).@(js)`.
//
// Disabling the `fastpaths` option due to a problem with parsing strings as `.ts` in the pattern like `**/!(*.d).ts`.
const expression = parse(rest, { ...options, fastpaths: false }).output;
// Clear prepend so the suffix is not prefixed again; the outer bos token already holds it.
const expression = parse(rest, { ...options, fastpaths: false, prepend: '' }).output;

output = token.close = `)${expression})${extglobStar})`;
}
Expand All @@ -603,7 +604,8 @@ const parse = (input, options) => {
* Fast paths
*/

if (opts.fastpaths !== false && !/(^[*!]|[/()[\]{}"])/.test(input)) {
// Fast paths skip tokenization, which is where options.prepend is applied.
if (opts.fastpaths !== false && !opts.prepend && !/(^[*!]|[/()[\]{}"])/.test(input)) {
let backslashes = false;

let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => {
Expand Down
3 changes: 2 additions & 1 deletion lib/picomatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@ picomatch.makeRe = (input, options = {}, returnOutput = false, returnState = fal

let parsed = { negated: false, fastpaths: true };

if (options.fastpaths !== false && (input[0] === '.' || input[0] === '*')) {
// Fast paths skip tokenization, which is where options.prepend is applied.
if (options.fastpaths !== false && !options.prepend && (input[0] === '.' || input[0] === '*')) {
parsed.output = parse.fastpaths(input, options);
}

Expand Down
40 changes: 40 additions & 0 deletions test/options.prepend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use strict';

const assert = require('assert');
const { isMatch, makeRe, parse } = require('..');

describe('options.prepend', () => {
it('should prepend a string to the parsed regex source (issue #110)', () => {
const state = parse('abc', { prepend: 'foo' });
assert.strictEqual(state.tokens[0].output, 'foo');
assert.strictEqual(state.output, 'fooabc');
});

it('should prepend a string to the generated matcher regex', () => {
assert.strictEqual(makeRe('abc', { prepend: 'foo' }).source, '^(?:fooabc)$');
assert(isMatch('fooabc', 'ab*', { prepend: 'foo' }));
assert(!isMatch('abc', 'ab*', { prepend: 'foo' }));
});

it('should prepend a string when compiling common fast-path patterns', () => {
const re = makeRe('*.js', { prepend: 'foo' });
assert.ok(re.source.indexOf('^(?:foo') === 0);
assert(isMatch('fooa.js', '*.js', { prepend: 'foo' }));
assert(!isMatch('a.js', '*.js', { prepend: 'foo' }));
});

it('should not double-prepend when the parser rebuilds output after backtracking', () => {
const re = makeRe('[[:alpha:]]', { prepend: 'foo' });
assert.ok(re.source.indexOf('^(?:foo') === 0);
assert.strictEqual(re.source.split('foo').length - 1, 1);
assert(isMatch('fooa', '[[:alpha:]]', { prepend: 'foo' }));
assert(!isMatch('a', '[[:alpha:]]', { prepend: 'foo' }));
});

it('should not re-apply prepend when parsing a suffix after a negated extglob', () => {
const state = parse('!(*.d).ts', { prepend: 'foo' });
assert.strictEqual(state.output.split('foo').length - 1, 1);
assert(isMatch('foofile.ts', '!(*.d).ts', { prepend: 'foo' }));
assert(!isMatch('foofile.d.ts', '!(*.d).ts', { prepend: 'foo' }));
});
});