A glob is a pattern that picks out files by their paths, with wildcards standing in for the parts that vary. *.css matches every name ending in .css in one folder, and src/**/*.test.js matches every file ending in .test.js anywhere inside src, however deep. You write globs to tell a tool which files to work on or to skip, without listing them one by one. The name is short for global: it comes from a program in early Unix that expanded these patterns into lists of file names.
A path is made of segments, the names between the / characters: src/utils/date.js has three. Think of ? as a blank tile in Scrabble, which stands for any one letter, and * as a blank on a form, which takes a word of any length but stays on its own line. Only ** crosses from one segment to the next:
| Wildcard | Matches | Example |
|---|---|---|
? | Exactly one character, but never /. | photo-?.jpg matches photo-1.jpg and photo-a.jpg, but not photo-10.jpg. |
* | Any run of characters within one segment, including none, but never /. | *.css matches main.css, but not styles/main.css. |
**, as a whole segment | Any number of whole segments, including none, so it can cross /. | docs/**/*.md matches docs/intro.md and docs/guides/setup/intro.md. |
| Any other character | Only itself. | . matches only a dot, so data.json doesn't match dataxjson. |
Here's a pattern a test runner might use to find test files, and a couple of others, checked against some paths. A pattern has to match the whole path, not only part of it:
| Pattern | Path | Matches? | Why |
|---|---|---|---|
src/**/*.test.js | src/date.test.js | Yes | ** matches no segments at all. |
src/**/*.test.js | src/utils/time/date.test.js | Yes | ** matches utils/time. |
src/**/*.test.js | lib/date.test.js | No | The first segment has to be src. |
src/**/*.test.js | src/date.js | No | The name doesn't end in .test.js. |
styles/* | styles/themes/dark.css | No | * can't cross the / after themes. |
*.js | app.json | No | *.js would match app.js, but here on is left over, and the whole path has to match. |
Globs look like regular expressions, but they aren't. In a regular expression, . matches any character, ? makes the thing before it optional, and * repeats the thing before it, so it can't stand on its own. In a glob, . is a plain dot, ? is any one character, and * stands on its own for any run of characters. Characters that are special in regular expressions, such as +, (, and $, are plain characters in the basic glob syntax described here.
Many tools add more syntax: {js,ts} for any one of a list, so *.{js,ts} matches app.js and app.ts, and [abc] for any one of a set of characters. Tools also differ in the details, so check the docs of the one you're configuring. In .gitignore, for example, a pattern with no / in it, such as *.log, is matched against names at any depth, so it ignores logs/debug.log too.
You meet globs in .gitignore, in tsconfig.json's include and exclude, in the settings that tell a test runner which files hold tests, such as Jest's testMatch and Vitest's include, in ESLint's files and ignores, and in the shell, where ls *.css lists the CSS files in the current folder.
Write globMatch(pattern, path). It returns true when path matches the glob pattern, and false otherwise. Tools use globs like src/**/*.test.js to pick files.
Paths and patterns are made of segments separated by /, like src/utils/date.js. The pattern must match the whole path, not just part of it, and letters must match in case.
Within a segment, ? matches exactly one character, and * matches any number of characters, including none. Neither matches /, so *.js matches app.js but not src/app.js. Every other character matches only itself: ., +, (, [, and the like have no special meaning.
A segment that's exactly ** matches any number of whole segments, including none: src/**/*.js matches src/app.js and src/a/b/app.js, and docs/** matches docs, docs/intro.md, and docs/a/b. A ** that shares its segment with other characters, as in **.js, works like *.
globMatch("*.js", "app.js"); // true
globMatch("*.js", "src/app.js"); // false
globMatch("src/**/*.test.js", "src/date.test.js"); // true
globMatch("src/**/*.test.js", "src/a/b/x.test.js"); // true
globMatch("img/??.png", "img/01.png"); // true
globMatch("img/??.png", "img/1.png"); // false
expect(globMatch("src/app.js", "src/app.js")).toBe(true);
expect(globMatch("src/app.js", "src/app.jsx")).toBe(false);
expect(globMatch("src/app.js", "SRC/app.js")).toBe(false);
expect(globMatch("README.md", "readme.md")).toBe(false);
expect(globMatch("img/??.png", "img/01.png")).toBe(true);
expect(globMatch("img/??.png", "img/1.png")).toBe(false);
expect(globMatch("img/??.png", "img/001.png")).toBe(false);
expect(globMatch("v?", "v1")).toBe(true);
expect(globMatch("*.js", "app.js")).toBe(true);
expect(globMatch("*.js", ".js")).toBe(true);
expect(globMatch("test-*-v*.js", "test--v.js")).toBe(true);
expect(globMatch("test-*-v*.js", "test-unit-v12.js")).toBe(true);
expect(globMatch("*", "README")).toBe(true);
expect(globMatch("*.js", "src/app.js")).toBe(false);
expect(globMatch("src*", "src/app.js")).toBe(false);
expect(globMatch("a?b", "a/b")).toBe(false);
expect(globMatch("src/*", "src/lib/app.js")).toBe(false);
expect(globMatch("*/*", "src/app.js")).toBe(true);
expect(globMatch("*.js", "app.json")).toBe(false);
expect(globMatch("app", "my-app")).toBe(false);
expect(globMatch("src", "src/app.js")).toBe(false);
expect(globMatch("lib/*.js", "src/lib/app.js")).toBe(false);
expect(globMatch("file(1).txt", "file(1).txt")).toBe(true);
expect(globMatch("file.txt", "fileXtxt")).toBe(false);
expect(globMatch("a+b.js", "aab.js")).toBe(false);
expect(globMatch("[ab].js", "a.js")).toBe(false);
expect(globMatch("[ab].js", "[ab].js")).toBe(true);
expect(globMatch("$v^{1,2}|x", "$v^{1,2}|x")).toBe(true);
expect(globMatch("a\\d", "a\\d")).toBe(true);
expect(globMatch("a\\d", "a5")).toBe(false);
expect(globMatch("src/**/*.js", "src/app.js")).toBe(true);
expect(globMatch("src/**/*.js", "src/lib/app.js")).toBe(true);
expect(globMatch("src/**/*.js", "src/a/b/c/app.js")).toBe(true);
expect(globMatch("src/**/*.js", "lib/app.js")).toBe(false);
expect(globMatch("src/**/*.js", "src/app.ts")).toBe(false);
expect(globMatch("a/**/b", "a/xb")).toBe(false);
expect(globMatch("**/*.md", "README.md")).toBe(true);
expect(globMatch("**/*.md", "docs/guide/intro.md")).toBe(true);
expect(globMatch("docs/**", "docs")).toBe(true);
expect(globMatch("docs/**", "docs/intro.md")).toBe(true);
expect(globMatch("docs/**", "docs/a/b/c")).toBe(true);
expect(globMatch("docs/**", "documents/intro.md")).toBe(false);
expect(globMatch("**", "any/path/at/all")).toBe(true);
expect(globMatch("**.js", "app.js")).toBe(true);
expect(globMatch("**.js", "src/app.js")).toBe(false);
expect(globMatch("a/**b", "a/xyb")).toBe(true);
expect(globMatch("a/**b", "a/x/yb")).toBe(false);
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Glob matching. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.