Skip to content

Commit bebd037

Browse files
authored
glob/fsWalk: early exclusion of non-matching directories (#1251)
1 parent 54a188a commit bebd037

File tree

3 files changed

+70
-2
lines changed

3 files changed

+70
-2
lines changed

src/Spago/Glob.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import mm from 'micromatch';
2+
import picomatch from 'picomatch';
23
import * as fsWalk from '@nodelib/fs.walk';
34

45
export const testGlob = glob => mm.matcher(glob.include, { ignore: glob.ignore });
@@ -13,3 +14,6 @@ export const fsWalkImpl = Left => Right => respond => options => path => () => {
1314
};
1415

1516
export const isFile = dirent => dirent.isFile();
17+
18+
export const scanPattern = picomatch.scan
19+

src/Spago/Glob.purs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,22 @@ type FsWalkOptions = { entryFilter :: Entry -> Effect Boolean, deepFilter :: Ent
3232
foreign import data DirEnt :: Type
3333
foreign import isFile :: DirEnt -> Boolean
3434

35+
-- https://www.npmjs.com/package/picomatch#scan
36+
foreign import scanPattern :: String -> PatternInfo
37+
type PatternInfo =
38+
{ prefix :: String
39+
, input :: String
40+
, start :: Int
41+
, base :: String
42+
, glob :: String
43+
, isBrace :: Boolean
44+
, isBracket :: Boolean
45+
, isGlob :: Boolean
46+
, isExtglob :: Boolean
47+
, isGlobstar :: Boolean
48+
, negated :: Boolean
49+
}
50+
3551
foreign import fsWalkImpl
3652
:: (forall a b. a -> Either a b)
3753
-> (forall a b. b -> Either a b)
@@ -55,7 +71,7 @@ gitignoreFileToGlob base =
5571
>>> (\{ left, right } -> { ignore: left, include: right })
5672

5773
where
58-
isComment = isJust <<< String.stripPrefix (String.Pattern "#")
74+
isComment = isPrefix (String.Pattern "#")
5975
dropSuffixSlash str = fromMaybe str $ String.stripSuffix (String.Pattern "/") str
6076
dropPrefixSlash str = fromMaybe str $ String.stripPrefix (String.Pattern "/") str
6177

@@ -116,13 +132,56 @@ fsWalk cwd ignorePatterns includePatterns = Aff.makeAff \cb -> do
116132

117133
Ref.modify_ addMatcher ignoreMatcherRef
118134

135+
-- The base of every includePattern
136+
-- The base of a pattern is its longest non-glob prefix.
137+
-- For example: foo/bar/*/*.purs => foo/bar
138+
-- **/spago.yaml => ""
139+
includePatternBases :: Array String
140+
includePatternBases = map (_.base <<< scanPattern) includePatterns
141+
142+
matchesAnyPatternBase :: String -> Boolean
143+
matchesAnyPatternBase relDirPath = any matchesPatternBase includePatternBases
144+
where
145+
matchesPatternBase :: String -> Boolean
146+
matchesPatternBase "" =
147+
-- Patterns which have no base, for example **/spago.yaml, match every directory.
148+
true
149+
matchesPatternBase patternBase | String.length relDirPath < String.length patternBase =
150+
-- The directoryPath is shorter than the patterns base, so in order for this pattern to
151+
-- match anything in this directory, the directories path must be a prefix of the patterns base.
152+
-- For example: pattern = .spago/p/unfoldable-6.0.0/src/**/*.purs
153+
-- patternBase = .spago/p/unfoldable-6.0.0/src
154+
-- relDirPath = .spago/p/
155+
-- => relDirPath is a prefix of patternBase => the directory matches
156+
--
157+
-- Or in the negative case:
158+
-- pattern = .spago/p/unfoldable-6.0.0/src/**/*.purs
159+
-- patternBase = .spago/p/unfoldable-6.0.0/src
160+
-- relDirPath = .spago/p/arrays-7.3.0
161+
-- => relDirPath is not a prefix of patternBase => the directory does not match
162+
String.Pattern relDirPath `isPrefix` patternBase
163+
matchesPatternBase patternBase | otherwise =
164+
-- The directoryPath is longer than the patterns base, so the directoryPath is more specific.
165+
-- In order for this pattern to match anything in this directory, the patterns base must be a
166+
-- prefix of the directories path.
167+
-- For example: pattern = .spago/p/unfoldable-6.0.0/src/**/*.purs
168+
-- patternBase = .spago/p/unfoldable-6.0.0/src
169+
-- relDirPath = .spago/p/unfoldable-6.0.0/src/Data
170+
-- => patternBase is a prefix of relDirPath => the directory matches
171+
String.Pattern patternBase `isPrefix` relDirPath
172+
119173
-- Should `fsWalk` recurse into this directory?
120174
deepFilter :: Entry -> Effect Boolean
121175
deepFilter entry = fromMaybe false <$> runMaybeT do
122176
isCanceled <- lift $ Ref.read canceled
123177
guard $ not isCanceled
178+
let relPath = withForwardSlashes $ Path.relative cwd entry.path
124179
shouldIgnore <- lift $ Ref.read ignoreMatcherRef
125-
pure $ not $ shouldIgnore $ Path.relative cwd entry.path
180+
guard $ not $ shouldIgnore relPath
181+
182+
-- Only if the path of this directory matches any of the patterns base path,
183+
-- can anything in this directory possibly match the corresponding full pattern.
184+
pure $ matchesAnyPatternBase relPath
126185

127186
-- Should `fsWalk` retain this entry for the result array?
128187
entryFilter :: Entry -> Effect Boolean

src/Spago/Prelude.purs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module Spago.Prelude
1515
, unsafeStringify
1616
, withBackoff'
1717
, withForwardSlashes
18+
, isPrefix
1819
) where
1920

2021
import Spago.Core.Prelude
@@ -164,3 +165,7 @@ mkTemp = mkTemp' Nothing
164165

165166
withForwardSlashes :: String -> String
166167
withForwardSlashes = String.replaceAll (Pattern "\\") (Replacement "/")
168+
169+
isPrefix :: String.Pattern -> String -> Boolean
170+
isPrefix p = isJust <<< String.stripPrefix p
171+

0 commit comments

Comments
 (0)