Skip to content

Commit

Permalink
feat(matcher): ignore patterns (#5336)
Browse files Browse the repository at this point in the history
Co-authored-by: Zoltan Kochan <z@kochan.io>
  • Loading branch information
LuciNyan and zkochan committed Sep 14, 2022
1 parent 3f01370 commit 9b44d38
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 6 deletions.
16 changes: 16 additions & 0 deletions .changeset/calm-phones-jog.md
@@ -0,0 +1,16 @@
---
"@pnpm/matcher": minor
"pnpm": minor
---

Now it is possible to exclude packages from hoisting by prepending a `!` to the pattern. This works with both the `hoist-pattern` and `public-hoist-pattern` settings. For instance:

```
public-hoist-pattern[]='*types*'
public-hoist-pattern[]='!@types/react'
hoist-pattern[]='*eslint*'
hoist-pattern[]='!*eslint-plugin*'
```

Ref [#5272](https://github.com/pnpm/pnpm/issues/5272)
46 changes: 40 additions & 6 deletions packages/matcher/src/index.ts
@@ -1,16 +1,40 @@
import escapeStringRegexp from 'escape-string-regexp'

export default function matcher (patterns: string[] | string) {
if (typeof patterns === 'string') return matcherFromPattern(patterns)
type Matcher = (input: string) => boolean

export default function matcher (patterns: string[] | string): Matcher {
if (typeof patterns === 'string') return matcherWhenOnlyOnePattern(patterns)
switch (patterns.length) {
case 0: return () => false
case 1: return matcherFromPattern(patterns[0])
case 1: return matcherWhenOnlyOnePattern(patterns[0])
}
const matchArr: Array<{ match: Matcher, ignore: boolean }> = []
let hasIgnore = false
for (const pattern of patterns) {
if (isIgnorePattern(pattern)) {
hasIgnore = true
matchArr.push({ ignore: true, match: matcherFromPattern(pattern.substring(1)) })
} else {
matchArr.push({ ignore: false, match: matcherFromPattern(pattern) })
}
}
if (!hasIgnore) {
return (input: string) => matchArr.some(({ match }) => match(input))
}
return (input: string) => {
let isMatched = false
for (const { ignore, match } of matchArr) {
if (ignore) {
isMatched = !match(input)
} else if (!isMatched && match(input)) {
isMatched = true
}
}
return isMatched
}
const matchArr = patterns.map(matcherFromPattern)
return (input: string) => matchArr.some((match) => match(input))
}

function matcherFromPattern (pattern: string) {
function matcherFromPattern (pattern: string): Matcher {
if (pattern === '*') {
return () => true
}
Expand All @@ -23,3 +47,13 @@ function matcherFromPattern (pattern: string) {
const regexp = new RegExp(`^${escapedPattern}$`)
return (input: string) => regexp.test(input)
}

function isIgnorePattern (pattern: string): boolean {
return pattern.startsWith('!')
}

function matcherWhenOnlyOnePattern (pattern: string): Matcher {
return isIgnorePattern(pattern)
? () => false
: matcherFromPattern(pattern)
}
16 changes: 16 additions & 0 deletions packages/matcher/test/index.ts
Expand Up @@ -30,4 +30,20 @@ test('matcher()', () => {
expect(match('bar')).toBe(true)
expect(match('express')).toBe(false)
}
{
const match = matcher(['eslint-*', '!eslint-plugin-bar'])
expect(match('eslint-plugin-foo')).toBe(true)
expect(match('eslint-plugin-bar')).toBe(false)
}
{
const match = matcher(['!eslint-plugin-bar', 'eslint-*'])
expect(match('eslint-plugin-foo')).toBe(true)
expect(match('eslint-plugin-bar')).toBe(true)
}
{
const match = matcher(['eslint-*', '!eslint-plugin-*', 'eslint-plugin-bar'])
expect(match('eslint-config-foo')).toBe(true)
expect(match('eslint-plugin-foo')).toBe(false)
expect(match('eslint-plugin-bar')).toBe(true)
}
})

0 comments on commit 9b44d38

Please sign in to comment.