pax_global_header00006660000000000000000000000064143065500000014503gustar00rootroot0000000000000052 comment=a028e3670ba3186a102cb9108e3a6e48c1148a9e fast-glob-3.2.12/000077500000000000000000000000001430655000000134465ustar00rootroot00000000000000fast-glob-3.2.12/.editorconfig000066400000000000000000000005321430655000000161230ustar00rootroot00000000000000# editorconfig.org root = true [*] indent_style = tab indent_size = 4 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [{.travis.yml,circle.yml,appveyor.yml,.github/**}] indent_style = space indent_size = 2 [{npm-shrinkwrap.json,package-lock.json,package.json}] indent_style = space indent_size = 2 fast-glob-3.2.12/.eslintrc.json000066400000000000000000000001651430655000000162440ustar00rootroot00000000000000{ "extends": "mrmlnc", "rules": { "no-magic-numbers": "off", "@typescript-eslint/no-magic-numbers": "off" } } fast-glob-3.2.12/.gitattributes000066400000000000000000000000231430655000000163340ustar00rootroot00000000000000* text=auto eol=lf fast-glob-3.2.12/.github/000077500000000000000000000000001430655000000150065ustar00rootroot00000000000000fast-glob-3.2.12/.github/CODE-OF-CONDUCT.md000066400000000000000000000061751430655000000174520ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at dmalinochkin@rambler.ru. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org fast-glob-3.2.12/.github/CONTRIBUTING.md000066400000000000000000000032161430655000000172410ustar00rootroot00000000000000# Contributing to my package Welcome, and thank you for your interest in contributing to **fast-glob**! Please note that this project is released with a [Contributor Code of Conduct](CODE-OF-CONDUCT.md). By participating in this project you agree to abide by its terms. ## Contribution Guidelines There are a couple of ways you can contribute to this repository: * **Ideas, feature requests and bugs**: We are open to all ideas and we want to get rid of bugs! Use the [Issues section](https://github.com/mrmlnc/fast-glob/issues) to either report a new issue, provide your ideas or contribute to existing threads. * **Documentation**: Found a typo or strangely worded sentences? Submit a PR! * **Code**: Contribute bug fixes, features or design changes. ### Creating an Issue Before you create a new Issue: * Check the [Issues](https://github.com/mrmlnc/fast-glob/issues) on GitHub to ensure one doesn't already exist. * Clearly describe the issue, including the steps to reproduce the issue. ### Making Changes #### Getting Started * Install [Node.js](https://nodejs.org/en/). * Fork the project and clone the fork repository. ([how to create fork?](https://help.github.com/articles/fork-a-repo/#fork-an-example-repository)). * Create a topic branch from the master branch. * Run `yarn` or `npm install` to install the application dependencies. #### Setup 1. Start watching: ``` $ npm run watch ``` 2. Make changes: ``` $ code . ``` 3. Run tests: ``` $ npm t ``` #### Commit Keep git commit messages clear and appropriate. You can use [Angular guide](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines). fast-glob-3.2.12/.github/FUNDING.yml000066400000000000000000000001371430655000000166240ustar00rootroot00000000000000ko_fi: mrmlnc custom: - https://www.buymeacoffee.com/mrmlnc - https://www.paypal.me/mrmlnc fast-glob-3.2.12/.github/ISSUE_TEMPLATE.md000066400000000000000000000003111430655000000175060ustar00rootroot00000000000000### Environment * OS Version: … * Node.js Version: … ### Actual behavior … ### Expected behavior … ### Steps to reproduce 1. … ### Code sample ```js // Paste your code here. ``` fast-glob-3.2.12/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000001511430655000000206040ustar00rootroot00000000000000### What is the purpose of this pull request? … ### What changes did you make? (Give an overview) … fast-glob-3.2.12/.github/workflows/000077500000000000000000000000001430655000000170435ustar00rootroot00000000000000fast-glob-3.2.12/.github/workflows/benchmark.yml000066400000000000000000000037431430655000000215270ustar00rootroot00000000000000name: Benchmark on: - push env: BENCHMARK_LAUNCHES: 100 BENCHMARK_MAX_STDEV: 10 BENCHMARK_RETRIES: 3 jobs: cancel: name: Cancel previous jobs if: github.ref != 'refs/heads/master' runs-on: ubuntu-latest timeout-minutes: 3 steps: - name: Cancel previous jobs uses: styfle/cancel-workflow-action@0.6.0 with: workflow_id: 4198440 access_token: ${{ github.token }} product: name: Product benchmark if: always() needs: - cancel runs-on: ubuntu-latest env: BENCHMARK_TYPE: product BENCHMARK_OPTIONS: '{}' steps: - name: Setup repository uses: actions/checkout@v2 - name: Setup environment uses: actions/setup-node@v1 with: node-version: 16 - name: Install dependencies run: npm install - name: Compile sources run: npm run compile - name: Benchmark (async) run: npm run bench-async - name: Benchmark (stream) run: npm run bench-stream - name: Benchmark (sync) run: npm run bench-sync regress: name: Regress benchmark with options (${{ matrix.benchmark_options }}) needs: - cancel runs-on: ubuntu-latest strategy: fail-fast: false matrix: benchmark_options: - '{}' - '{ "objectMode": true }' - '{ "absolute": true }' env: BENCHMARK_TYPE: regression BENCHMARK_OPTIONS: ${{ matrix.benchmark_options }} steps: - name: Setup repository uses: actions/checkout@v2 - name: Setup environment uses: actions/setup-node@v1 with: node-version: 16 - name: Install dependencies run: npm install - name: Compile sources run: npm run compile - name: Benchmark (async) run: npm run bench-async - name: Benchmark (stream) run: npm run bench-stream - name: Benchmark (sync) run: npm run bench-sync fast-glob-3.2.12/.github/workflows/codeql.yml000066400000000000000000000011571430655000000210410ustar00rootroot00000000000000name: "CodeQL" on: push: branches: - master - "!dependabot/**" pull_request: branches: - master schedule: - cron: "0 0 * * 0" workflow_dispatch: jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write steps: - name: Checkout repository uses: actions/checkout@v2 - name: Initialize CodeQL uses: github/codeql-action/init@v1 with: languages: "javascript" - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v1 fast-glob-3.2.12/.github/workflows/main.yml000066400000000000000000000026161430655000000205170ustar00rootroot00000000000000name: CI on: push: branches: - master - releases/* pull_request: branches: - '*' jobs: cancel: name: Cancel previous jobs if: github.ref != 'refs/heads/master' runs-on: ubuntu-latest timeout-minutes: 3 steps: - name: Cancel previous jobs uses: styfle/cancel-workflow-action@0.6.0 with: workflow_id: 4197767 access_token: ${{ github.token }} test: name: Node.js ${{ matrix.node_version }} on ${{ matrix.os }} if: always() needs: - cancel runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: node_version: [8, 10, 12, 14, 16] os: - ubuntu-latest - macos-latest - windows-latest steps: - name: Setup repository uses: actions/checkout@v2 - name: Setup environment uses: actions/setup-node@v1 with: node-version: ${{ matrix.node_version }} - name: Install dependencies run: npm install - name: Compile sources run: npm run compile - name: Run Hygiene Checks run: npm run lint - name: Run unit tests run: npm run test - name: Run smoke tests (sync) run: npm run smoke:sync - name: Run smoke tests (async) run: npm run smoke:async - name: Run smoke tests (stream) run: npm run smoke:stream fast-glob-3.2.12/.gitignore000066400000000000000000000003261430655000000154370ustar00rootroot00000000000000# Logs logs/ *.log npm-debug.log* # IDE & editors .idea .vscode # Dependency directory node_modules/ # Compiled and temporary files .eslintcache .tmp/ .benchmark/ out/ # Other files package-lock.json yarn.lock fast-glob-3.2.12/.npmrc000066400000000000000000000000231430655000000145610ustar00rootroot00000000000000package-lock=false fast-glob-3.2.12/LICENSE000066400000000000000000000020671430655000000144600ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) Denis Malinochkin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. fast-glob-3.2.12/README.md000066400000000000000000000561361430655000000147400ustar00rootroot00000000000000# fast-glob > It's a very fast and efficient [glob][glob_definition] library for [Node.js][node_js]. This package provides methods for traversing the file system and returning pathnames that matched a defined set of a specified pattern according to the rules used by the Unix Bash shell with some simplifications, meanwhile results are returned in **arbitrary order**. Quick, simple, effective. ## Table of Contents
Details * [Highlights](#highlights) * [Donation](#donation) * [Old and modern mode](#old-and-modern-mode) * [Pattern syntax](#pattern-syntax) * [Basic syntax](#basic-syntax) * [Advanced syntax](#advanced-syntax) * [Installation](#installation) * [API](#api) * [Asynchronous](#asynchronous) * [Synchronous](#synchronous) * [Stream](#stream) * [patterns](#patterns) * [[options]](#options) * [Helpers](#helpers) * [generateTasks](#generatetaskspatterns-options) * [isDynamicPattern](#isdynamicpatternpattern-options) * [escapePath](#escapepathpattern) * [Options](#options-3) * [Common](#common) * [concurrency](#concurrency) * [cwd](#cwd) * [deep](#deep) * [followSymbolicLinks](#followsymboliclinks) * [fs](#fs) * [ignore](#ignore) * [suppressErrors](#suppresserrors) * [throwErrorOnBrokenSymbolicLink](#throwerroronbrokensymboliclink) * [Output control](#output-control) * [absolute](#absolute) * [markDirectories](#markdirectories) * [objectMode](#objectmode) * [onlyDirectories](#onlydirectories) * [onlyFiles](#onlyfiles) * [stats](#stats) * [unique](#unique) * [Matching control](#matching-control) * [braceExpansion](#braceexpansion) * [caseSensitiveMatch](#casesensitivematch) * [dot](#dot) * [extglob](#extglob) * [globstar](#globstar) * [baseNameMatch](#basenamematch) * [FAQ](#faq) * [What is a static or dynamic pattern?](#what-is-a-static-or-dynamic-pattern) * [How to write patterns on Windows?](#how-to-write-patterns-on-windows) * [Why are parentheses match wrong?](#why-are-parentheses-match-wrong) * [How to exclude directory from reading?](#how-to-exclude-directory-from-reading) * [How to use UNC path?](#how-to-use-unc-path) * [Compatible with `node-glob`?](#compatible-with-node-glob) * [Benchmarks](#benchmarks) * [Server](#server) * [Nettop](#nettop) * [Changelog](#changelog) * [License](#license)
## Highlights * Fast. Probably the fastest. * Supports multiple and negative patterns. * Synchronous, Promise and Stream API. * Object mode. Can return more than just strings. * Error-tolerant. ## Donation Do you like this project? Support it by donating, creating an issue or pull request. [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)][paypal_mrmlnc] ## Old and modern mode This package works in two modes, depending on the environment in which it is used. * **Old mode**. Node.js below 10.10 or when the [`stats`](#stats) option is *enabled*. * **Modern mode**. Node.js 10.10+ and the [`stats`](#stats) option is *disabled*. The modern mode is faster. Learn more about the [internal mechanism][nodelib_fs_scandir_old_and_modern_modern]. ## Pattern syntax > :warning: Always use forward-slashes in glob expressions (patterns and [`ignore`](#ignore) option). Use backslashes for escaping characters. There is more than one form of syntax: basic and advanced. Below is a brief overview of the supported features. Also pay attention to our [FAQ](#faq). > :book: This package uses a [`micromatch`][micromatch] as a library for pattern matching. ### Basic syntax * An asterisk (`*`) — matches everything except slashes (path separators), hidden files (names starting with `.`). * A double star or globstar (`**`) — matches zero or more directories. * Question mark (`?`) – matches any single character except slashes (path separators). * Sequence (`[seq]`) — matches any character in sequence. > :book: A few additional words about the [basic matching behavior][picomatch_matching_behavior]. Some examples: * `src/**/*.js` — matches all files in the `src` directory (any level of nesting) that have the `.js` extension. * `src/*.??` — matches all files in the `src` directory (only first level of nesting) that have a two-character extension. * `file-[01].js` — matches files: `file-0.js`, `file-1.js`. ### Advanced syntax * [Escapes characters][micromatch_backslashes] (`\\`) — matching special characters (`$^*+?()[]`) as literals. * [POSIX character classes][picomatch_posix_brackets] (`[[:digit:]]`). * [Extended globs][micromatch_extglobs] (`?(pattern-list)`). * [Bash style brace expansions][micromatch_braces] (`{}`). * [Regexp character classes][micromatch_regex_character_classes] (`[1-5]`). * [Regex groups][regular_expressions_brackets] (`(a|b)`). > :book: A few additional words about the [advanced matching behavior][micromatch_extended_globbing]. Some examples: * `src/**/*.{css,scss}` — matches all files in the `src` directory (any level of nesting) that have the `.css` or `.scss` extension. * `file-[[:digit:]].js` — matches files: `file-0.js`, `file-1.js`, …, `file-9.js`. * `file-{1..3}.js` — matches files: `file-1.js`, `file-2.js`, `file-3.js`. * `file-(1|2)` — matches files: `file-1.js`, `file-2.js`. ## Installation ```console npm install fast-glob ``` ## API ### Asynchronous ```js fg(patterns, [options]) ``` Returns a `Promise` with an array of matching entries. ```js const fg = require('fast-glob'); const entries = await fg(['.editorconfig', '**/index.js'], { dot: true }); // ['.editorconfig', 'services/index.js'] ``` ### Synchronous ```js fg.sync(patterns, [options]) ``` Returns an array of matching entries. ```js const fg = require('fast-glob'); const entries = fg.sync(['.editorconfig', '**/index.js'], { dot: true }); // ['.editorconfig', 'services/index.js'] ``` ### Stream ```js fg.stream(patterns, [options]) ``` Returns a [`ReadableStream`][node_js_stream_readable_streams] when the `data` event will be emitted with matching entry. ```js const fg = require('fast-glob'); const stream = fg.stream(['.editorconfig', '**/index.js'], { dot: true }); for await (const entry of stream) { // .editorconfig // services/index.js } ``` #### patterns * Required: `true` * Type: `string | string[]` Any correct pattern(s). > :1234: [Pattern syntax](#pattern-syntax) > > :warning: This package does not respect the order of patterns. First, all the negative patterns are applied, and only then the positive patterns. If you want to get a certain order of records, use sorting or split calls. #### [options] * Required: `false` * Type: [`Options`](#options-3) See [Options](#options-3) section. ### Helpers #### `generateTasks(patterns, [options])` Returns the internal representation of patterns ([`Task`](./src/managers/tasks.ts) is a combining patterns by base directory). ```js fg.generateTasks('*'); [{ base: '.', // Parent directory for all patterns inside this task dynamic: true, // Dynamic or static patterns are in this task patterns: ['*'], positive: ['*'], negative: [] }] ``` ##### patterns * Required: `true` * Type: `string | string[]` Any correct pattern(s). ##### [options] * Required: `false` * Type: [`Options`](#options-3) See [Options](#options-3) section. #### `isDynamicPattern(pattern, [options])` Returns `true` if the passed pattern is a dynamic pattern. > :1234: [What is a static or dynamic pattern?](#what-is-a-static-or-dynamic-pattern) ```js fg.isDynamicPattern('*'); // true fg.isDynamicPattern('abc'); // false ``` ##### pattern * Required: `true` * Type: `string` Any correct pattern. ##### [options] * Required: `false` * Type: [`Options`](#options-3) See [Options](#options-3) section. #### `escapePath(pattern)` Returns a path with escaped special characters (`*?|(){}[]`, `!` at the beginning of line, `@+!` before the opening parenthesis). ```js fg.escapePath('!abc'); // \\!abc fg.escapePath('C:/Program Files (x86)'); // C:/Program Files \\(x86\\) ``` ##### pattern * Required: `true` * Type: `string` Any string, for example, a path to a file. ## Options ### Common options #### concurrency * Type: `number` * Default: `os.cpus().length` Specifies the maximum number of concurrent requests from a reader to read directories. > :book: The higher the number, the higher the performance and load on the file system. If you want to read in quiet mode, set the value to a comfortable number or `1`. #### cwd * Type: `string` * Default: `process.cwd()` The current working directory in which to search. #### deep * Type: `number` * Default: `Infinity` Specifies the maximum depth of a read directory relative to the start directory. For example, you have the following tree: ```js dir/ └── one/ // 1 └── two/ // 2 └── file.js // 3 ``` ```js // With base directory fg.sync('dir/**', { onlyFiles: false, deep: 1 }); // ['dir/one'] fg.sync('dir/**', { onlyFiles: false, deep: 2 }); // ['dir/one', 'dir/one/two'] // With cwd option fg.sync('**', { onlyFiles: false, cwd: 'dir', deep: 1 }); // ['one'] fg.sync('**', { onlyFiles: false, cwd: 'dir', deep: 2 }); // ['one', 'one/two'] ``` > :book: If you specify a pattern with some base directory, this directory will not participate in the calculation of the depth of the found directories. Think of it as a [`cwd`](#cwd) option. #### followSymbolicLinks * Type: `boolean` * Default: `true` Indicates whether to traverse descendants of symbolic link directories when expanding `**` patterns. > :book: Note that this option does not affect the base directory of the pattern. For example, if `./a` is a symlink to directory `./b` and you specified `['./a**', './b/**']` patterns, then directory `./a` will still be read. > :book: If the [`stats`](#stats) option is specified, the information about the symbolic link (`fs.lstat`) will be replaced with information about the entry (`fs.stat`) behind it. #### fs * Type: `FileSystemAdapter` * Default: `fs.*` Custom implementation of methods for working with the file system. ```ts export interface FileSystemAdapter { lstat?: typeof fs.lstat; stat?: typeof fs.stat; lstatSync?: typeof fs.lstatSync; statSync?: typeof fs.statSync; readdir?: typeof fs.readdir; readdirSync?: typeof fs.readdirSync; } ``` #### ignore * Type: `string[]` * Default: `[]` An array of glob patterns to exclude matches. This is an alternative way to use negative patterns. ```js dir/ ├── package-lock.json └── package.json ``` ```js fg.sync(['*.json', '!package-lock.json']); // ['package.json'] fg.sync('*.json', { ignore: ['package-lock.json'] }); // ['package.json'] ``` #### suppressErrors * Type: `boolean` * Default: `false` By default this package suppress only `ENOENT` errors. Set to `true` to suppress any error. > :book: Can be useful when the directory has entries with a special level of access. #### throwErrorOnBrokenSymbolicLink * Type: `boolean` * Default: `false` Throw an error when symbolic link is broken if `true` or safely return `lstat` call if `false`. > :book: This option has no effect on errors when reading the symbolic link directory. ### Output control #### absolute * Type: `boolean` * Default: `false` Return the absolute path for entries. ```js fg.sync('*.js', { absolute: false }); // ['index.js'] fg.sync('*.js', { absolute: true }); // ['/home/user/index.js'] ``` > :book: This option is required if you want to use negative patterns with absolute path, for example, `!${__dirname}/*.js`. #### markDirectories * Type: `boolean` * Default: `false` Mark the directory path with the final slash. ```js fg.sync('*', { onlyFiles: false, markDirectories: false }); // ['index.js', 'controllers'] fg.sync('*', { onlyFiles: false, markDirectories: true }); // ['index.js', 'controllers/'] ``` #### objectMode * Type: `boolean` * Default: `false` Returns objects (instead of strings) describing entries. ```js fg.sync('*', { objectMode: false }); // ['src/index.js'] fg.sync('*', { objectMode: true }); // [{ name: 'index.js', path: 'src/index.js', dirent: }] ``` The object has the following fields: * name (`string`) — the last part of the path (basename) * path (`string`) — full path relative to the pattern base directory * dirent ([`fs.Dirent`][node_js_fs_class_fs_dirent]) — instance of `fs.Dirent` > :book: An object is an internal representation of entry, so getting it does not affect performance. #### onlyDirectories * Type: `boolean` * Default: `false` Return only directories. ```js fg.sync('*', { onlyDirectories: false }); // ['index.js', 'src'] fg.sync('*', { onlyDirectories: true }); // ['src'] ``` > :book: If `true`, the [`onlyFiles`](#onlyfiles) option is automatically `false`. #### onlyFiles * Type: `boolean` * Default: `true` Return only files. ```js fg.sync('*', { onlyFiles: false }); // ['index.js', 'src'] fg.sync('*', { onlyFiles: true }); // ['index.js'] ``` #### stats * Type: `boolean` * Default: `false` Enables an [object mode](#objectmode) with an additional field: * stats ([`fs.Stats`][node_js_fs_class_fs_stats]) — instance of `fs.Stats` ```js fg.sync('*', { stats: false }); // ['src/index.js'] fg.sync('*', { stats: true }); // [{ name: 'index.js', path: 'src/index.js', dirent: , stats: }] ``` > :book: Returns `fs.stat` instead of `fs.lstat` for symbolic links when the [`followSymbolicLinks`](#followsymboliclinks) option is specified. > > :warning: Unlike [object mode](#objectmode) this mode requires additional calls to the file system. On average, this mode is slower at least twice. See [old and modern mode](#old-and-modern-mode) for more details. #### unique * Type: `boolean` * Default: `true` Ensures that the returned entries are unique. ```js fg.sync(['*.json', 'package.json'], { unique: false }); // ['package.json', 'package.json'] fg.sync(['*.json', 'package.json'], { unique: true }); // ['package.json'] ``` If `true` and similar entries are found, the result is the first found. ### Matching control #### braceExpansion * Type: `boolean` * Default: `true` Enables Bash-like brace expansion. > :1234: [Syntax description][bash_hackers_syntax_expansion_brace] or more [detailed description][micromatch_braces]. ```js dir/ ├── abd ├── acd └── a{b,c}d ``` ```js fg.sync('a{b,c}d', { braceExpansion: false }); // ['a{b,c}d'] fg.sync('a{b,c}d', { braceExpansion: true }); // ['abd', 'acd'] ``` #### caseSensitiveMatch * Type: `boolean` * Default: `true` Enables a [case-sensitive][wikipedia_case_sensitivity] mode for matching files. ```js dir/ ├── file.txt └── File.txt ``` ```js fg.sync('file.txt', { caseSensitiveMatch: false }); // ['file.txt', 'File.txt'] fg.sync('file.txt', { caseSensitiveMatch: true }); // ['file.txt'] ``` #### dot * Type: `boolean` * Default: `false` Allow patterns to match entries that begin with a period (`.`). > :book: Note that an explicit dot in a portion of the pattern will always match dot files. ```js dir/ ├── .editorconfig └── package.json ``` ```js fg.sync('*', { dot: false }); // ['package.json'] fg.sync('*', { dot: true }); // ['.editorconfig', 'package.json'] ``` #### extglob * Type: `boolean` * Default: `true` Enables Bash-like `extglob` functionality. > :1234: [Syntax description][micromatch_extglobs]. ```js dir/ ├── README.md └── package.json ``` ```js fg.sync('*.+(json|md)', { extglob: false }); // [] fg.sync('*.+(json|md)', { extglob: true }); // ['README.md', 'package.json'] ``` #### globstar * Type: `boolean` * Default: `true` Enables recursively repeats a pattern containing `**`. If `false`, `**` behaves exactly like `*`. ```js dir/ └── a └── b ``` ```js fg.sync('**', { onlyFiles: false, globstar: false }); // ['a'] fg.sync('**', { onlyFiles: false, globstar: true }); // ['a', 'a/b'] ``` #### baseNameMatch * Type: `boolean` * Default: `false` If set to `true`, then patterns without slashes will be matched against the basename of the path if it contains slashes. ```js dir/ └── one/ └── file.md ``` ```js fg.sync('*.md', { baseNameMatch: false }); // [] fg.sync('*.md', { baseNameMatch: true }); // ['one/file.md'] ``` ## FAQ ## What is a static or dynamic pattern? All patterns can be divided into two types: * **static**. A pattern is considered static if it can be used to get an entry on the file system without using matching mechanisms. For example, the `file.js` pattern is a static pattern because we can just verify that it exists on the file system. * **dynamic**. A pattern is considered dynamic if it cannot be used directly to find occurrences without using a matching mechanisms. For example, the `*` pattern is a dynamic pattern because we cannot use this pattern directly. A pattern is considered dynamic if it contains the following characters (`…` — any characters or their absence) or options: * The [`caseSensitiveMatch`](#casesensitivematch) option is disabled * `\\` (the escape character) * `*`, `?`, `!` (at the beginning of line) * `[…]` * `(…|…)` * `@(…)`, `!(…)`, `*(…)`, `?(…)`, `+(…)` (respects the [`extglob`](#extglob) option) * `{…,…}`, `{…..…}` (respects the [`braceExpansion`](#braceexpansion) option) ## How to write patterns on Windows? Always use forward-slashes in glob expressions (patterns and [`ignore`](#ignore) option). Use backslashes for escaping characters. With the [`cwd`](#cwd) option use a convenient format. **Bad** ```ts [ 'directory\\*', path.join(process.cwd(), '**') ] ``` **Good** ```ts [ 'directory/*', path.join(process.cwd(), '**').replace(/\\/g, '/') ] ``` > :book: Use the [`normalize-path`][npm_normalize_path] or the [`unixify`][npm_unixify] package to convert Windows-style path to a Unix-style path. Read more about [matching with backslashes][micromatch_backslashes]. ## Why are parentheses match wrong? ```js dir/ └── (special-*file).txt ``` ```js fg.sync(['(special-*file).txt']) // [] ``` Refers to Bash. You need to escape special characters: ```js fg.sync(['\\(special-*file\\).txt']) // ['(special-*file).txt'] ``` Read more about [matching special characters as literals][picomatch_matching_special_characters_as_literals]. ## How to exclude directory from reading? You can use a negative pattern like this: `!**/node_modules` or `!**/node_modules/**`. Also you can use [`ignore`](#ignore) option. Just look at the example below. ```js first/ ├── file.md └── second/ └── file.txt ``` If you don't want to read the `second` directory, you must write the following pattern: `!**/second` or `!**/second/**`. ```js fg.sync(['**/*.md', '!**/second']); // ['first/file.md'] fg.sync(['**/*.md'], { ignore: ['**/second/**'] }); // ['first/file.md'] ``` > :warning: When you write `!**/second/**/*` it means that the directory will be **read**, but all the entries will not be included in the results. You have to understand that if you write the pattern to exclude directories, then the directory will not be read under any circumstances. ## How to use UNC path? You cannot use [Uniform Naming Convention (UNC)][unc_path] paths as patterns (due to syntax), but you can use them as [`cwd`](#cwd) directory. ```ts fg.sync('*', { cwd: '\\\\?\\C:\\Python27' /* or //?/C:/Python27 */ }); fg.sync('Python27/*', { cwd: '\\\\?\\C:\\' /* or //?/C:/ */ }); ``` ## Compatible with `node-glob`? | node-glob | fast-glob | | :----------: | :-------: | | `cwd` | [`cwd`](#cwd) | | `root` | – | | `dot` | [`dot`](#dot) | | `nomount` | – | | `mark` | [`markDirectories`](#markdirectories) | | `nosort` | – | | `nounique` | [`unique`](#unique) | | `nobrace` | [`braceExpansion`](#braceexpansion) | | `noglobstar` | [`globstar`](#globstar) | | `noext` | [`extglob`](#extglob) | | `nocase` | [`caseSensitiveMatch`](#casesensitivematch) | | `matchBase` | [`baseNameMatch`](#basenamematch) | | `nodir` | [`onlyFiles`](#onlyfiles) | | `ignore` | [`ignore`](#ignore) | | `follow` | [`followSymbolicLinks`](#followsymboliclinks) | | `realpath` | – | | `absolute` | [`absolute`](#absolute) | ## Benchmarks ### Server Link: [Vultr Bare Metal][vultr_pricing_baremetal] * Processor: E3-1270v6 (8 CPU) * RAM: 32GB * Disk: SSD ([Intel DC S3520 SSDSC2BB240G7][intel_ssd]) You can see results [here][github_gist_benchmark_server] for latest release. ### Nettop Link: [Zotac bi323][zotac_bi323] * Processor: Intel N3150 (4 CPU) * RAM: 8GB * Disk: SSD ([Silicon Power SP060GBSS3S55S25][silicon_power_ssd]) You can see results [here][github_gist_benchmark_nettop] for latest release. ## Changelog See the [Releases section of our GitHub project][github_releases] for changelog for each release version. ## License This software is released under the terms of the MIT license. [bash_hackers_syntax_expansion_brace]: https://wiki.bash-hackers.org/syntax/expansion/brace [github_gist_benchmark_nettop]: https://gist.github.com/mrmlnc/f06246b197f53c356895fa35355a367c#file-fg-benchmark-nettop-product-txt [github_gist_benchmark_server]: https://gist.github.com/mrmlnc/f06246b197f53c356895fa35355a367c#file-fg-benchmark-server-product-txt [github_releases]: https://github.com/mrmlnc/fast-glob/releases [glob_definition]: https://en.wikipedia.org/wiki/Glob_(programming) [glob_linux_man]: http://man7.org/linux/man-pages/man3/glob.3.html [intel_ssd]: https://ark.intel.com/content/www/us/en/ark/products/93012/intel-ssd-dc-s3520-series-240gb-2-5in-sata-6gb-s-3d1-mlc.html [micromatch_backslashes]: https://github.com/micromatch/micromatch#backslashes [micromatch_braces]: https://github.com/micromatch/braces [micromatch_extended_globbing]: https://github.com/micromatch/micromatch#extended-globbing [micromatch_extglobs]: https://github.com/micromatch/micromatch#extglobs [micromatch_regex_character_classes]: https://github.com/micromatch/micromatch#regex-character-classes [micromatch]: https://github.com/micromatch/micromatch [node_js_fs_class_fs_dirent]: https://nodejs.org/api/fs.html#fs_class_fs_dirent [node_js_fs_class_fs_stats]: https://nodejs.org/api/fs.html#fs_class_fs_stats [node_js_stream_readable_streams]: https://nodejs.org/api/stream.html#stream_readable_streams [node_js]: https://nodejs.org/en [nodelib_fs_scandir_old_and_modern_modern]: https://github.com/nodelib/nodelib/blob/master/packages/fs/fs.scandir/README.md#old-and-modern-mode [npm_normalize_path]: https://www.npmjs.com/package/normalize-path [npm_unixify]: https://www.npmjs.com/package/unixify [paypal_mrmlnc]:https://paypal.me/mrmlnc [picomatch_matching_behavior]: https://github.com/micromatch/picomatch#matching-behavior-vs-bash [picomatch_matching_special_characters_as_literals]: https://github.com/micromatch/picomatch#matching-special-characters-as-literals [picomatch_posix_brackets]: https://github.com/micromatch/picomatch#posix-brackets [regular_expressions_brackets]: https://www.regular-expressions.info/brackets.html [silicon_power_ssd]: https://www.silicon-power.com/web/product-1 [unc_path]: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/62e862f4-2a51-452e-8eeb-dc4ff5ee33cc [vultr_pricing_baremetal]: https://www.vultr.com/pricing/baremetal [wikipedia_case_sensitivity]: https://en.wikipedia.org/wiki/Case_sensitivity [zotac_bi323]: https://www.zotac.com/ee/product/mini_pcs/zbox-bi323 fast-glob-3.2.12/fixtures/000077500000000000000000000000001430655000000153175ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/.directory/000077500000000000000000000000001430655000000174015ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/.directory/file.md000066400000000000000000000000001430655000000206300ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/.file000066400000000000000000000000001430655000000162250ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/file.md000066400000000000000000000000001430655000000165460ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/first/000077500000000000000000000000001430655000000164465ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/first/file.md000066400000000000000000000000001430655000000176750ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/first/nested/000077500000000000000000000000001430655000000177305ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/first/nested/directory/000077500000000000000000000000001430655000000217345ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/first/nested/directory/file.json000066400000000000000000000000001430655000000235340ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/first/nested/directory/file.md000066400000000000000000000000001430655000000231630ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/first/nested/file.md000066400000000000000000000000001430655000000211570ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/second/000077500000000000000000000000001430655000000165725ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/second/file.md000066400000000000000000000000001430655000000200210ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/second/nested/000077500000000000000000000000001430655000000200545ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/second/nested/directory/000077500000000000000000000000001430655000000220605ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/second/nested/directory/file.md000066400000000000000000000000001430655000000233070ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/second/nested/file.md000066400000000000000000000000001430655000000213030ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/third/000077500000000000000000000000001430655000000164315ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/third/library/000077500000000000000000000000001430655000000200755ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/third/library/a/000077500000000000000000000000001430655000000203155ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/third/library/a/book.md000066400000000000000000000000001430655000000215570ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/third/library/b/000077500000000000000000000000001430655000000203165ustar00rootroot00000000000000fast-glob-3.2.12/fixtures/third/library/b/book.md000066400000000000000000000000001430655000000215600ustar00rootroot00000000000000fast-glob-3.2.12/package.json000066400000000000000000000074231430655000000157420ustar00rootroot00000000000000{ "name": "fast-glob", "version": "3.2.12", "description": "It's a very fast and efficient glob library for Node.js", "license": "MIT", "repository": "mrmlnc/fast-glob", "author": { "name": "Denis Malinochkin", "url": "https://mrmlnc.com" }, "engines": { "node": ">=8.6.0" }, "main": "out/index.js", "typings": "out/index.d.ts", "files": [ "out", "!out/{benchmark,tests}", "!out/**/*.map", "!out/**/*.spec.*" ], "keywords": [ "glob", "patterns", "fast", "implementation" ], "devDependencies": { "@nodelib/fs.macchiato": "^1.0.1", "@types/compute-stdev": "^1.0.0", "@types/easy-table": "^0.0.32", "@types/glob": "^7.1.1", "@types/glob-parent": "^5.1.0", "@types/is-ci": "^2.0.0", "@types/merge2": "^1.1.4", "@types/micromatch": "^4.0.0", "@types/minimist": "^1.2.0", "@types/mocha": "^5.2.7", "@types/node": "^12.7.8", "@types/rimraf": "^2.0.2", "@types/sinon": "^7.5.0", "compute-stdev": "^1.0.0", "easy-table": "^1.1.1", "eslint": "^6.5.1", "eslint-config-mrmlnc": "^1.1.0", "execa": "^2.0.4", "fast-glob": "^3.0.4", "fdir": "^5.1.0", "glob": "^7.1.4", "is-ci": "^2.0.0", "log-update": "^4.0.0", "minimist": "^1.2.0", "mocha": "^6.2.1", "rimraf": "^3.0.0", "sinon": "^7.5.0", "tiny-glob": "^0.2.6", "typescript": "^3.6.3" }, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" }, "scripts": { "clean": "rimraf out", "lint": "eslint \"src/**/*.ts\" --cache", "compile": "tsc", "test": "mocha \"out/**/*.spec.js\" -s 0", "smoke": "mocha \"out/**/*.smoke.js\" -s 0", "smoke:sync": "mocha \"out/**/*.smoke.js\" -s 0 --grep \"\\(sync\\)\"", "smoke:async": "mocha \"out/**/*.smoke.js\" -s 0 --grep \"\\(async\\)\"", "smoke:stream": "mocha \"out/**/*.smoke.js\" -s 0 --grep \"\\(stream\\)\"", "build": "npm run clean && npm run compile && npm run lint && npm test", "watch": "npm run clean && npm run compile -- --sourceMap --watch", "bench": "npm run bench-async && npm run bench-stream && npm run bench-sync", "bench-async": "npm run bench-async-flatten && npm run bench-async-deep && npm run bench-async-partial-flatten && npm run bench-async-partial-deep", "bench-stream": "npm run bench-stream-flatten && npm run bench-stream-deep && npm run bench-stream-partial-flatten && npm run bench-stream-partial-deep", "bench-sync": "npm run bench-sync-flatten && npm run bench-sync-deep && npm run bench-sync-partial-flatten && npm run bench-sync-partial-deep", "bench-async-flatten": "node ./out/benchmark --mode async --pattern \"*\"", "bench-async-deep": "node ./out/benchmark --mode async --pattern \"**\"", "bench-async-partial-flatten": "node ./out/benchmark --mode async --pattern \"{fixtures,out}/{first,second}/*\"", "bench-async-partial-deep": "node ./out/benchmark --mode async --pattern \"{fixtures,out}/**\"", "bench-stream-flatten": "node ./out/benchmark --mode stream --pattern \"*\"", "bench-stream-deep": "node ./out/benchmark --mode stream --pattern \"**\"", "bench-stream-partial-flatten": "node ./out/benchmark --mode stream --pattern \"{fixtures,out}/{first,second}/*\"", "bench-stream-partial-deep": "node ./out/benchmark --mode stream --pattern \"{fixtures,out}/**\"", "bench-sync-flatten": "node ./out/benchmark --mode sync --pattern \"*\"", "bench-sync-deep": "node ./out/benchmark --mode sync --pattern \"**\"", "bench-sync-partial-flatten": "node ./out/benchmark --mode sync --pattern \"{fixtures,out}/{first,second}/*\"", "bench-sync-partial-deep": "node ./out/benchmark --mode sync --pattern \"{fixtures,out}/**\"" } } fast-glob-3.2.12/src/000077500000000000000000000000001430655000000142355ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/000077500000000000000000000000001430655000000161675ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/index.ts000066400000000000000000000025441430655000000176530ustar00rootroot00000000000000import * as minimist from 'minimist'; import * as logger from './logger'; import Runner, { RunnerOptions } from './runner'; import * as utils from './utils'; const PROCESS_FIRST_ARGUMENT_INDEX = 2; const DEFAULT_BENCHMARK_LAUNCHES = 10; const DEFAULT_BENCHMARK_MAX_STDEV = 3; const DEFAULT_BENCHMARK_RETRIES = 5; type Arguments = RunnerOptions & { basedir: string; }; const defaultArgv: Arguments = { basedir: '.', type: utils.getEnvironmentAsString('BENCHMARK_TYPE', 'product'), mode: utils.getEnvironmentAsString('BENCHMARK_MODE', 'async'), pattern: utils.getEnvironmentAsString('BENCHMARK_PATTERN', '*'), launches: utils.getEnvironmentAsInteger('BENCHMARK_LAUNCHES', DEFAULT_BENCHMARK_LAUNCHES), maxStdev: utils.getEnvironmentAsInteger('BENCHMARK_MAX_STDEV', DEFAULT_BENCHMARK_MAX_STDEV), retries: utils.getEnvironmentAsInteger('BENCHMARK_RETRIES', DEFAULT_BENCHMARK_RETRIES), options: utils.getEnvironmentAsObject('BENCHMARK_OPTIONS', {}) }; const argv = minimist(process.argv.slice(PROCESS_FIRST_ARGUMENT_INDEX), { default: defaultArgv }); const runner = new Runner(argv.basedir, argv); logger.head(`Benchmark pattern "${argv.pattern}" with ${argv.launches} launches (${argv.type}, ${argv.mode})`); logger.head(`Max stdev: ${argv.maxStdev} | Retries: ${argv.retries} | Options: ${JSON.stringify(argv.options)}`); logger.newline(); runner.packs(); fast-glob-3.2.12/src/benchmark/logger.ts000066400000000000000000000002071430655000000200150ustar00rootroot00000000000000export function head(message: string): void { console.info('===> ' + message); } export function newline(): void { console.log(); } fast-glob-3.2.12/src/benchmark/reporter.spec.ts000066400000000000000000000016651430655000000213420ustar00rootroot00000000000000import * as assert from 'assert'; import Reporter from './reporter'; import { SuitePackResult } from './runner'; describe('Benchmark → Reporter', () => { const result: SuitePackResult = { name: 'name', errors: 0, retries: 1, entries: 1, measures: { time: { raw: [1, 1, 1], average: 1, stdev: 0, units: 'ms' }, memory: { raw: [1, 1, 1], average: 1, stdev: 0, units: 'MB' } } }; describe('.format', () => { it('should returns report', () => { const reporter = new Reporter(); reporter.row(result); const expected = [ 'Name Time, ms Time stdev, % Memory, MB Memory stdev, % Entries Errors Retries', '---- -------- ------------- ---------- --------------- ------- ------ -------', 'name 1.000 0.000 1.000 0.000 1 0 1 ', '' ].join('\n'); const actual = reporter.format(); assert.strictEqual(actual, expected); }); }); }); fast-glob-3.2.12/src/benchmark/reporter.ts000066400000000000000000000031321430655000000204000ustar00rootroot00000000000000import * as logUpdate from 'log-update'; import * as isCi from 'is-ci'; import { SuitePackResult, Measure } from './runner'; import Table = require('easy-table'); // eslint-disable-line @typescript-eslint/no-require-imports const FRACTION_DIGITS = 3; export default class Reporter { private readonly _table: Table = new Table(); private readonly _log: logUpdate.LogUpdate = logUpdate.create(process.stdout); public row(result: SuitePackResult): void { this._table.cell('Name', result.name); this._table.cell(`Time, ${result.measures.time.units}`, this._formatMeasureValue(result.measures.time)); this._table.cell('Time stdev, %', this._formatMeasureStdevValue(result.measures.time)); this._table.cell(`Memory, ${result.measures.memory.units}`, this._formatMeasureValue(result.measures.memory)); this._table.cell('Memory stdev, %', this._formatMeasureStdevValue(result.measures.memory)); this._table.cell('Entries', result.entries); this._table.cell('Errors', result.errors); this._table.cell('Retries', result.retries); this._table.newRow(); } public format(): string { return this._table.toString(); } public display(): void { if (!isCi) { this._log(this.format()); } } public reset(): void { if (isCi) { console.log(this.format()); } this._log.done(); } private _formatMeasureValue(measure: Measure): string { return this._formatMeasure(measure.average); } private _formatMeasureStdevValue(measure: Measure): string { return this._formatMeasure(measure.stdev); } private _formatMeasure(value: number): string { return value.toFixed(FRACTION_DIGITS); } } fast-glob-3.2.12/src/benchmark/runner.spec.ts000066400000000000000000000054571430655000000210140ustar00rootroot00000000000000import * as assert from 'assert'; import Runner, { RunnerOptions, SuiteMeasures, SuitePackResult } from './runner'; import Reporter from './reporter'; class RunnerFakeProcess extends Runner { public execNodeProcess(): string { return '{"matches":1,"time":1,"memory":1}'; } } class RunnerFakeProcessError extends Runner { public execNodeProcess(): string { return 'error'; } } class RunnerFakeReport extends RunnerFakeProcess { public results: SuitePackResult[] = []; public report(_: Reporter, result: SuitePackResult): void { this.results.push(result); } public getSuites(): string[] { return ['suite.js']; } } describe('Benchmark → Runner', () => { const runnerOptions: RunnerOptions = { type: 'product', mode: 'async', pattern: '*', launches: 3, maxStdev: 3, retries: 5, options: {} }; describe('.suite', () => { it('should returns measures', () => { const runner = new RunnerFakeProcess('basedir', runnerOptions); const expected: SuiteMeasures = { matches: 1, time: 1, memory: 1 }; const actual = runner.suite('suitePath'); assert.deepStrictEqual(actual, expected); }); it('should throw error', () => { const runner = new RunnerFakeProcessError('basedir', runnerOptions); assert.throws(() => runner.suite('suitePath'), /Ops! Broken suite run\./); }); }); describe('.suitePack', () => { it('should returns pack measures', () => { const runner = new RunnerFakeProcess('basedir', runnerOptions); const expected: SuitePackResult = { name: 'suitePath', errors: 0, retries: 1, entries: 1, measures: { time: { raw: [1, 1, 1], average: 1, stdev: 0, units: 'ms' }, memory: { raw: [1, 1, 1], average: 1, stdev: 0, units: 'MB' } } }; const actual = runner.suitePack('suitePath', 0); assert.deepStrictEqual(actual, expected); }); it('should returns pack measures with errors', () => { const runner = new RunnerFakeProcessError('basedir', runnerOptions); const expected: SuitePackResult = { name: 'suitePath', errors: 3, retries: 1, entries: 0, measures: { time: { raw: [0, 0, 0], average: 0, stdev: 0, units: 'ms' }, memory: { raw: [0, 0, 0], average: 0, stdev: 0, units: 'MB' } } }; const actual = runner.suitePack('suitePath', 0); assert.deepStrictEqual(actual, expected); }); }); describe('.packs', () => { it('should run pack of suites', () => { const runner = new RunnerFakeReport('basedir', runnerOptions); const expected = [{ name: 'suite.js', errors: 0, entries: 1, retries: 1, measures: { time: { raw: [1, 1, 1], average: 1, stdev: 0, units: 'ms' }, memory: { raw: [1, 1, 1], average: 1, stdev: 0, units: 'MB' } } }]; runner.packs(); assert.deepStrictEqual(runner.results, expected); }); }); }); fast-glob-3.2.12/src/benchmark/runner.ts000066400000000000000000000070731430655000000200570ustar00rootroot00000000000000import * as fs from 'fs'; import * as path from 'path'; import { Options } from '../settings'; import Reporter from './reporter'; import * as utils from './utils'; import execa = require('execa'); // eslint-disable-line @typescript-eslint/no-require-imports export type RunnerOptions = { type: string; mode: string; pattern: string; launches: number; maxStdev: number; retries: number; options: Options; }; export type SuiteMeasures = { matches: number; time: number; memory: number; }; export type Measure = { units: string; raw: number[]; average: number; stdev: number; }; export type SuitePackMeasures = { time: Measure; memory: Measure; }; export type SuitePackResult = { name: string; errors: number; entries: number; retries: number; measures: SuitePackMeasures; }; export default class Runner { constructor(private readonly _basedir: string, private readonly _options: RunnerOptions) { } public execNodeProcess(args: string[], options: Partial): string { return execa.sync('node', args, options).stdout; } /** * Runs a single suite in the child process and returns the measurements of his work. */ public suite(suitePath: string): SuiteMeasures { const environment: NodeJS.ProcessEnv = { NODE_ENV: 'production', BENCHMARK_BASE_DIR: this._basedir, BENCHMARK_PATTERN: this._options.pattern, BENCHMARK_OPTIONS: JSON.stringify(this._options.options) }; const execaOptions: execa.SyncOptions = { env: environment, extendEnv: true }; const stdout = this.execNodeProcess([suitePath], execaOptions); try { return JSON.parse(stdout) as SuiteMeasures; } catch { throw new TypeError('Ops! Broken suite run.'); } } public suitePack(suitePath: string, retries: number): SuitePackResult { const results: SuitePackResult = { name: path.basename(suitePath), errors: 0, entries: 0, retries: retries + 1, measures: this._getSuitePackMeasures() }; for (let i = 0; i < this._options.launches; i++) { try { const { matches, time, memory } = this.suite(suitePath); results.entries = matches; results.measures.time.raw.push(time); results.measures.memory.raw.push(memory); } catch { results.errors++; results.measures.time.raw.push(0); results.measures.memory.raw.push(0); } } results.measures = { time: this._getMeasures(results.measures.time.raw, 'ms'), memory: this._getMeasures(results.measures.memory.raw, 'MB') }; return results; } public report(reporter: Reporter, result: SuitePackResult): void { reporter.row(result); reporter.display(); } public packs(): void { const suitesPath = path.join(__dirname, 'suites', this._options.type, this._options.mode); const suites = this.getSuites(suitesPath); const reporter = new Reporter(); for (const filepath of suites) { const suitePath = path.join(suitesPath, filepath); let result = this.suitePack(suitePath, 0); while (result.measures.time.stdev > this._options.maxStdev && result.retries < this._options.retries) { result = this.suitePack(suitePath, result.retries); } this.report(reporter, result); } reporter.reset(); } public getSuites(suitesPath: string): string[] { return fs.readdirSync(suitesPath).filter((suite) => suite.endsWith('.js')); } private _getMeasures(raw: number[], units: string): Measure { return { units, raw, average: utils.getAverageValue(raw), stdev: utils.getStdev(raw) }; } private _getSuitePackMeasures(): SuitePackMeasures { return { time: this._getMeasures([], 'ms'), memory: this._getMeasures([], 'MB') }; } } fast-glob-3.2.12/src/benchmark/suites/000077500000000000000000000000001430655000000175035ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/suites/product/000077500000000000000000000000001430655000000211635ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/suites/product/async/000077500000000000000000000000001430655000000223005ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/suites/product/async/fast-glob.ts000066400000000000000000000010711430655000000245250ustar00rootroot00000000000000import * as path from 'path'; import * as glob from '../../../..'; import * as utils from '../../../utils'; const options: glob.Options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), unique: false }; const timeStart = utils.timeStart(); glob(process.env.BENCHMARK_PATTERN as string, options) .then((matches) => { const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); }) .catch(() => { process.exit(0); }); fast-glob-3.2.12/src/benchmark/suites/product/async/fdir.ts000066400000000000000000000012061430655000000235730ustar00rootroot00000000000000import * as path from 'path'; import { fdir as GlobBuilder, PathsOutput } from 'fdir'; import * as utils from '../../../utils'; const CWD = path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string); const PATTERN = process.env.BENCHMARK_PATTERN as string; const fdir = new GlobBuilder() .glob(PATTERN) .crawl(CWD); const timeStart = utils.timeStart(); fdir.withPromise() .then((matches) => { const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures((matches as PathsOutput).length, time, memory); console.info(measures); }) .catch(() => { process.exit(0); }); fast-glob-3.2.12/src/benchmark/suites/product/async/node-glob.ts000066400000000000000000000011241430655000000245140ustar00rootroot00000000000000import * as path from 'path'; import * as glob from 'glob'; import * as utils from '../../../utils'; const options: glob.IOptions = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), nosort: true, nounique: true, nodir: true }; const timeStart = utils.timeStart(); glob(process.env.BENCHMARK_PATTERN as string, options, (error, matches) => { if (error !== null) { process.exit(0); } const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); }); fast-glob-3.2.12/src/benchmark/suites/product/async/tiny-glob.ts000066400000000000000000000011461430655000000245560ustar00rootroot00000000000000import * as path from 'path'; import * as utils from '../../../utils'; import glob = require('tiny-glob'); // eslint-disable-line @typescript-eslint/no-require-imports const options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), flush: true }; const timeStart = utils.timeStart(); glob(process.env.BENCHMARK_PATTERN as string, options) .then((matches) => { const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); }) .catch(() => { process.exit(0); }); fast-glob-3.2.12/src/benchmark/suites/product/stream/000077500000000000000000000000001430655000000224565ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/suites/product/stream/fast-glob.ts000066400000000000000000000013041430655000000247020ustar00rootroot00000000000000import * as path from 'path'; import * as glob from '../../../..'; import * as utils from '../../../utils'; const options: glob.Options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), unique: false, objectMode: true }; const entries: string[] = []; const timeStart = utils.timeStart(); const stream = glob.stream(process.env.BENCHMARK_PATTERN as string, options); stream.once('error', () => process.exit(0)); stream.on('data', (entry: string) => entries.push(entry)); stream.once('end', () => { const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(entries.length, time, memory); console.info(measures); }); fast-glob-3.2.12/src/benchmark/suites/product/sync/000077500000000000000000000000001430655000000221375ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/suites/product/sync/fast-glob.ts000066400000000000000000000010561430655000000243670ustar00rootroot00000000000000import * as path from 'path'; import * as glob from '../../../..'; import * as utils from '../../../utils'; const options: glob.Options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), unique: false }; const timeStart = utils.timeStart(); try { const matches = glob.sync(process.env.BENCHMARK_PATTERN as string, options); const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); } catch { process.exit(0); } fast-glob-3.2.12/src/benchmark/suites/product/sync/fdir.ts000066400000000000000000000011551430655000000234350ustar00rootroot00000000000000import * as path from 'path'; import { fdir as GlobBuilder, PathsOutput } from 'fdir'; import * as utils from '../../../utils'; const CWD = path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string); const PATTERN = process.env.BENCHMARK_PATTERN as string; const glob = new GlobBuilder() .glob(PATTERN) .crawl(CWD); const timeStart = utils.timeStart(); try { const matches = glob.sync() as PathsOutput; const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); } catch { process.exit(0); } fast-glob-3.2.12/src/benchmark/suites/product/sync/node-glob.ts000066400000000000000000000011071430655000000243540ustar00rootroot00000000000000import * as path from 'path'; import * as glob from 'glob'; import * as utils from '../../../utils'; const options: glob.IOptions = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), nosort: true, nounique: true, nodir: true }; const timeStart = utils.timeStart(); try { const matches = glob.sync(process.env.BENCHMARK_PATTERN as string, options); const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); } catch { process.exit(0); } fast-glob-3.2.12/src/benchmark/suites/product/sync/tiny-glob.ts000066400000000000000000000011401430655000000244070ustar00rootroot00000000000000import * as path from 'path'; import * as utils from '../../../utils'; // eslint-disable-next-line @typescript-eslint/no-require-imports import glob = require('tiny-glob/sync'); const options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), flush: true }; const timeStart = utils.timeStart(); try { const matches = glob(process.env.BENCHMARK_PATTERN as string, options); const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); } catch { process.exit(0); } fast-glob-3.2.12/src/benchmark/suites/regression/000077500000000000000000000000001430655000000216635ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/suites/regression/async/000077500000000000000000000000001430655000000230005ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/suites/regression/async/fast-glob-current.ts000066400000000000000000000011621430655000000267060ustar00rootroot00000000000000import * as path from 'path'; import * as glob from '../../../..'; import * as utils from '../../../utils'; const options: glob.Options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), unique: false, ...JSON.parse(process.env.BENCHMARK_OPTIONS as string) }; const timeStart = utils.timeStart(); glob(process.env.BENCHMARK_PATTERN as string, options) .then((matches) => { const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); }) .catch(() => { process.exit(0); }); fast-glob-3.2.12/src/benchmark/suites/regression/async/fast-glob-previous.ts000066400000000000000000000011531430655000000271000ustar00rootroot00000000000000import * as path from 'path'; import * as fg from 'fast-glob'; import * as utils from '../../../utils'; const options: fg.Options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), unique: false, ...JSON.parse(process.env.BENCHMARK_OPTIONS as string) }; const timeStart = utils.timeStart(); fg(process.env.BENCHMARK_PATTERN as string, options) .then((matches) => { const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); }) .catch(() => { process.exit(0); }); fast-glob-3.2.12/src/benchmark/suites/regression/stream/000077500000000000000000000000001430655000000231565ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/suites/regression/stream/fast-glob-current.ts000066400000000000000000000013521430655000000270650ustar00rootroot00000000000000import * as path from 'path'; import * as glob from '../../../..'; import * as utils from '../../../utils'; const options: glob.Options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), unique: false, ...JSON.parse(process.env.BENCHMARK_OPTIONS as string) }; const entries: string[] = []; const timeStart = utils.timeStart(); const stream = glob.stream(process.env.BENCHMARK_PATTERN as string, options); stream.once('error', () => process.exit(0)); stream.on('data', (entry: string) => entries.push(entry)); stream.once('end', () => { const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(entries.length, time, memory); console.info(measures); }); fast-glob-3.2.12/src/benchmark/suites/regression/stream/fast-glob-previous.ts000066400000000000000000000013431430655000000272570ustar00rootroot00000000000000import * as path from 'path'; import * as fg from 'fast-glob'; import * as utils from '../../../utils'; const options: fg.Options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), unique: false, ...JSON.parse(process.env.BENCHMARK_OPTIONS as string) }; const entries: string[] = []; const timeStart = utils.timeStart(); const stream = fg.stream(process.env.BENCHMARK_PATTERN as string, options); stream.once('error', () => process.exit(0)); stream.on('data', (entry: string) => entries.push(entry)); stream.once('end', () => { const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(entries.length, time, memory); console.info(measures); }); fast-glob-3.2.12/src/benchmark/suites/regression/sync/000077500000000000000000000000001430655000000226375ustar00rootroot00000000000000fast-glob-3.2.12/src/benchmark/suites/regression/sync/fast-glob-current.ts000066400000000000000000000011471430655000000265500ustar00rootroot00000000000000import * as path from 'path'; import * as glob from '../../../..'; import * as utils from '../../../utils'; const options: glob.Options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), unique: false, ...JSON.parse(process.env.BENCHMARK_OPTIONS as string) }; const timeStart = utils.timeStart(); try { const matches = glob.sync(process.env.BENCHMARK_PATTERN as string, options); const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); } catch { process.exit(0); } fast-glob-3.2.12/src/benchmark/suites/regression/sync/fast-glob-previous.ts000066400000000000000000000011401430655000000267330ustar00rootroot00000000000000import * as path from 'path'; import * as fg from 'fast-glob'; import * as utils from '../../../utils'; const options: fg.Options = { cwd: path.join(process.cwd(), process.env.BENCHMARK_BASE_DIR as string), unique: false, ...JSON.parse(process.env.BENCHMARK_OPTIONS as string) }; const timeStart = utils.timeStart(); try { const matches = fg.sync(process.env.BENCHMARK_PATTERN as string, options); const memory = utils.getMemory(); const time = utils.timeEnd(timeStart); const measures = utils.formatMeasures(matches.length, time, memory); console.info(measures); } catch { process.exit(0); } fast-glob-3.2.12/src/benchmark/utils.spec.ts000066400000000000000000000072611430655000000206360ustar00rootroot00000000000000import * as assert from 'assert'; import * as utils from './utils'; describe('Benchmark → Utils', () => { const oldProcessHrtime = process.hrtime; const oldProcessMemoryUsage = process.memoryUsage; before(() => { process.env.FG_TEST_ENV_INTEGER = '1'; process.env.FG_TEST_ENV_OBJECT = '{ "value": true }'; process.hrtime = (() => [0, 1e7]) as NodeJS.HRTime; process.memoryUsage = () => ({ external: 0, rss: 0, heapTotal: 0, heapUsed: 10 * 1e6 }); }); after(() => { delete process.env.FG_TEST_ENV_INTEGER; delete process.env.FG_TEST_ENV_OBJECT; process.hrtime = oldProcessHrtime; process.memoryUsage = oldProcessMemoryUsage; }); describe('.convertHrtimeToMilliseconds', () => { it('should return milliseconds', () => { const hrtime: [number, number] = [0, 1e7]; const expected = 10; const actual = utils.convertHrtimeToMilliseconds(hrtime); assert.strictEqual(actual, expected); }); }); describe('.convertBytesToMegaBytes', () => { it('should return megabytes', () => { const expected = 1; const actual = utils.convertBytesToMegaBytes(1e6); assert.strictEqual(actual, expected); }); }); describe('.timeStart', () => { it('should return hrtime', () => { const expected: [number, number] = [0, 1e7]; const actual = utils.timeStart(); assert.deepStrictEqual(actual, expected); }); }); describe('.timeEnd', () => { it('should return diff between hrtime\'s', () => { const expected = 10; const actual = utils.timeEnd([0, 1e7]); assert.strictEqual(actual, expected); }); }); describe('.getMemory', () => { it('should return memory usage in megabytes', () => { const expected = 10; const actual = utils.getMemory(); assert.strictEqual(actual, expected); }); }); describe('.getMeasures', () => { it('should return measures', () => { const expected = '{"matches":1,"time":1,"memory":1}'; const actual = utils.formatMeasures(1, 1, 1); assert.strictEqual(actual, expected); }); }); describe('.getAverageValue', () => { it('should return average value for array', () => { const expected = 2; const actual = utils.getAverageValue([3, 1, 2]); assert.strictEqual(actual, expected); }); }); describe('.getStdev', () => { it('should return stdev for array', () => { const expected = 1; const actual = utils.getStdev([1, 2, 3]); assert.strictEqual(actual, expected); }); }); describe('.getEnvironmentAsString', () => { it('should return string', () => { const expected = 'text'; const actual = utils.getEnvironmentAsString('FG_TEST_ENV_STRING', 'text'); assert.strictEqual(actual, expected); }); it('should return default value', () => { const expected = ''; const actual = utils.getEnvironmentAsString('NON_EXIST_ENV_VARIABLE', ''); assert.strictEqual(actual, expected); }); }); describe('.getEnvironmentAsInteger', () => { it('should return integer', () => { const expected = 1; const actual = utils.getEnvironmentAsInteger('FG_TEST_ENV_INTEGER', 0); assert.strictEqual(actual, expected); }); it('should return default value', () => { const expected = 0; const actual = utils.getEnvironmentAsInteger('NON_EXIST_ENV_VARIABLE', 0); assert.strictEqual(actual, expected); }); }); describe('.getEnvironmentAsObject', () => { it('should return object', () => { const expected = { value: true }; const actual = utils.getEnvironmentAsObject('FG_TEST_ENV_OBJECT', {}); assert.deepStrictEqual(actual, expected); }); it('should return default value', () => { const expected = {}; const actual = utils.getEnvironmentAsObject('NON_EXIST_ENV_VARIABLE', {}); assert.deepStrictEqual(actual, expected); }); }); }); fast-glob-3.2.12/src/benchmark/utils.ts000066400000000000000000000034621430655000000177040ustar00rootroot00000000000000import { SuiteMeasures } from './runner'; import stdev = require('compute-stdev'); // eslint-disable-line @typescript-eslint/no-require-imports const NANOSECONDS_IN_SECOND = 1e9; const MICROSECONDS_IN_SECOND = 1e6; const BYTES_IN_MEGABYTE = 1e6; export function convertHrtimeToMilliseconds(hrtime: [number, number]): number { const nanoseconds = hrtime[0] * NANOSECONDS_IN_SECOND; return (nanoseconds + hrtime[1]) / MICROSECONDS_IN_SECOND; } export function convertBytesToMegaBytes(bytes: number): number { return bytes / MICROSECONDS_IN_SECOND; } export function timeStart(): [number, number] { return process.hrtime(); } export function timeEnd(start: [number, number]): number { const hrtime = process.hrtime(start); return convertHrtimeToMilliseconds(hrtime); } export function getMemory(): number { return process.memoryUsage().heapUsed / BYTES_IN_MEGABYTE; } export function formatMeasures(matches: number, time: number, memory: number): string { const measures: SuiteMeasures = { matches, time, memory }; return JSON.stringify(measures); } export function getAverageValue(values: number[]): number { return values.reduce((a, b) => a + b, 0) / values.length; } export function getStdev(values: number[]): number { return stdev(values); } export function getEnvironmentAsString(name: string, value: string): string { const environment = process.env[name]; return environment === undefined ? value : environment; } export function getEnvironmentAsInteger(name: string, value: number): number { const environment = process.env[name]; return environment === undefined ? value : parseInt(environment, 10); } export function getEnvironmentAsObject(name: string, value: object): object { const environment = process.env[name]; return environment === undefined ? value : JSON.parse(environment) as object; } fast-glob-3.2.12/src/index.spec.ts000066400000000000000000000150611430655000000166500ustar00rootroot00000000000000import * as assert from 'assert'; import * as tests from './tests'; import { EntryItem, ErrnoException } from './types'; import * as fg from '.'; describe('Package', () => { describe('.sync', () => { it('should throw an error when input values can not pass validation', () => { const message = 'Patterns must be a string (non empty) or an array of strings'; // eslint-disable-next-line @typescript-eslint/no-explicit-any assert.throws(() => fg.sync(null as any), { message }); assert.throws(() => fg.sync(''), { message }); }); it('should returns entries', () => { const expected: EntryItem[] = [ 'fixtures/file.md', 'fixtures/first/file.md', 'fixtures/first/nested/directory/file.md', 'fixtures/first/nested/file.md', 'fixtures/second/file.md', 'fixtures/second/nested/directory/file.md', 'fixtures/second/nested/file.md', 'fixtures/third/library/a/book.md', 'fixtures/third/library/b/book.md' ]; const actual = fg.sync(['fixtures/**/*.md']); actual.sort((a, b) => a.localeCompare(b)); assert.deepStrictEqual(actual, expected); }); it('should returns entries (two sources)', () => { const expected: EntryItem[] = [ 'fixtures/first/file.md', 'fixtures/first/nested/directory/file.md', 'fixtures/first/nested/file.md', 'fixtures/second/file.md', 'fixtures/second/nested/directory/file.md', 'fixtures/second/nested/file.md' ]; const actual = fg.sync(['fixtures/first/**/*.md', 'fixtures/second/**/*.md']); actual.sort((a, b) => a.localeCompare(b)); assert.deepStrictEqual(actual, expected); }); }); describe('.async', () => { it('should throw an error when input values can not pass validation', async () => { const message = 'Patterns must be a string (non empty) or an array of strings'; // eslint-disable-next-line @typescript-eslint/no-explicit-any await assert.rejects(() => fg(null as any), { message }); await assert.rejects(() => fg(''), { message }); }); it('should returns entries', async () => { const expected: EntryItem[] = [ 'fixtures/file.md', 'fixtures/first/file.md', 'fixtures/first/nested/directory/file.md', 'fixtures/first/nested/file.md', 'fixtures/second/file.md', 'fixtures/second/nested/directory/file.md', 'fixtures/second/nested/file.md', 'fixtures/third/library/a/book.md', 'fixtures/third/library/b/book.md' ]; const actual = await fg(['fixtures/**/*.md']); actual.sort((a, b) => a.localeCompare(b)); assert.deepStrictEqual(actual, expected); }); it('should returns entries (two sources)', async () => { const expected: EntryItem[] = [ 'fixtures/first/file.md', 'fixtures/first/nested/directory/file.md', 'fixtures/first/nested/file.md', 'fixtures/second/file.md', 'fixtures/second/nested/directory/file.md', 'fixtures/second/nested/file.md' ]; const actual = await fg(['fixtures/first/**/*.md', 'fixtures/second/**/*.md']); actual.sort((a, b) => a.localeCompare(b)); assert.deepStrictEqual(actual, expected); }); }); describe('.stream', () => { it('should throw an error when input values can not pass validation', () => { const message = 'Patterns must be a string (non empty) or an array of strings'; // eslint-disable-next-line @typescript-eslint/no-explicit-any assert.throws(() => fg.stream(null as any), { message }); assert.throws(() => fg.stream(''), { message }); }); it('should returns entries', (done) => { const expected: string[] = [ 'fixtures/file.md', 'fixtures/first/file.md', 'fixtures/first/nested/directory/file.md', 'fixtures/first/nested/file.md', 'fixtures/second/file.md', 'fixtures/second/nested/directory/file.md', 'fixtures/second/nested/file.md', 'fixtures/third/library/a/book.md', 'fixtures/third/library/b/book.md' ]; const actual: string[] = []; const stream = fg.stream(['fixtures/**/*.md']); stream.on('data', (entry: string) => actual.push(entry)); stream.once('error', (error: ErrnoException) => assert.fail(error)); stream.once('end', () => { actual.sort((a, b) => a.localeCompare(b)); assert.deepStrictEqual(actual, expected); done(); }); }); it('should returns entries (two sources)', (done) => { const expected: EntryItem[] = [ 'fixtures/first/file.md', 'fixtures/first/nested/directory/file.md', 'fixtures/first/nested/file.md', 'fixtures/second/file.md', 'fixtures/second/nested/directory/file.md', 'fixtures/second/nested/file.md' ]; const actual: string[] = []; const stream = fg.stream(['fixtures/first/**/*.md', 'fixtures/second/**/*.md']); stream.on('data', (entry: string) => actual.push(entry)); stream.once('error', (error: ErrnoException) => assert.fail(error)); stream.once('end', () => { actual.sort((a, b) => a.localeCompare(b)); assert.deepStrictEqual(actual, expected); done(); }); }); }); describe('.generateTasks', () => { it('should throw an error when input values can not pass validation', () => { const message = 'Patterns must be a string (non empty) or an array of strings'; // eslint-disable-next-line @typescript-eslint/no-explicit-any assert.throws(() => fg.generateTasks(null as any), { message }); assert.throws(() => fg.generateTasks(''), { message }); }); it('should return tasks', () => { const expected = [ tests.task.builder().base('.').positive('*').build() ]; const actual = fg.generateTasks(['*']); assert.deepStrictEqual(actual, expected); }); it('should return tasks with negative patterns', () => { const expected = [ tests.task.builder().base('.').positive('*').negative('*.txt').negative('*.md').build() ]; const actual = fg.generateTasks(['*', '!*.txt'], { ignore: ['*.md'] }); assert.deepStrictEqual(actual, expected); }); it('should clean up patterns', () => { const expected = [ // Clean up duplicate slashes tests.task.builder().base('fixtures').positive('fixtures/*').build() ]; const actual = fg.generateTasks(['fixtures//*']); assert.deepStrictEqual(actual, expected); }); }); describe('.isDynamicPattern', () => { it('should return true for dynamic pattern', () => { assert.ok(fg.isDynamicPattern('*')); }); it('should return false for static pattern', () => { assert.ok(!fg.isDynamicPattern('abc')); }); }); describe('.escapePath', () => { it('should return escaped path', () => { const expected = 'C:/Program Files \\(x86\\)'; const actual = fg.escapePath('C:/Program Files (x86)'); assert.strictEqual(actual, expected); }); }); }); fast-glob-3.2.12/src/index.ts000066400000000000000000000101171430655000000157140ustar00rootroot00000000000000import * as taskManager from './managers/tasks'; import * as patternManager from './managers/patterns'; import ProviderAsync from './providers/async'; import Provider from './providers/provider'; import ProviderStream from './providers/stream'; import ProviderSync from './providers/sync'; import Settings, { Options as OptionsInternal } from './settings'; import { Entry as EntryInternal, EntryItem, FileSystemAdapter as FileSystemAdapterInternal, Pattern as PatternInternal } from './types'; import * as utils from './utils'; type EntryObjectModePredicate = { [TKey in keyof Pick]-?: true }; type EntryStatsPredicate = { [TKey in keyof Pick]-?: true }; type EntryObjectPredicate = EntryObjectModePredicate | EntryStatsPredicate; function FastGlob(source: PatternInternal | PatternInternal[], options: OptionsInternal & EntryObjectPredicate): Promise; function FastGlob(source: PatternInternal | PatternInternal[], options?: OptionsInternal): Promise; async function FastGlob(source: PatternInternal | PatternInternal[], options?: OptionsInternal): Promise { assertPatternsInput(source); const works = getWorks(source, ProviderAsync, options); const result = await Promise.all(works); return utils.array.flatten(result); } // https://github.com/typescript-eslint/typescript-eslint/issues/60 // eslint-disable-next-line no-redeclare namespace FastGlob { export type Options = OptionsInternal; export type Entry = EntryInternal; export type Task = taskManager.Task; export type Pattern = PatternInternal; export type FileSystemAdapter = FileSystemAdapterInternal; export function sync(source: PatternInternal | PatternInternal[], options: OptionsInternal & EntryObjectPredicate): EntryInternal[]; export function sync(source: PatternInternal | PatternInternal[], options?: OptionsInternal): string[]; export function sync(source: PatternInternal | PatternInternal[], options?: OptionsInternal): EntryItem[] { assertPatternsInput(source); const works = getWorks(source, ProviderSync, options); return utils.array.flatten(works); } export function stream(source: PatternInternal | PatternInternal[], options?: OptionsInternal): NodeJS.ReadableStream { assertPatternsInput(source); const works = getWorks(source, ProviderStream, options); /** * The stream returned by the provider cannot work with an asynchronous iterator. * To support asynchronous iterators, regardless of the number of tasks, we always multiplex streams. * This affects performance (+25%). I don't see best solution right now. */ return utils.stream.merge(works); } export function generateTasks(source: PatternInternal | PatternInternal[], options?: OptionsInternal): Task[] { assertPatternsInput(source); const patterns = patternManager.transform(([] as PatternInternal[]).concat(source)); const settings = new Settings(options); return taskManager.generate(patterns, settings); } export function isDynamicPattern(source: PatternInternal, options?: OptionsInternal): boolean { assertPatternsInput(source); const settings = new Settings(options); return utils.pattern.isDynamicPattern(source, settings); } export function escapePath(source: PatternInternal): PatternInternal { assertPatternsInput(source); return utils.path.escape(source); } } function getWorks(source: PatternInternal | PatternInternal[], _Provider: new (settings: Settings) => Provider, options?: OptionsInternal): T[] { const patterns = patternManager.transform(([] as PatternInternal[]).concat(source)); const settings = new Settings(options); const tasks = taskManager.generate(patterns, settings); const provider = new _Provider(settings); return tasks.map(provider.read, provider); } function assertPatternsInput(input: unknown): void | never { const source = ([] as unknown[]).concat(input); const isValidSource = source.every((item) => utils.string.isString(item) && !utils.string.isEmpty(item)); if (!isValidSource) { throw new TypeError('Patterns must be a string (non empty) or an array of strings'); } } export = FastGlob; fast-glob-3.2.12/src/managers/000077500000000000000000000000001430655000000160325ustar00rootroot00000000000000fast-glob-3.2.12/src/managers/patterns.spec.ts000066400000000000000000000033521430655000000211760ustar00rootroot00000000000000import * as assert from 'assert'; import * as manager from './patterns'; describe('Managers → Pattern', () => { describe('.transform', () => { it('should transform patterns with duplicate slashes', () => { const expected = ['a/b', 'b/c']; const actual = manager.transform(['a/b', 'b//c']); assert.deepStrictEqual(actual, expected); }); }); describe('.removeDuplicateSlashes', () => { it('should do not change patterns', () => { const action = manager.removeDuplicateSlashes; assert.strictEqual(action('directory/file.md'), 'directory/file.md'); assert.strictEqual(action('files{.txt,/file.md}'), 'files{.txt,/file.md}'); }); it('should do not change the device path in patterns with UNC parts', () => { const action = manager.removeDuplicateSlashes; assert.strictEqual(action('//?//D://'), '//?/D:/'); assert.strictEqual(action('//.//D:///'), '//./D:/'); assert.strictEqual(action('//LOCALHOST//d$//'), '//LOCALHOST/d$/'); assert.strictEqual(action('//127.0.0.1///d$//'), '//127.0.0.1/d$/'); assert.strictEqual(action('//./UNC////LOCALHOST///d$//'), '//./UNC/LOCALHOST/d$/'); }); it('should remove duplicate slashes in the middle and the of the pattern', () => { const action = manager.removeDuplicateSlashes; assert.strictEqual(action('a//b'), 'a/b'); assert.strictEqual(action('b///c'), 'b/c'); assert.strictEqual(action('c/d///'), 'c/d/'); assert.strictEqual(action('//?//D://'), '//?/D:/'); }); it('should form double slashes at the beginning of the pattern', () => { const action = manager.removeDuplicateSlashes; assert.strictEqual(action('///*'), '//*'); assert.strictEqual(action('////?'), '//?'); assert.strictEqual(action('///?/D:/'), '//?/D:/'); }); }); }); fast-glob-3.2.12/src/managers/patterns.ts000066400000000000000000000014131430655000000202410ustar00rootroot00000000000000/** * Matches a sequence of two or more consecutive slashes, excluding the first two slashes at the beginning of the string. * The latter is due to the presence of the device path at the beginning of the UNC path. * @todo rewrite to negative lookbehind with the next major release. */ const DOUBLE_SLASH_RE = /(?!^)\/{2,}/g; export function transform(patterns: string[]): string[] { return patterns.map((pattern) => removeDuplicateSlashes(pattern)); } /** * This package only works with forward slashes as a path separator. * Because of this, we cannot use the standard `path.normalize` method, because on Windows platform it will use of backslashes. */ export function removeDuplicateSlashes(pattern: string): string { return pattern.replace(DOUBLE_SLASH_RE, '/'); } fast-glob-3.2.12/src/managers/tasks.spec.ts000066400000000000000000000130011430655000000204530ustar00rootroot00000000000000import * as assert from 'assert'; import Settings from '../settings'; import * as tests from '../tests'; import { PatternsGroup } from '../types'; import * as manager from './tasks'; describe('Managers → Task', () => { describe('.generate', () => { it('should return task with negative patterns from «ignore» option', () => { const settings = new Settings({ ignore: ['*.txt'] }); const expected = [ tests.task.builder().base('a').positive('a/*').negative('*.md').negative('*.txt').build() ]; const actual = manager.generate(['a/*', '!*.md'], settings); assert.deepStrictEqual(actual, expected); }); it('should return static and dynamic tasks', () => { const settings = new Settings({ ignore: ['*.txt'] }); const expected = [ tests.task.builder().base('a').static().positive('a/file.json').negative('b/*.md').negative('*.txt').build(), tests.task.builder().base('b').positive('b/*').negative('b/*.md').negative('*.txt').build() ]; const actual = manager.generate(['a/file.json', 'b/*', '!b/*.md'], settings); assert.deepStrictEqual(actual, expected); }); it('should return only dynamic tasks when the `caseSensitiveMatch` option is enabled', () => { const settings = new Settings({ caseSensitiveMatch: false }); const expected = [ tests.task.builder().base('a').positive('a/file.json').negative('b/*.md').build(), tests.task.builder().base('b').positive('b/*').negative('b/*.md').build() ]; const actual = manager.generate(['a/file.json', 'b/*', '!b/*.md'], settings); assert.deepStrictEqual(actual, expected); }); }); describe('.convertPatternsToTasks', () => { it('should return one task when positive patterns have a global pattern', () => { const expected = [ tests.task.builder().base('.').positive('*').negative('*.md').build() ]; const actual = manager.convertPatternsToTasks(['*'], ['*.md'], /* dynamic */ true); assert.deepStrictEqual(actual, expected); }); it('should return two tasks when one of patterns contains reference to the parent directory', () => { const expected = [ tests.task.builder().base('..').positive('../*.md').negative('*.md').build(), tests.task.builder().base('.').positive('*').positive('a/*').negative('*.md').build() ]; const actual = manager.convertPatternsToTasks(['*', 'a/*', '../*.md'], ['*.md'], /* dynamic */ true); console.dir(actual, { colors: true }); assert.deepStrictEqual(actual, expected); }); it('should return two tasks when all patterns refers to the different base directories', () => { const expected = [ tests.task.builder().base('a').positive('a/*').negative('b/*.md').build(), tests.task.builder().base('b').positive('b/*').negative('b/*.md').build() ]; const actual = manager.convertPatternsToTasks(['a/*', 'b/*'], ['b/*.md'], /* dynamic */ true); assert.deepStrictEqual(actual, expected); }); }); describe('.getPositivePatterns', () => { it('should return only positive patterns', () => { const expected = ['*']; const actual = manager.getPositivePatterns(['*', '!*.md']); assert.deepStrictEqual(actual, expected); }); }); describe('.getNegativePatternsAsPositive', () => { it('should return negative patterns as positive', () => { const expected = ['*.md']; const actual = manager.getNegativePatternsAsPositive(['*', '!*.md'], []); assert.deepStrictEqual(actual, expected); }); it('should return negative patterns as positive with patterns from ignore option', () => { const expected = ['*.md', '*.txt', '*.json']; const actual = manager.getNegativePatternsAsPositive(['*', '!*.md'], ['*.txt', '!*.json']); assert.deepStrictEqual(actual, expected); }); }); describe('.groupPatternsByBaseDirectory', () => { it('should return empty object', () => { const expected: PatternsGroup = {}; const actual = manager.groupPatternsByBaseDirectory([]); assert.deepStrictEqual(actual, expected); }); it('should return grouped patterns', () => { const expected: PatternsGroup = { '.': ['*'], a: ['a/*'] }; const actual = manager.groupPatternsByBaseDirectory(['*', 'a/*']); assert.deepStrictEqual(actual, expected); }); }); describe('.convertPatternGroupsToTasks', () => { it('should return two tasks', () => { const expected = [ tests.task.builder().base('a').positive('a/*').negative('b/*.md').build(), tests.task.builder().base('b').positive('b/*').negative('b/*.md').build() ]; const actual = manager.convertPatternGroupsToTasks({ a: ['a/*'], b: ['b/*'] }, ['b/*.md'], /* dynamic */ true); assert.deepStrictEqual(actual, expected); }); }); describe('.convertPatternGroupToTask', () => { it('should return created dynamic task', () => { const expected = tests.task.builder().base('.').positive('*').negative('*.md').build(); const actual = manager.convertPatternGroupToTask('.', ['*'], ['*.md'], /* dynamic */ true); assert.deepStrictEqual(actual, expected); }); it('should return created static task', () => { const expected = tests.task.builder().base('.').static().positive('.gitignore').negative('.git*').build(); const actual = manager.convertPatternGroupToTask('.', ['.gitignore'], ['.git*'], /* dynamic */ false); assert.deepStrictEqual(actual, expected); }); it('should normalize the base path', () => { const expected = tests.task.builder().base('root/directory').build(); const actual = manager.convertPatternGroupToTask('root/directory', [], [], /* dynamic */ true); assert.deepStrictEqual(actual, expected); }); }); }); fast-glob-3.2.12/src/managers/tasks.ts000066400000000000000000000071121430655000000175300ustar00rootroot00000000000000import Settings from '../settings'; import { Pattern, PatternsGroup } from '../types'; import * as utils from '../utils'; export type Task = { base: string; dynamic: boolean; patterns: Pattern[]; positive: Pattern[]; negative: Pattern[]; }; export function generate(patterns: Pattern[], settings: Settings): Task[] { const positivePatterns = getPositivePatterns(patterns); const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); const staticPatterns = positivePatterns.filter((pattern) => utils.pattern.isStaticPattern(pattern, settings)); const dynamicPatterns = positivePatterns.filter((pattern) => utils.pattern.isDynamicPattern(pattern, settings)); const staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, /* dynamic */ false); const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, /* dynamic */ true); return staticTasks.concat(dynamicTasks); } /** * Returns tasks grouped by basic pattern directories. * * Patterns that can be found inside (`./`) and outside (`../`) the current directory are handled separately. * This is necessary because directory traversal starts at the base directory and goes deeper. */ export function convertPatternsToTasks(positive: Pattern[], negative: Pattern[], dynamic: boolean): Task[] { const tasks: Task[] = []; const patternsOutsideCurrentDirectory = utils.pattern.getPatternsOutsideCurrentDirectory(positive); const patternsInsideCurrentDirectory = utils.pattern.getPatternsInsideCurrentDirectory(positive); const outsideCurrentDirectoryGroup = groupPatternsByBaseDirectory(patternsOutsideCurrentDirectory); const insideCurrentDirectoryGroup = groupPatternsByBaseDirectory(patternsInsideCurrentDirectory); tasks.push(...convertPatternGroupsToTasks(outsideCurrentDirectoryGroup, negative, dynamic)); /* * For the sake of reducing future accesses to the file system, we merge all tasks within the current directory * into a global task, if at least one pattern refers to the root (`.`). In this case, the global task covers the rest. */ if ('.' in insideCurrentDirectoryGroup) { tasks.push(convertPatternGroupToTask('.', patternsInsideCurrentDirectory, negative, dynamic)); } else { tasks.push(...convertPatternGroupsToTasks(insideCurrentDirectoryGroup, negative, dynamic)); } return tasks; } export function getPositivePatterns(patterns: Pattern[]): Pattern[] { return utils.pattern.getPositivePatterns(patterns); } export function getNegativePatternsAsPositive(patterns: Pattern[], ignore: Pattern[]): Pattern[] { const negative = utils.pattern.getNegativePatterns(patterns).concat(ignore); const positive = negative.map(utils.pattern.convertToPositivePattern); return positive; } export function groupPatternsByBaseDirectory(patterns: Pattern[]): PatternsGroup { const group: PatternsGroup = {}; return patterns.reduce((collection, pattern) => { const base = utils.pattern.getBaseDirectory(pattern); if (base in collection) { collection[base].push(pattern); } else { collection[base] = [pattern]; } return collection; }, group); } export function convertPatternGroupsToTasks(positive: PatternsGroup, negative: Pattern[], dynamic: boolean): Task[] { return Object.keys(positive).map((base) => { return convertPatternGroupToTask(base, positive[base], negative, dynamic); }); } export function convertPatternGroupToTask(base: string, positive: Pattern[], negative: Pattern[], dynamic: boolean): Task { return { dynamic, positive, negative, base, patterns: ([] as Pattern[]).concat(positive, negative.map(utils.pattern.convertToNegativePattern)) }; } fast-glob-3.2.12/src/providers/000077500000000000000000000000001430655000000162525ustar00rootroot00000000000000fast-glob-3.2.12/src/providers/async.spec.ts000066400000000000000000000050461430655000000206750ustar00rootroot00000000000000import * as assert from 'assert'; import * as sinon from 'sinon'; import { Task } from '../managers/tasks'; import ReaderStream from '../readers/stream'; import Settings, { Options } from '../settings'; import * as tests from '../tests'; import { Entry, EntryItem, ErrnoException } from '../types'; import ReaderAsync from '../readers/async'; import ProviderAsync from './async'; class TestProvider extends ProviderAsync { protected _reader: ReaderAsync = sinon.createStubInstance(ReaderAsync) as unknown as ReaderAsync; constructor(options?: Options) { super(new Settings(options)); } public get reader(): sinon.SinonStubbedInstance { return this._reader as unknown as sinon.SinonStubbedInstance; } } function getProvider(options?: Options): TestProvider { return new TestProvider(options); } function getEntries(provider: TestProvider, task: Task, entry: Entry): Promise { provider.reader.dynamic.resolves([entry]); provider.reader.static.resolves([entry]); return provider.read(task); } describe('Providers → ProviderAsync', () => { describe('Constructor', () => { it('should create instance of class', () => { const provider = getProvider(); assert.ok(provider instanceof ProviderAsync); }); }); describe('.read', () => { it('should return entries for dynamic task', async () => { const provider = getProvider(); const task = tests.task.builder().base('.').positive('*').build(); const entry = tests.entry.builder().path('root/file.txt').build(); const expected = ['root/file.txt']; const actual = await getEntries(provider, task, entry); assert.strictEqual(provider.reader.dynamic.callCount, 1); assert.deepStrictEqual(actual, expected); }); it('should return entries for static task', async () => { const provider = getProvider(); const task = tests.task.builder().base('.').static().positive('*').build(); const entry = tests.entry.builder().path('root/file.txt').build(); const expected = ['root/file.txt']; const actual = await getEntries(provider, task, entry); assert.strictEqual(provider.reader.static.callCount, 1); assert.deepStrictEqual(actual, expected); }); it('should throw error', async () => { const provider = getProvider(); const task = tests.task.builder().base('.').positive('*').build(); provider.reader.dynamic.rejects(tests.errno.getEnoent()); try { await provider.read(task); throw new Error('Wow'); } catch (error) { assert.strictEqual((error as ErrnoException).code, 'ENOENT'); } }); }); }); fast-glob-3.2.12/src/providers/async.ts000066400000000000000000000014511430655000000177400ustar00rootroot00000000000000import { Task } from '../managers/tasks'; import { Entry, EntryItem, ReaderOptions } from '../types'; import ReaderAsync from '../readers/async'; import Provider from './provider'; export default class ProviderAsync extends Provider> { protected _reader: ReaderAsync = new ReaderAsync(this._settings); public async read(task: Task): Promise { const root = this._getRootDirectory(task); const options = this._getReaderOptions(task); const entries = await this.api(root, task, options); return entries.map((entry) => options.transform(entry)); } public api(root: string, task: Task, options: ReaderOptions): Promise { if (task.dynamic) { return this._reader.dynamic(root, options); } return this._reader.static(task.patterns, options); } } fast-glob-3.2.12/src/providers/filters/000077500000000000000000000000001430655000000177225ustar00rootroot00000000000000fast-glob-3.2.12/src/providers/filters/deep.spec.ts000066400000000000000000000124761430655000000221520ustar00rootroot00000000000000import * as assert from 'assert'; import Settings, { Options } from '../../settings'; import * as tests from '../../tests'; import { EntryFilterFunction, Pattern, Entry } from '../../types'; import DeepFilter from './deep'; type FilterOptions = { base?: string; positive: Pattern[]; negative?: Pattern[]; options?: Options; }; const DIRECTORY_ENTRY_LEVEL_1 = tests.entry.builder().path('root').directory().build(); const DIRECTORY_ENTRY_LEVEL_2 = tests.entry.builder().path('root/directory').directory().build(); const DIRECTORY_ENTRY_LEVEL_3 = tests.entry.builder().path('root/nested/directory').directory().build(); function getDeepFilterInstance(options?: Options): DeepFilter { const settings = new Settings(options); return new DeepFilter(settings, { dot: settings.dot }); } function getFilter(options: FilterOptions): EntryFilterFunction { const base = options.base ?? '.'; const negative = options.negative ?? []; return getDeepFilterInstance(options.options).getFilter(base, options.positive, negative); } function getResult(entry: Entry, options: FilterOptions): boolean { const filter = getFilter(options); return filter(entry); } function accept(entry: Entry, options: FilterOptions): void { assert.strictEqual(getResult(entry, options), true); } function reject(entry: Entry, options: FilterOptions): void { assert.strictEqual(getResult(entry, options), false); } describe('Providers → Filters → Deep', () => { describe('Constructor', () => { it('should create instance of class', () => { const filter = getDeepFilterInstance(); assert.ok(filter instanceof DeepFilter); }); }); describe('.getFilter', () => { describe('options.deep', () => { it('should reject when an option has "0" as value', () => { reject(DIRECTORY_ENTRY_LEVEL_1, { positive: ['**/*'], options: { deep: 0 } }); }); it('should reject when the depth of entry is greater than an allowable value (without base)', () => { reject(DIRECTORY_ENTRY_LEVEL_3, { positive: ['**/*'], options: { deep: 1 } }); }); it('should reject when the depth of entry is greater than an allowable value (with base as current level)', () => { reject(DIRECTORY_ENTRY_LEVEL_3, { positive: ['**/*'], options: { deep: 1 } }); }); it('should reject when the depth of entry is greater than an allowable value (with nested base)', () => { reject(DIRECTORY_ENTRY_LEVEL_3, { base: 'root/a', positive: ['root/a/*'], options: { deep: 1 } }); }); it('should accept when an option has "Infinity" as value', () => { accept(DIRECTORY_ENTRY_LEVEL_1, { positive: ['**/*'], options: { deep: Infinity } }); }); }); describe('options.followSymbolicLinks', () => { it('should reject when an entry is symbolic link and option is disabled', () => { const entry = tests.entry.builder().path('root').directory().symlink().build(); reject(entry, { positive: ['**/*'], options: { followSymbolicLinks: false } }); }); it('should accept when an entry is symbolic link and option is enabled', () => { const entry = tests.entry.builder().path('root').directory().symlink().build(); accept(entry, { positive: ['**/*'], options: { followSymbolicLinks: true } }); }); }); describe('Positive pattern', () => { it('should reject when an entry does not match to the positive pattern', () => { reject(DIRECTORY_ENTRY_LEVEL_1, { positive: ['non-root/*'] }); }); it('should reject when an entry starts with leading dot and does not match to the positive pattern', () => { const entry = tests.entry.builder().path('./root').directory().build(); reject(entry, { positive: ['non-root/*'] }); }); it('should accept when an entry match to the positive pattern with leading dot', () => { const entry = tests.entry.builder().path('./root').directory().build(); accept(entry, { positive: ['./root/*'] }); }); it('should accept when the positive pattern does not match by level, but the "baseNameMatch" is enabled', () => { accept(DIRECTORY_ENTRY_LEVEL_2, { positive: ['*'], options: { baseNameMatch: true } }); }); it('should accept when the positive pattern has a globstar', () => { accept(DIRECTORY_ENTRY_LEVEL_3, { positive: ['**/*'] }); }); }); describe('Negative pattern', () => { it('should reject when an entry match to the negative pattern', () => { reject(DIRECTORY_ENTRY_LEVEL_2, { positive: ['**/*'], negative: ['root/**'] }); }); it('should accept when the negative pattern has no effect to depth reading', () => { accept(DIRECTORY_ENTRY_LEVEL_3, { positive: ['**/*'], negative: ['**/*'] }); }); it('should accept when an entry does not match to the negative pattern', () => { accept(DIRECTORY_ENTRY_LEVEL_3, { positive: ['**/*'], negative: ['non-root/**/*'] }); }); }); }); describe('Immutability', () => { it('should return the data without changes', () => { const filter = getFilter({ positive: ['**/*'] }); const reference = tests.entry.builder().path('root/directory').directory().build(); const entry = tests.entry.builder().path('root/directory').directory().build(); filter(entry); assert.deepStrictEqual(entry, reference); }); }); }); fast-glob-3.2.12/src/providers/filters/deep.ts000066400000000000000000000050241430655000000212100ustar00rootroot00000000000000import { Entry, MicromatchOptions, EntryFilterFunction, Pattern, PatternRe } from '../../types'; import Settings from '../../settings'; import * as utils from '../../utils'; import PartialMatcher from '../matchers/partial'; export default class DeepFilter { constructor(private readonly _settings: Settings, private readonly _micromatchOptions: MicromatchOptions) { } public getFilter(basePath: string, positive: Pattern[], negative: Pattern[]): EntryFilterFunction { const matcher = this._getMatcher(positive); const negativeRe = this._getNegativePatternsRe(negative); return (entry) => this._filter(basePath, entry, matcher, negativeRe); } private _getMatcher(patterns: Pattern[]): PartialMatcher { return new PartialMatcher(patterns, this._settings, this._micromatchOptions); } private _getNegativePatternsRe(patterns: Pattern[]): PatternRe[] { const affectDepthOfReadingPatterns = patterns.filter(utils.pattern.isAffectDepthOfReadingPattern); return utils.pattern.convertPatternsToRe(affectDepthOfReadingPatterns, this._micromatchOptions); } private _filter(basePath: string, entry: Entry, matcher: PartialMatcher, negativeRe: PatternRe[]): boolean { if (this._isSkippedByDeep(basePath, entry.path)) { return false; } if (this._isSkippedSymbolicLink(entry)) { return false; } const filepath = utils.path.removeLeadingDotSegment(entry.path); if (this._isSkippedByPositivePatterns(filepath, matcher)) { return false; } return this._isSkippedByNegativePatterns(filepath, negativeRe); } private _isSkippedByDeep(basePath: string, entryPath: string): boolean { /** * Avoid unnecessary depth calculations when it doesn't matter. */ if (this._settings.deep === Infinity) { return false; } return this._getEntryLevel(basePath, entryPath) >= this._settings.deep; } private _getEntryLevel(basePath: string, entryPath: string): number { const entryPathDepth = entryPath.split('/').length; if (basePath === '') { return entryPathDepth; } const basePathDepth = basePath.split('/').length; return entryPathDepth - basePathDepth; } private _isSkippedSymbolicLink(entry: Entry): boolean { return !this._settings.followSymbolicLinks && entry.dirent.isSymbolicLink(); } private _isSkippedByPositivePatterns(entryPath: string, matcher: PartialMatcher): boolean { return !this._settings.baseNameMatch && !matcher.match(entryPath); } private _isSkippedByNegativePatterns(entryPath: string, patternsRe: PatternRe[]): boolean { return !utils.pattern.matchAny(entryPath, patternsRe); } } fast-glob-3.2.12/src/providers/filters/entry.spec.ts000066400000000000000000000147061430655000000223740ustar00rootroot00000000000000import * as assert from 'assert'; import * as path from 'path'; import Settings, { Options } from '../../settings'; import * as tests from '../../tests'; import { EntryFilterFunction, Pattern, Entry } from '../../types'; import EntryFilter from './entry'; type FilterOptions = { positive: Pattern[]; negative?: Pattern[]; options?: Options; }; const FILE_ENTRY = tests.entry.builder().path('root/file.txt').file().build(); const DIRECTORY_ENTRY = tests.entry.builder().path('root/directory').directory().build(); function getEntryFilterInstance(options?: Options): EntryFilter { const settings = new Settings(options); return new EntryFilter(settings, { dot: settings.dot }); } function getFilter(options: FilterOptions): EntryFilterFunction { const negative = options.negative ?? []; return getEntryFilterInstance(options.options).getFilter(options.positive, negative); } function getResult(entry: Entry, options: FilterOptions): boolean { const filter = getFilter(options); return filter(entry); } function accept(entry: Entry, options: FilterOptions): void { assert.strictEqual(getResult(entry, options), true); } function reject(entry: Entry, options: FilterOptions): void { assert.strictEqual(getResult(entry, options), false); } describe('Providers → Filters → Entry', () => { describe('Constructor', () => { it('should create instance of class', () => { const filter = getEntryFilterInstance(); assert.ok(filter instanceof EntryFilter); }); }); describe('.getFilter', () => { describe('options.unique', () => { it('should do not build the index when an option is disabled', () => { const filterInstance = getEntryFilterInstance({ unique: false }); const filter = filterInstance.getFilter(['**/*'], []); filter(FILE_ENTRY); assert.strictEqual(filterInstance.index.size, 0); }); it('should do not add an entry to the index when an entry does not match to patterns', () => { const filterInstance = getEntryFilterInstance(); const filter = filterInstance.getFilter(['**/*.unrelated-file-extension'], []); filter(FILE_ENTRY); assert.strictEqual(filterInstance.index.size, 0); }); it('should reject a duplicate entry', () => { const filter = getFilter({ positive: ['**/*'] }); filter(FILE_ENTRY); const actual = filter(FILE_ENTRY); assert.ok(!actual); }); it('should accept a duplicate entry when an option is disabled', () => { const filter = getFilter({ positive: ['**/*'], options: { unique: false } }); filter(FILE_ENTRY); const actual = filter(FILE_ENTRY); assert.ok(actual); }); }); describe('options.onlyFiles', () => { it('should reject a directory entry', () => { reject(DIRECTORY_ENTRY, { positive: ['**/*'], options: { onlyFiles: true } }); }); it('should accept a directory entry', () => { accept(DIRECTORY_ENTRY, { positive: ['**/*'], options: { onlyFiles: false } }); }); it('should accept a file entry', () => { accept(FILE_ENTRY, { positive: ['**/*'], options: { onlyFiles: true } }); }); }); describe('options.onlyDirectories', () => { it('should reject a file entry', () => { reject(FILE_ENTRY, { positive: ['**/*'], options: { onlyDirectories: true } }); }); it('should accept a directory entry', () => { accept(DIRECTORY_ENTRY, { positive: ['**/*'], options: { onlyDirectories: true } }); }); }); describe('options.absolute', () => { it('should reject when an entry match to the negative pattern', () => { reject(FILE_ENTRY, { positive: ['**/*'], negative: ['**/*'], options: { absolute: true } }); }); it('should reject when an entry match to the negative pattern with absolute path', () => { const negative = path.posix.join(process.cwd().replace(/\\/g, '/'), '**', '*'); reject(FILE_ENTRY, { positive: ['**/*'], negative: [negative], options: { absolute: true } }); }); it('should accept when an entry does not match to the negative pattern', () => { accept(FILE_ENTRY, { positive: ['**/*'], negative: ['*'], options: { absolute: true } }); }); it('should accept when an entry does not match to the negative pattern with absolute path', () => { const negative = path.posix.join(process.cwd().replace(/\\/g, '/'), 'non-root', '**', '*'); accept(FILE_ENTRY, { positive: ['**/*'], negative: [negative], options: { absolute: true } }); }); }); describe('options.baseNameMatch', () => { it('should reject an entry', () => { reject(FILE_ENTRY, { positive: ['*'], options: { baseNameMatch: false } }); }); it('should accept an entry', () => { accept(FILE_ENTRY, { positive: ['*'], options: { baseNameMatch: true } }); }); }); describe('Pattern', () => { it('should reject when an entry match to the negative pattern', () => { reject(FILE_ENTRY, { positive: ['**/*'], negative: ['**/*'] }); }); it('should reject when an entry does not match to the positive pattern', () => { reject(FILE_ENTRY, { positive: ['*'] }); }); it('should accept when an entry match to the positive pattern with a leading dot', () => { accept(FILE_ENTRY, { positive: ['./**/*'] }); }); it('should accept an entry with a leading dot', () => { const entry = tests.entry.builder().path('./root/file.txt').file().build(); accept(entry, { positive: ['**/*'] }); }); it('should accept when an entry match to the positive pattern', () => { accept(FILE_ENTRY, { positive: ['**/*'] }); }); it('should try to apply patterns to the path with the trailing slash for directory entry', () => { accept(DIRECTORY_ENTRY, { positive: ['**/'], options: { onlyFiles: false } }); }); it('should not try to apply patterns to the path with the trailing slash for non-directory entry', () => { reject(FILE_ENTRY, { positive: ['**/'], options: { onlyFiles: false } }); }); }); }); describe('Immutability', () => { it('should return the data without changes', () => { const filter = getFilter({ positive: ['**/*'] }); const reference = tests.entry.builder().path('root/file.txt').file().build(); const entry = tests.entry.builder().path('root/file.txt').file().build(); filter(entry); assert.deepStrictEqual(entry, reference); }); }); }); fast-glob-3.2.12/src/providers/filters/entry.ts000066400000000000000000000053121430655000000214340ustar00rootroot00000000000000import Settings from '../../settings'; import { Entry, EntryFilterFunction, MicromatchOptions, Pattern, PatternRe } from '../../types'; import * as utils from '../../utils'; export default class EntryFilter { public readonly index: Map = new Map(); constructor(private readonly _settings: Settings, private readonly _micromatchOptions: MicromatchOptions) {} public getFilter(positive: Pattern[], negative: Pattern[]): EntryFilterFunction { const positiveRe = utils.pattern.convertPatternsToRe(positive, this._micromatchOptions); const negativeRe = utils.pattern.convertPatternsToRe(negative, this._micromatchOptions); return (entry) => this._filter(entry, positiveRe, negativeRe); } private _filter(entry: Entry, positiveRe: PatternRe[], negativeRe: PatternRe[]): boolean { if (this._settings.unique && this._isDuplicateEntry(entry)) { return false; } if (this._onlyFileFilter(entry) || this._onlyDirectoryFilter(entry)) { return false; } if (this._isSkippedByAbsoluteNegativePatterns(entry.path, negativeRe)) { return false; } const filepath = this._settings.baseNameMatch ? entry.name : entry.path; const isDirectory = entry.dirent.isDirectory(); const isMatched = this._isMatchToPatterns(filepath, positiveRe, isDirectory) && !this._isMatchToPatterns(entry.path, negativeRe, isDirectory); if (this._settings.unique && isMatched) { this._createIndexRecord(entry); } return isMatched; } private _isDuplicateEntry(entry: Entry): boolean { return this.index.has(entry.path); } private _createIndexRecord(entry: Entry): void { this.index.set(entry.path, undefined); } private _onlyFileFilter(entry: Entry): boolean { return this._settings.onlyFiles && !entry.dirent.isFile(); } private _onlyDirectoryFilter(entry: Entry): boolean { return this._settings.onlyDirectories && !entry.dirent.isDirectory(); } private _isSkippedByAbsoluteNegativePatterns(entryPath: string, patternsRe: PatternRe[]): boolean { if (!this._settings.absolute) { return false; } const fullpath = utils.path.makeAbsolute(this._settings.cwd, entryPath); return utils.pattern.matchAny(fullpath, patternsRe); } private _isMatchToPatterns(entryPath: string, patternsRe: PatternRe[], isDirectory: boolean): boolean { const filepath = utils.path.removeLeadingDotSegment(entryPath); // Trying to match files and directories by patterns. const isMatched = utils.pattern.matchAny(filepath, patternsRe); // A pattern with a trailling slash can be used for directory matching. // To apply such pattern, we need to add a tralling slash to the path. if (!isMatched && isDirectory) { return utils.pattern.matchAny(filepath + '/', patternsRe); } return isMatched; } } fast-glob-3.2.12/src/providers/filters/error.spec.ts000066400000000000000000000024401430655000000223540ustar00rootroot00000000000000import * as assert from 'assert'; import Settings, { Options } from '../../settings'; import * as tests from '../../tests'; import { ErrorFilterFunction } from '../../types'; import ErrorFilter from './error'; function getErrorFilterInstance(options?: Options): ErrorFilter { const settings = new Settings(options); return new ErrorFilter(settings); } function getFilter(options?: Options): ErrorFilterFunction { return getErrorFilterInstance(options).getFilter(); } describe('Providers → Filters → Error', () => { describe('Constructor', () => { it('should create instance of class', () => { const filter = getErrorFilterInstance(); assert.ok(filter instanceof ErrorFilter); }); }); describe('.getFilter', () => { it('should return true for ENOENT error', () => { const filter = getFilter(); const actual = filter(tests.errno.getEnoent()); assert.ok(actual); }); it('should return true for EPERM error when the `suppressErrors` options is enabled', () => { const filter = getFilter({ suppressErrors: true }); const actual = filter(tests.errno.getEperm()); assert.ok(actual); }); it('should return false for EPERM error', () => { const filter = getFilter(); const actual = filter(tests.errno.getEperm()); assert.ok(!actual); }); }); }); fast-glob-3.2.12/src/providers/filters/error.ts000066400000000000000000000007361430655000000214310ustar00rootroot00000000000000import Settings from '../../settings'; import { ErrnoException, ErrorFilterFunction } from '../../types'; import * as utils from '../../utils'; export default class ErrorFilter { constructor(private readonly _settings: Settings) { } public getFilter(): ErrorFilterFunction { return (error) => this._isNonFatalError(error); } private _isNonFatalError(error: ErrnoException): boolean { return utils.errno.isEnoentCodeError(error) || this._settings.suppressErrors; } } fast-glob-3.2.12/src/providers/matchers/000077500000000000000000000000001430655000000200605ustar00rootroot00000000000000fast-glob-3.2.12/src/providers/matchers/matcher.spec.ts000066400000000000000000000020551430655000000230060ustar00rootroot00000000000000import * as assert from 'assert'; import * as tests from '../../tests'; import { Pattern, MicromatchOptions } from '../../types'; import Settings from '../../settings'; import Matcher, { PatternInfo } from './matcher'; class TestMatcher extends Matcher { public get storage(): PatternInfo[] { return this._storage; } } function getMatcher(patterns: Pattern[], options: MicromatchOptions = {}): TestMatcher { return new TestMatcher(patterns, new Settings(), options); } describe('Providers → Matchers → Matcher', () => { describe('.storage', () => { it('should return created storage', () => { const matcher = getMatcher(['a*', 'a/**/b']); const expected: PatternInfo[] = [ tests.pattern.info() .section(tests.pattern.segment().dynamic().pattern('a*').build()) .build(), tests.pattern.info() .section(tests.pattern.segment().pattern('a').build()) .section(tests.pattern.segment().pattern('b').build()) .build() ]; const actual = matcher.storage; assert.deepStrictEqual(actual, expected); }); }); }); fast-glob-3.2.12/src/providers/matchers/matcher.ts000066400000000000000000000042111430655000000220510ustar00rootroot00000000000000import { Pattern, MicromatchOptions, PatternRe } from '../../types'; import * as utils from '../../utils'; import Settings from '../../settings'; export type PatternSegment = StaticPatternSegment | DynamicPatternSegment; type StaticPatternSegment = { dynamic: false; pattern: Pattern; }; type DynamicPatternSegment = { dynamic: true; pattern: Pattern; patternRe: PatternRe; }; export type PatternSection = PatternSegment[]; export type PatternInfo = { /** * Indicates that the pattern has a globstar (more than a single section). */ complete: boolean; pattern: Pattern; segments: PatternSegment[]; sections: PatternSection[]; }; export default abstract class Matcher { protected readonly _storage: PatternInfo[] = []; constructor(private readonly _patterns: Pattern[], private readonly _settings: Settings, private readonly _micromatchOptions: MicromatchOptions) { this._fillStorage(); } private _fillStorage(): void { /** * The original pattern may include `{,*,**,a/*}`, which will lead to problems with matching (unresolved level). * So, before expand patterns with brace expansion into separated patterns. */ const patterns = utils.pattern.expandPatternsWithBraceExpansion(this._patterns); for (const pattern of patterns) { const segments = this._getPatternSegments(pattern); const sections = this._splitSegmentsIntoSections(segments); this._storage.push({ complete: sections.length <= 1, pattern, segments, sections }); } } private _getPatternSegments(pattern: Pattern): PatternSegment[] { const parts = utils.pattern.getPatternParts(pattern, this._micromatchOptions); return parts.map((part) => { const dynamic = utils.pattern.isDynamicPattern(part, this._settings); if (!dynamic) { return { dynamic: false, pattern: part }; } return { dynamic: true, pattern: part, patternRe: utils.pattern.makeRe(part, this._micromatchOptions) }; }); } private _splitSegmentsIntoSections(segments: PatternSegment[]): PatternSection[] { return utils.array.splitWhen(segments, (segment) => segment.dynamic && utils.pattern.hasGlobStar(segment.pattern)); } } fast-glob-3.2.12/src/providers/matchers/partial.spec.ts000066400000000000000000000032451430655000000230210ustar00rootroot00000000000000import * as assert from 'assert'; import { Pattern, MicromatchOptions } from '../../types'; import Settings from '../../settings'; import Matcher from './partial'; function getMatcher(patterns: Pattern[], options: MicromatchOptions = {}): Matcher { return new Matcher(patterns, new Settings(), options); } function assertMatch(patterns: Pattern[], filepath: string): void | never { const matcher = getMatcher(patterns); assert.ok(matcher.match(filepath), `Path "${filepath}" should match: ${patterns}`); } function assertNotMatch(patterns: Pattern[], filepath: string): void | never { const matcher = getMatcher(patterns); assert.ok(!matcher.match(filepath), `Path "${filepath}" should do not match: ${patterns}`); } describe('Providers → Matchers → Partial', () => { describe('.match', () => { it('should handle patterns with globstar', () => { assertMatch(['**'], 'a'); assertMatch(['**'], './a'); assertMatch(['**/a'], 'a'); assertMatch(['**/a'], 'b/a'); assertMatch(['a/**'], 'a/b'); assertNotMatch(['a/**'], 'b'); }); it('should do not match the latest segment', () => { assertMatch(['b/*'], 'b'); assertNotMatch(['*'], 'a'); assertNotMatch(['a/*'], 'a/b'); }); it('should trying to match all patterns', () => { assertMatch(['a/*', 'b/*'], 'b'); assertMatch(['non-match/b/c', 'a/*/c'], 'a/b'); assertNotMatch(['non-match/d/c', 'a/b/c'], 'a/d'); }); it('should match a static segment', () => { assertMatch(['a/b'], 'a'); assertNotMatch(['b/b'], 'a'); }); it('should match a dynamic segment', () => { assertMatch(['*/b'], 'a'); assertMatch(['{a,b}/*'], 'a'); assertNotMatch(['{a,b}/*'], 'c'); }); }); }); fast-glob-3.2.12/src/providers/matchers/partial.ts000066400000000000000000000020301430655000000220570ustar00rootroot00000000000000import Matcher from './matcher'; export default class PartialMatcher extends Matcher { public match(filepath: string): boolean { const parts = filepath.split('/'); const levels = parts.length; const patterns = this._storage.filter((info) => !info.complete || info.segments.length > levels); for (const pattern of patterns) { const section = pattern.sections[0]; /** * In this case, the pattern has a globstar and we must read all directories unconditionally, * but only if the level has reached the end of the first group. * * fixtures/{a,b}/** * ^ true/false ^ always true */ if (!pattern.complete && levels > section.length) { return true; } const match = parts.every((part, index) => { const segment = pattern.segments[index]; if (segment.dynamic && segment.patternRe.test(part)) { return true; } if (!segment.dynamic && segment.pattern === part) { return true; } return false; }); if (match) { return true; } } return false; } } fast-glob-3.2.12/src/providers/provider.spec.ts000066400000000000000000000063701430655000000214130ustar00rootroot00000000000000import * as assert from 'assert'; import * as path from 'path'; import { Task } from '../managers/tasks'; import Settings, { Options } from '../settings'; import * as tests from '../tests'; import { MicromatchOptions, ReaderOptions } from '../types'; import Provider from './provider'; export class TestProvider extends Provider> { public read(): Array<{}> { return []; } public getRootDirectory(task: Task): string { return this._getRootDirectory(task); } public getReaderOptions(task: Task): ReaderOptions { return this._getReaderOptions(task); } public getMicromatchOptions(): MicromatchOptions { return this._getMicromatchOptions(); } } export function getProvider(options?: Options): TestProvider { const settings = new Settings(options); return new TestProvider(settings); } describe('Providers → Provider', () => { describe('Constructor', () => { it('should create instance of class', () => { const provider = getProvider(); assert.ok(provider instanceof Provider); }); }); describe('.getRootDirectory', () => { it('should return root directory for reader with global base (.)', () => { const provider = getProvider(); const task = tests.task.builder().base('.').build(); const expected = process.cwd(); const actual = provider.getRootDirectory(task); assert.strictEqual(actual, expected); }); it('should return root directory for reader with non-global base (fixtures)', () => { const provider = getProvider(); const task = tests.task.builder().base('root').build(); const expected = path.join(process.cwd(), 'root'); const actual = provider.getRootDirectory(task); assert.strictEqual(actual, expected); }); }); describe('.getReaderOptions', () => { it('should return options for reader with global base (.)', () => { const settings = new Settings(); const provider = getProvider(settings); const task = tests.task.builder().base('.').positive('*').build(); const actual = provider.getReaderOptions(task); assert.strictEqual(actual.basePath, ''); assert.strictEqual(actual.concurrency, settings.concurrency); assert.strictEqual(typeof actual.deepFilter, 'function'); assert.strictEqual(typeof actual.entryFilter, 'function'); assert.strictEqual(typeof actual.errorFilter, 'function'); assert.ok(actual.followSymbolicLinks); assert.strictEqual(typeof actual.fs, 'object'); assert.ok(!actual.stats); assert.ok(actual.throwErrorOnBrokenSymbolicLink === false); assert.strictEqual(typeof actual.transform, 'function'); }); it('should return options for reader with non-global base', () => { const provider = getProvider(); const task = tests.task.builder().base('root').positive('*').build(); const actual = provider.getReaderOptions(task); assert.strictEqual(actual.basePath, 'root'); }); }); describe('.getMicromatchOptions', () => { it('should return options for micromatch', () => { const provider = getProvider(); const expected: MicromatchOptions = { dot: false, matchBase: false, nobrace: false, nocase: false, noext: false, noglobstar: false, posix: true, strictSlashes: false }; const actual = provider.getMicromatchOptions(); assert.deepStrictEqual(actual, expected); }); }); }); fast-glob-3.2.12/src/providers/provider.ts000066400000000000000000000037371430655000000204660ustar00rootroot00000000000000import * as path from 'path'; import { Task } from '../managers/tasks'; import Settings from '../settings'; import { MicromatchOptions, ReaderOptions } from '../types'; import DeepFilter from './filters/deep'; import EntryFilter from './filters/entry'; import ErrorFilter from './filters/error'; import EntryTransformer from './transformers/entry'; export default abstract class Provider { public readonly errorFilter: ErrorFilter = new ErrorFilter(this._settings); public readonly entryFilter: EntryFilter = new EntryFilter(this._settings, this._getMicromatchOptions()); public readonly deepFilter: DeepFilter = new DeepFilter(this._settings, this._getMicromatchOptions()); public readonly entryTransformer: EntryTransformer = new EntryTransformer(this._settings); constructor(protected readonly _settings: Settings) { } public abstract read(_task: Task): T; protected _getRootDirectory(task: Task): string { return path.resolve(this._settings.cwd, task.base); } protected _getReaderOptions(task: Task): ReaderOptions { const basePath = task.base === '.' ? '' : task.base; return { basePath, pathSegmentSeparator: '/', concurrency: this._settings.concurrency, deepFilter: this.deepFilter.getFilter(basePath, task.positive, task.negative), entryFilter: this.entryFilter.getFilter(task.positive, task.negative), errorFilter: this.errorFilter.getFilter(), followSymbolicLinks: this._settings.followSymbolicLinks, fs: this._settings.fs, stats: this._settings.stats, throwErrorOnBrokenSymbolicLink: this._settings.throwErrorOnBrokenSymbolicLink, transform: this.entryTransformer.getTransformer() }; } protected _getMicromatchOptions(): MicromatchOptions { return { dot: this._settings.dot, matchBase: this._settings.baseNameMatch, nobrace: !this._settings.braceExpansion, nocase: !this._settings.caseSensitiveMatch, noext: !this._settings.extglob, noglobstar: !this._settings.globstar, posix: true, strictSlashes: false }; } } fast-glob-3.2.12/src/providers/stream.spec.ts000066400000000000000000000066501430655000000210550ustar00rootroot00000000000000import * as assert from 'assert'; import { PassThrough } from 'stream'; import * as sinon from 'sinon'; import { Task } from '../managers/tasks'; import ReaderStream from '../readers/stream'; import Settings, { Options } from '../settings'; import * as tests from '../tests'; import { Entry, EntryItem, ErrnoException } from '../types'; import ProviderStream from './stream'; class TestProvider extends ProviderStream { protected _reader: ReaderStream = sinon.createStubInstance(ReaderStream) as unknown as ReaderStream; constructor(options?: Options) { super(new Settings(options)); } public get reader(): sinon.SinonStubbedInstance { return this._reader as unknown as sinon.SinonStubbedInstance; } } function getProvider(options?: Options): TestProvider { return new TestProvider(options); } function getEntries(provider: TestProvider, task: Task, entry: Entry): Promise { const reader = new PassThrough({ objectMode: true }); provider.reader.dynamic.returns(reader); provider.reader.static.returns(reader); reader.push(entry); reader.push(null); return new Promise((resolve, reject) => { const items: EntryItem[] = []; const api = provider.read(task); api.on('data', (item: EntryItem) => items.push(item)); api.once('error', reject); api.once('end', () => resolve(items)); }); } describe('Providers → ProviderStream', () => { describe('Constructor', () => { it('should create instance of class', () => { const provider = getProvider(); assert.ok(provider instanceof ProviderStream); }); }); describe('.read', () => { it('should return entries for dynamic entries', async () => { const provider = getProvider(); const task = tests.task.builder().base('.').positive('*').build(); const entry = tests.entry.builder().path('root/file.txt').file().build(); const expected = ['root/file.txt']; const actual = await getEntries(provider, task, entry); assert.strictEqual(provider.reader.dynamic.callCount, 1); assert.deepStrictEqual(actual, expected); }); it('should return entries for static entries', async () => { const provider = getProvider(); const task = tests.task.builder().base('.').static().positive('root/file.txt').build(); const entry = tests.entry.builder().path('root/file.txt').file().build(); const expected = ['root/file.txt']; const actual = await getEntries(provider, task, entry); assert.strictEqual(provider.reader.static.callCount, 1); assert.deepStrictEqual(actual, expected); }); it('should emit error to the transform stream', (done) => { const provider = getProvider(); const task = tests.task.builder().base('.').positive('*').build(); const stream = new PassThrough({ read(): void { stream.emit('error', tests.errno.getEnoent()); } }); provider.reader.dynamic.returns(stream); const actual = provider.read(task); actual.once('error', (error: ErrnoException) => { assert.strictEqual(error.code, 'ENOENT'); done(); }); }); it('should destroy source stream when the destination stream is closed', (done) => { const provider = getProvider(); const task = tests.task.builder().base('.').positive('*').build(); const stream = new PassThrough(); provider.reader.dynamic.returns(stream); const actual = provider.read(task); actual.once('close', () => { assert.ok(stream.destroyed); done(); }); actual.emit('close'); }); }); }); fast-glob-3.2.12/src/providers/stream.ts000066400000000000000000000021631430655000000201170ustar00rootroot00000000000000import { Readable } from 'stream'; import { Task } from '../managers/tasks'; import ReaderStream from '../readers/stream'; import { Entry, ErrnoException, ReaderOptions } from '../types'; import Provider from './provider'; export default class ProviderStream extends Provider { protected _reader: ReaderStream = new ReaderStream(this._settings); public read(task: Task): Readable { const root = this._getRootDirectory(task); const options = this._getReaderOptions(task); const source = this.api(root, task, options); const destination = new Readable({ objectMode: true, read: () => { /* noop */ } }); source .once('error', (error: ErrnoException) => destination.emit('error', error)) .on('data', (entry: Entry) => destination.emit('data', options.transform(entry))) .once('end', () => destination.emit('end')); destination .once('close', () => source.destroy()); return destination; } public api(root: string, task: Task, options: ReaderOptions): Readable { if (task.dynamic) { return this._reader.dynamic(root, options); } return this._reader.static(task.patterns, options); } } fast-glob-3.2.12/src/providers/sync.spec.ts000066400000000000000000000035651430655000000205400ustar00rootroot00000000000000import * as assert from 'assert'; import * as sinon from 'sinon'; import ReaderSync from '../readers/sync'; import Settings, { Options } from '../settings'; import * as tests from '../tests'; import ProviderSync from './sync'; class TestProvider extends ProviderSync { protected _reader: ReaderSync = sinon.createStubInstance(ReaderSync) as unknown as ReaderSync; constructor(options?: Options) { super(new Settings(options)); } public get reader(): sinon.SinonStubbedInstance { return this._reader as unknown as sinon.SinonStubbedInstance; } } function getProvider(options?: Options): TestProvider { return new TestProvider(options); } describe('Providers → ProviderSync', () => { describe('Constructor', () => { it('should create instance of class', () => { const provider = getProvider(); assert.ok(provider instanceof ProviderSync); }); }); describe('.read', () => { it('should return entries for dynamic task', () => { const provider = getProvider(); const task = tests.task.builder().base('.').positive('*').build(); const entry = tests.entry.builder().path('root/file.txt').file().build(); provider.reader.dynamic.returns([entry]); const expected = ['root/file.txt']; const actual = provider.read(task); assert.strictEqual(provider.reader.dynamic.callCount, 1); assert.deepStrictEqual(actual, expected); }); it('should return entries for static task', () => { const provider = getProvider(); const task = tests.task.builder().base('.').static().positive('root/file.txt').build(); const entry = tests.entry.builder().path('root/file.txt').file().build(); provider.reader.static.returns([entry]); const expected = ['root/file.txt']; const actual = provider.read(task); assert.strictEqual(provider.reader.static.callCount, 1); assert.deepStrictEqual(actual, expected); }); }); }); fast-glob-3.2.12/src/providers/sync.ts000066400000000000000000000013531430655000000176000ustar00rootroot00000000000000import { Task } from '../managers/tasks'; import ReaderSync from '../readers/sync'; import { Entry, EntryItem, ReaderOptions } from '../types'; import Provider from './provider'; export default class ProviderSync extends Provider { protected _reader: ReaderSync = new ReaderSync(this._settings); public read(task: Task): EntryItem[] { const root = this._getRootDirectory(task); const options = this._getReaderOptions(task); const entries = this.api(root, task, options); return entries.map(options.transform); } public api(root: string, task: Task, options: ReaderOptions): Entry[] { if (task.dynamic) { return this._reader.dynamic(root, options); } return this._reader.static(task.patterns, options); } } fast-glob-3.2.12/src/providers/transformers/000077500000000000000000000000001430655000000207775ustar00rootroot00000000000000fast-glob-3.2.12/src/providers/transformers/entry.spec.ts000066400000000000000000000066471430655000000234560ustar00rootroot00000000000000import * as assert from 'assert'; import * as path from 'path'; import Settings, { Options } from '../../settings'; import * as tests from '../../tests'; import { EntryTransformerFunction } from '../../types'; import * as utils from '../../utils'; import EntryTransformer from './entry'; function getEntryTransformer(options?: Options): EntryTransformer { return new EntryTransformer(new Settings(options)); } function getTransformer(options?: Options): EntryTransformerFunction { return getEntryTransformer(options).getTransformer(); } describe('Providers → Transformers → Entry', () => { describe('Constructor', () => { it('should create instance of class', () => { const filter = getEntryTransformer(); assert.ok(filter instanceof EntryTransformer); }); }); describe('.getTransformer', () => { it('should return transformed entry as string when options is not provided', () => { const transformer = getTransformer(); const entry = tests.entry.builder().path('root/file.txt').file().build(); const expected = 'root/file.txt'; const actual = transformer(entry); assert.strictEqual(actual, expected); }); it('should return transformed entry as object when the `objectMode` option is enabled', () => { const transformer = getTransformer({ objectMode: true }); const entry = tests.entry.builder().path('root/file.txt').file().build(); const expected = entry; const actual = transformer(entry); assert.deepStrictEqual(actual, expected); }); it('should return transformed entry as object when the `stats` option is enabled', () => { const transformer = getTransformer({ stats: true }); const entry = tests.entry.builder().path('root/file.txt').file().stats().build(); const expected = entry; const actual = transformer(entry); assert.deepStrictEqual(actual, expected); }); it('should return entry with absolute filepath when the `absolute` option is enabled', () => { const transformer = getTransformer({ absolute: true }); const entry = tests.entry.builder().path('root/file.txt').file().build(); const fullpath = path.join(process.cwd(), 'root', 'file.txt'); const expected = utils.path.unixify(fullpath); const actual = transformer(entry); assert.strictEqual(actual, expected); }); it('should return entry with trailing slash when the `markDirectories` is enabled', () => { const transformer = getTransformer({ markDirectories: true }); const entry = tests.entry.builder().path('root/directory').directory().build(); const expected = 'root/directory/'; const actual = transformer(entry); assert.strictEqual(actual, expected); }); it('should return correct entry when the `absolute` and `markDirectories` options is enabled', () => { const transformer = getTransformer({ absolute: true, markDirectories: true }); const entry = tests.entry.builder().path('root/directory').directory().build(); const fullpath = path.join(process.cwd(), 'root', 'directory', '/'); const expected = utils.path.unixify(fullpath); const actual = transformer(entry); assert.strictEqual(actual, expected); }); it('should do not mutate the entry when the `markDirectories` option is enabled', () => { const transformer = getTransformer({ markDirectories: true }); const entry = tests.entry.builder().path('root/directory').directory().build(); const actual = transformer(entry); assert.notStrictEqual(actual, entry.path); }); }); }); fast-glob-3.2.12/src/providers/transformers/entry.ts000066400000000000000000000014211430655000000225060ustar00rootroot00000000000000import Settings from '../../settings'; import { Entry, EntryItem, EntryTransformerFunction } from '../../types'; import * as utils from '../../utils'; export default class EntryTransformer { constructor(private readonly _settings: Settings) { } public getTransformer(): EntryTransformerFunction { return (entry) => this._transform(entry); } private _transform(entry: Entry): EntryItem { let filepath = entry.path; if (this._settings.absolute) { filepath = utils.path.makeAbsolute(this._settings.cwd, filepath); filepath = utils.path.unixify(filepath); } if (this._settings.markDirectories && entry.dirent.isDirectory()) { filepath += '/'; } if (!this._settings.objectMode) { return filepath; } return { ...entry, path: filepath }; } } fast-glob-3.2.12/src/readers/000077500000000000000000000000001430655000000156625ustar00rootroot00000000000000fast-glob-3.2.12/src/readers/async.spec.ts000066400000000000000000000042371430655000000203060ustar00rootroot00000000000000import * as assert from 'assert'; import { PassThrough } from 'stream'; import * as sinon from 'sinon'; import * as fsWalk from '@nodelib/fs.walk'; import Settings, { Options } from '../settings'; import { ReaderOptions } from '../types'; import * as tests from '../tests'; import ReaderAsync from './async'; import ReaderStream from './stream'; type WalkSignature = typeof fsWalk.walk; class TestReader extends ReaderAsync { protected _walkAsync: WalkSignature = sinon.stub() as unknown as WalkSignature; protected _readerStream: ReaderStream = sinon.createStubInstance(ReaderStream) as unknown as ReaderStream; constructor(options?: Options) { super(new Settings(options)); } public get walkAsync(): sinon.SinonStub { return this._walkAsync as unknown as sinon.SinonStub; } public get readerStream(): sinon.SinonStubbedInstance { return this._readerStream as unknown as sinon.SinonStubbedInstance; } } function getReader(options?: Options): TestReader { return new TestReader(options); } function getReaderOptions(options: Partial = {}): ReaderOptions { return { ...options } as unknown as ReaderOptions; } describe('Readers → ReaderAsync', () => { describe('Constructor', () => { it('should create instance of class', () => { const reader = getReader(); assert.ok(reader instanceof TestReader); }); }); describe('.dynamic', () => { it('should call fs.walk method', async () => { const reader = getReader(); const readerOptions = getReaderOptions(); reader.walkAsync.yields(null, []); await reader.dynamic('root', readerOptions); assert.ok(reader.walkAsync.called); }); }); describe('.static', () => { it('should call stream reader method', async () => { const entry = tests.entry.builder().path('root/file.txt').build(); const reader = getReader(); const readerOptions = getReaderOptions(); const readerStream = new PassThrough({ objectMode: true }); readerStream.push(entry); readerStream.push(null); reader.readerStream.static.returns(readerStream); await reader.static(['a.txt'], readerOptions); assert.ok(reader.readerStream.static.called); }); }); }); fast-glob-3.2.12/src/readers/async.ts000066400000000000000000000021001430655000000173400ustar00rootroot00000000000000import * as fsWalk from '@nodelib/fs.walk'; import { Entry, ReaderOptions, Pattern } from '../types'; import Reader from './reader'; import ReaderStream from './stream'; export default class ReaderAsync extends Reader> { protected _walkAsync: typeof fsWalk.walk = fsWalk.walk; protected _readerStream: ReaderStream = new ReaderStream(this._settings); public dynamic(root: string, options: ReaderOptions): Promise { return new Promise((resolve, reject) => { this._walkAsync(root, options, (error, entries) => { if (error === null) { resolve(entries); } else { reject(error); } }); }); } public async static(patterns: Pattern[], options: ReaderOptions): Promise { const entries: Entry[] = []; const stream = this._readerStream.static(patterns, options); // After #235, replace it with an asynchronous iterator. return new Promise((resolve, reject) => { stream.once('error', reject); stream.on('data', (entry: Entry) => entries.push(entry)); stream.once('end', () => resolve(entries)); }); } } fast-glob-3.2.12/src/readers/reader.spec.ts000066400000000000000000000034571430655000000204360ustar00rootroot00000000000000import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import { Stats } from '@nodelib/fs.macchiato'; import Settings, { Options } from '../settings'; import { Entry, Pattern } from '../types'; import Reader from './reader'; class TestReader extends Reader { constructor(options?: Options) { super(new Settings(options)); } public dynamic(): never[] { return []; } public static(): never[] { return []; } public getFullEntryPath(filepath: string): string { return this._getFullEntryPath(filepath); } public makeEntry(stats: fs.Stats, pattern: Pattern): Entry { return this._makeEntry(stats, pattern); } } function getReader(options?: Options): TestReader { return new TestReader(options); } describe('Readers → Reader', () => { describe('Constructor', () => { it('should create instance of class', () => { const reader = getReader(); assert.ok(reader instanceof TestReader); }); }); describe('.getFullEntryPath', () => { it('should return path to entry', () => { const reader = getReader(); const expected = path.join(process.cwd(), 'config.json'); const actual = reader.getFullEntryPath('config.json'); assert.strictEqual(actual, expected); }); }); describe('.makeEntry', () => { it('should return created entry', () => { const reader = getReader(); const pattern = 'config.json'; const actual = reader.makeEntry(new Stats(), pattern); assert.strictEqual(actual.name, pattern); assert.strictEqual(actual.path, pattern); assert.ok(actual.dirent.isFile()); }); it('should return created entry with fs.Stats', () => { const reader = getReader({ stats: true }); const pattern = 'config.json'; const actual = reader.makeEntry(new Stats(), pattern); assert.ok(actual.stats); }); }); }); fast-glob-3.2.12/src/readers/reader.ts000066400000000000000000000023461430655000000175010ustar00rootroot00000000000000import * as fs from 'fs'; import * as path from 'path'; import * as fsStat from '@nodelib/fs.stat'; import Settings from '../settings'; import { Entry, ErrnoException, Pattern, ReaderOptions } from '../types'; import * as utils from '../utils'; export default abstract class Reader { protected readonly _fsStatSettings: fsStat.Settings = new fsStat.Settings({ followSymbolicLink: this._settings.followSymbolicLinks, fs: this._settings.fs, throwErrorOnBrokenSymbolicLink: this._settings.followSymbolicLinks }); constructor(protected readonly _settings: Settings) { } public abstract dynamic(root: string, options: ReaderOptions): T; public abstract static(patterns: Pattern[], options: ReaderOptions): T; protected _getFullEntryPath(filepath: string): string { return path.resolve(this._settings.cwd, filepath); } protected _makeEntry(stats: fs.Stats, pattern: Pattern): Entry { const entry: Entry = { name: pattern, path: pattern, dirent: utils.fs.createDirentFromStats(pattern, stats) }; if (this._settings.stats) { entry.stats = stats; } return entry; } protected _isFatalError(error: ErrnoException): boolean { return !utils.errno.isEnoentCodeError(error) && !this._settings.suppressErrors; } } fast-glob-3.2.12/src/readers/stream.spec.ts000066400000000000000000000076551430655000000204730ustar00rootroot00000000000000import * as assert from 'assert'; import { Stats } from '@nodelib/fs.macchiato'; import * as fsStat from '@nodelib/fs.stat'; import * as fsWalk from '@nodelib/fs.walk'; import * as sinon from 'sinon'; import Settings, { Options } from '../settings'; import * as tests from '../tests'; import { Entry, ErrnoException, ReaderOptions } from '../types'; import ReaderStream from './stream'; type WalkSignature = typeof fsWalk.walkStream; type StatSignature = typeof fsStat.stat; class TestReader extends ReaderStream { protected _walkStream: WalkSignature = sinon.stub() as unknown as WalkSignature; protected _stat: StatSignature = sinon.stub() as unknown as StatSignature; constructor(options?: Options) { super(new Settings(options)); } public get walkStream(): sinon.SinonStub { return this._walkStream as unknown as sinon.SinonStub; } public get stat(): sinon.SinonStub { return this._stat as unknown as sinon.SinonStub; } } function getReader(options?: Options): TestReader { return new TestReader(options); } function getReaderOptions(options: Partial = {}): ReaderOptions { return { ...options } as unknown as ReaderOptions; } describe('Readers → ReaderStream', () => { describe('Constructor', () => { it('should create instance of class', () => { const reader = getReader(); assert.ok(reader instanceof TestReader); }); }); describe('.dynamic', () => { it('should call fs.walk method', () => { const reader = getReader(); const readerOptions = getReaderOptions(); reader.dynamic('root', readerOptions); assert.ok(reader.walkStream.called); }); }); describe('.static', () => { it('should return entries', (done) => { const reader = getReader(); const readerOptions = getReaderOptions({ entryFilter: () => true }); reader.stat.onFirstCall().yields(null, new Stats()); reader.stat.onSecondCall().yields(null, new Stats()); const entries: Entry[] = []; const stream = reader.static(['a.txt', 'b.txt'], readerOptions); stream.on('data', (entry: Entry) => entries.push(entry)); stream.once('end', () => { assert.strictEqual(entries[0].name, 'a.txt'); assert.strictEqual(entries[1].name, 'b.txt'); done(); }); }); it('should throw an error when the filter does not suppress the error', (done) => { const reader = getReader(); const readerOptions = getReaderOptions({ errorFilter: () => false, entryFilter: () => true }); reader.stat.onFirstCall().yields(tests.errno.getEperm()); reader.stat.onSecondCall().yields(null, new Stats()); const entries: Entry[] = []; const stream = reader.static(['a.txt', 'b.txt'], readerOptions); stream.on('data', (entry: Entry) => entries.push(entry)); stream.once('error', (error: ErrnoException) => { assert.strictEqual(error.code, 'EPERM'); done(); }); }); it('should do not throw an error when the filter suppress the error', (done) => { const reader = getReader(); const readerOptions = getReaderOptions({ errorFilter: () => true, entryFilter: () => true }); reader.stat.onFirstCall().yields(tests.errno.getEnoent()); reader.stat.onSecondCall().yields(null, new Stats()); const entries: Entry[] = []; const stream = reader.static(['a.txt', 'b.txt'], readerOptions); stream.on('data', (entry: Entry) => entries.push(entry)); stream.once('end', () => { assert.strictEqual(entries.length, 1); assert.strictEqual(entries[0].name, 'b.txt'); done(); }); }); it('should do not include entry when the filter excludes it', (done) => { const reader = getReader(); const readerOptions = getReaderOptions({ entryFilter: () => false }); reader.stat.yields(null, new Stats()); const entries: Entry[] = []; const stream = reader.static(['a.txt'], readerOptions); stream.on('data', (entry: Entry) => entries.push(entry)); stream.once('end', () => { assert.strictEqual(entries.length, 0); done(); }); }); }); }); fast-glob-3.2.12/src/readers/stream.ts000066400000000000000000000034051430655000000175270ustar00rootroot00000000000000import * as fs from 'fs'; import { PassThrough, Readable } from 'stream'; import * as fsStat from '@nodelib/fs.stat'; import * as fsWalk from '@nodelib/fs.walk'; import { Entry, ErrnoException, Pattern, ReaderOptions } from '../types'; import Reader from './reader'; export default class ReaderStream extends Reader { protected _walkStream: typeof fsWalk.walkStream = fsWalk.walkStream; protected _stat: typeof fsStat.stat = fsStat.stat; public dynamic(root: string, options: ReaderOptions): Readable { return this._walkStream(root, options); } public static(patterns: Pattern[], options: ReaderOptions): Readable { const filepaths = patterns.map(this._getFullEntryPath, this); const stream = new PassThrough({ objectMode: true }); stream._write = (index: number, _enc, done) => { return this._getEntry(filepaths[index], patterns[index], options) .then((entry) => { if (entry !== null && options.entryFilter(entry)) { stream.push(entry); } if (index === filepaths.length - 1) { stream.end(); } done(); }) .catch(done); }; for (let i = 0; i < filepaths.length; i++) { stream.write(i); } return stream; } private _getEntry(filepath: string, pattern: Pattern, options: ReaderOptions): Promise { return this._getStat(filepath) .then((stats) => this._makeEntry(stats, pattern)) .catch((error: ErrnoException) => { if (options.errorFilter(error)) { return null; } throw error; }); } private _getStat(filepath: string): Promise { return new Promise((resolve, reject) => { this._stat(filepath, this._fsStatSettings, (error: NodeJS.ErrnoException | null, stats) => { return error === null ? resolve(stats) : reject(error); }); }); } } fast-glob-3.2.12/src/readers/sync.spec.ts000066400000000000000000000065461430655000000201520ustar00rootroot00000000000000import * as assert from 'assert'; import { Stats } from '@nodelib/fs.macchiato'; import * as fsStat from '@nodelib/fs.stat'; import * as fsWalk from '@nodelib/fs.walk'; import * as sinon from 'sinon'; import Settings, { Options } from '../settings'; import * as tests from '../tests'; import { ReaderOptions } from '../types'; import ReaderSync from './sync'; type WalkSignature = typeof fsWalk.walkSync; type StatSignature = typeof fsStat.statSync; class TestReader extends ReaderSync { protected _walkSync: WalkSignature = sinon.stub() as unknown as WalkSignature; protected _statSync: StatSignature = sinon.stub() as unknown as StatSignature; constructor(options?: Options) { super(new Settings(options)); } public get walkSync(): sinon.SinonStub { return this._walkSync as unknown as sinon.SinonStub; } public get statSync(): sinon.SinonStub { return this._statSync as unknown as sinon.SinonStub; } } function getReader(options?: Options): TestReader { return new TestReader(options); } function getReaderOptions(options: Partial = {}): ReaderOptions { return { ...options } as unknown as ReaderOptions; } describe('Readers → ReaderSync', () => { describe('Constructor', () => { it('should create instance of class', () => { const reader = getReader(); assert.ok(reader instanceof TestReader); }); }); describe('.dynamic', () => { it('should call fs.walk method', () => { const reader = getReader(); const readerOptions = getReaderOptions(); reader.dynamic('root', readerOptions); assert.ok(reader.walkSync.called); }); }); describe('.static', () => { it('should return entries', () => { const reader = getReader(); const readerOptions = getReaderOptions({ entryFilter: () => true }); reader.statSync.onFirstCall().returns(new Stats()); reader.statSync.onSecondCall().returns(new Stats()); const actual = reader.static(['a.txt', 'b.txt'], readerOptions); assert.strictEqual(actual[0].name, 'a.txt'); assert.strictEqual(actual[1].name, 'b.txt'); }); it('should throw an error when the filter does not suppress the error', () => { const reader = getReader(); const readerOptions = getReaderOptions({ errorFilter: () => false, entryFilter: () => true }); reader.statSync.onFirstCall().throws(tests.errno.getEperm()); reader.statSync.onSecondCall().returns(new Stats()); const expectedErrorMessageRe = /Error: EPERM: operation not permitted/; assert.throws(() => reader.static(['a.txt', 'b.txt'], readerOptions), expectedErrorMessageRe); }); it('should do not throw an error when the filter suppress the error', () => { const reader = getReader(); const readerOptions = getReaderOptions({ errorFilter: () => true, entryFilter: () => true }); reader.statSync.onFirstCall().throws(tests.errno.getEnoent()); reader.statSync.onSecondCall().returns(new Stats()); const actual = reader.static(['a.txt', 'b.txt'], readerOptions); assert.strictEqual(actual.length, 1); assert.strictEqual(actual[0].name, 'b.txt'); }); it('should do not include entry when the filter excludes it', () => { const reader = getReader(); const readerOptions = getReaderOptions({ entryFilter: () => false }); reader.statSync.returns(new Stats()); const actual = reader.static(['a.txt'], readerOptions); assert.strictEqual(actual.length, 0); }); }); }); fast-glob-3.2.12/src/readers/sync.ts000066400000000000000000000024501430655000000172070ustar00rootroot00000000000000import * as fs from 'fs'; import * as fsStat from '@nodelib/fs.stat'; import * as fsWalk from '@nodelib/fs.walk'; import { Entry, ErrnoException, Pattern, ReaderOptions } from '../types'; import Reader from './reader'; export default class ReaderSync extends Reader { protected _walkSync: typeof fsWalk.walkSync = fsWalk.walkSync; protected _statSync: typeof fsStat.statSync = fsStat.statSync; public dynamic(root: string, options: ReaderOptions): Entry[] { return this._walkSync(root, options); } public static(patterns: Pattern[], options: ReaderOptions): Entry[] { const entries: Entry[] = []; for (const pattern of patterns) { const filepath = this._getFullEntryPath(pattern); const entry = this._getEntry(filepath, pattern, options); if (entry === null || !options.entryFilter(entry)) { continue; } entries.push(entry); } return entries; } private _getEntry(filepath: string, pattern: Pattern, options: ReaderOptions): Entry | null { try { const stats = this._getStat(filepath); return this._makeEntry(stats, pattern); } catch (error) { if (options.errorFilter(error as ErrnoException)) { return null; } throw error; } } private _getStat(filepath: string): fs.Stats { return this._statSync(filepath, this._fsStatSettings); } } fast-glob-3.2.12/src/settings.spec.ts000066400000000000000000000036551430655000000174070ustar00rootroot00000000000000import * as assert from 'assert'; import * as os from 'os'; import Settings, { DEFAULT_FILE_SYSTEM_ADAPTER } from './settings'; describe('Settings', () => { it('should return instance with default values', () => { const settings = new Settings(); assert.deepStrictEqual(settings.fs, DEFAULT_FILE_SYSTEM_ADAPTER); assert.deepStrictEqual(settings.ignore, []); assert.ok(!settings.absolute); assert.ok(!settings.baseNameMatch); assert.ok(!settings.dot); assert.ok(!settings.markDirectories); assert.ok(!settings.objectMode); assert.ok(!settings.onlyDirectories); assert.ok(!settings.stats); assert.ok(!settings.suppressErrors); assert.ok(!settings.throwErrorOnBrokenSymbolicLink); assert.ok(settings.braceExpansion); assert.ok(settings.caseSensitiveMatch); assert.ok(settings.deep); assert.ok(settings.extglob); assert.ok(settings.followSymbolicLinks); assert.ok(settings.globstar); assert.ok(settings.onlyFiles); assert.ok(settings.unique); assert.strictEqual(settings.concurrency, os.cpus().length); assert.strictEqual(settings.cwd, process.cwd()); }); it('should return instance with custom values', () => { const settings = new Settings({ onlyFiles: false }); assert.ok(!settings.onlyFiles); }); it('should set the "onlyFiles" option when the "onlyDirectories" is enabled', () => { const settings = new Settings({ onlyDirectories: true }); assert.ok(!settings.onlyFiles); assert.ok(settings.onlyDirectories); }); it('should set the "objectMode" option when the "stats" is enabled', () => { const settings = new Settings({ stats: true }); assert.ok(settings.objectMode); assert.ok(settings.stats); }); it('should return the `fs` option with custom method', () => { const customReaddirSync = (): never[] => []; const settings = new Settings({ fs: { readdirSync: customReaddirSync } }); assert.strictEqual(settings.fs.readdirSync, customReaddirSync); }); }); fast-glob-3.2.12/src/settings.ts000066400000000000000000000130341430655000000164460ustar00rootroot00000000000000import * as fs from 'fs'; import * as os from 'os'; import { FileSystemAdapter, Pattern } from './types'; /** * The `os.cpus` method can return zero. We expect the number of cores to be greater than zero. * https://github.com/nodejs/node/blob/7faeddf23a98c53896f8b574a6e66589e8fb1eb8/lib/os.js#L106-L107 */ const CPU_COUNT = Math.max(os.cpus().length, 1); export const DEFAULT_FILE_SYSTEM_ADAPTER: FileSystemAdapter = { lstat: fs.lstat, lstatSync: fs.lstatSync, stat: fs.stat, statSync: fs.statSync, readdir: fs.readdir, readdirSync: fs.readdirSync }; export type Options = { /** * Return the absolute path for entries. * * @default false */ absolute?: boolean; /** * If set to `true`, then patterns without slashes will be matched against * the basename of the path if it contains slashes. * * @default false */ baseNameMatch?: boolean; /** * Enables Bash-like brace expansion. * * @default true */ braceExpansion?: boolean; /** * Enables a case-sensitive mode for matching files. * * @default true */ caseSensitiveMatch?: boolean; /** * Specifies the maximum number of concurrent requests from a reader to read * directories. * * @default os.cpus().length */ concurrency?: number; /** * The current working directory in which to search. * * @default process.cwd() */ cwd?: string; /** * Specifies the maximum depth of a read directory relative to the start * directory. * * @default Infinity */ deep?: number; /** * Allow patterns to match entries that begin with a period (`.`). * * @default false */ dot?: boolean; /** * Enables Bash-like `extglob` functionality. * * @default true */ extglob?: boolean; /** * Indicates whether to traverse descendants of symbolic link directories. * * @default true */ followSymbolicLinks?: boolean; /** * Custom implementation of methods for working with the file system. * * @default fs.* */ fs?: Partial; /** * Enables recursively repeats a pattern containing `**`. * If `false`, `**` behaves exactly like `*`. * * @default true */ globstar?: boolean; /** * An array of glob patterns to exclude matches. * This is an alternative way to use negative patterns. * * @default [] */ ignore?: Pattern[]; /** * Mark the directory path with the final slash. * * @default false */ markDirectories?: boolean; /** * Returns objects (instead of strings) describing entries. * * @default false */ objectMode?: boolean; /** * Return only directories. * * @default false */ onlyDirectories?: boolean; /** * Return only files. * * @default true */ onlyFiles?: boolean; /** * Enables an object mode (`objectMode`) with an additional `stats` field. * * @default false */ stats?: boolean; /** * By default this package suppress only `ENOENT` errors. * Set to `true` to suppress any error. * * @default false */ suppressErrors?: boolean; /** * Throw an error when symbolic link is broken if `true` or safely * return `lstat` call if `false`. * * @default false */ throwErrorOnBrokenSymbolicLink?: boolean; /** * Ensures that the returned entries are unique. * * @default true */ unique?: boolean; }; export default class Settings { public readonly absolute: boolean = this._getValue(this._options.absolute, false); public readonly baseNameMatch: boolean = this._getValue(this._options.baseNameMatch, false); public readonly braceExpansion: boolean = this._getValue(this._options.braceExpansion, true); public readonly caseSensitiveMatch: boolean = this._getValue(this._options.caseSensitiveMatch, true); public readonly concurrency: number = this._getValue(this._options.concurrency, CPU_COUNT); public readonly cwd: string = this._getValue(this._options.cwd, process.cwd()); public readonly deep: number = this._getValue(this._options.deep, Infinity); public readonly dot: boolean = this._getValue(this._options.dot, false); public readonly extglob: boolean = this._getValue(this._options.extglob, true); public readonly followSymbolicLinks: boolean = this._getValue(this._options.followSymbolicLinks, true); public readonly fs: FileSystemAdapter = this._getFileSystemMethods(this._options.fs); public readonly globstar: boolean = this._getValue(this._options.globstar, true); public readonly ignore: Pattern[] = this._getValue(this._options.ignore, [] as Pattern[]); public readonly markDirectories: boolean = this._getValue(this._options.markDirectories, false); public readonly objectMode: boolean = this._getValue(this._options.objectMode, false); public readonly onlyDirectories: boolean = this._getValue(this._options.onlyDirectories, false); public readonly onlyFiles: boolean = this._getValue(this._options.onlyFiles, true); public readonly stats: boolean = this._getValue(this._options.stats, false); public readonly suppressErrors: boolean = this._getValue(this._options.suppressErrors, false); public readonly throwErrorOnBrokenSymbolicLink: boolean = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, false); public readonly unique: boolean = this._getValue(this._options.unique, true); constructor(private readonly _options: Options = {}) { if (this.onlyDirectories) { this.onlyFiles = false; } if (this.stats) { this.objectMode = true; } } private _getValue(option: T | undefined, value: T): T { return option === undefined ? value : option; } private _getFileSystemMethods(methods: Partial = {}): FileSystemAdapter { return { ...DEFAULT_FILE_SYSTEM_ADAPTER, ...methods }; } } fast-glob-3.2.12/src/tests/000077500000000000000000000000001430655000000153775ustar00rootroot00000000000000fast-glob-3.2.12/src/tests/index.ts000066400000000000000000000004101430655000000170510ustar00rootroot00000000000000import * as entry from './utils/entry'; import * as errno from './utils/errno'; import * as pattern from './utils/pattern'; import * as platform from './utils/platform'; import * as task from './utils/task'; export { entry, errno, pattern, platform, task }; fast-glob-3.2.12/src/tests/smoke/000077500000000000000000000000001430655000000165155ustar00rootroot00000000000000fast-glob-3.2.12/src/tests/smoke/absolute.smoke.ts000066400000000000000000000045321430655000000220240ustar00rootroot00000000000000import * as path from 'path'; import * as smoke from './smoke'; const CWD = process.cwd().replace(/\\/g, '/'); smoke.suite('Smoke → Absolute', [ { pattern: 'fixtures/*', globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: 'fixtures/**', globOptions: { absolute: true }, fgOptions: { absolute: true }, broken: true, issue: 47 }, { pattern: 'fixtures/**/*', globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: 'fixtures/../*', globOptions: { absolute: true }, fgOptions: { absolute: true } } ]); smoke.suite('Smoke → Absolute (ignore)', [ { pattern: 'fixtures/*/*', ignore: 'fixtures/*/nested', globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: 'fixtures/*/*', ignore: '**/nested', globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: 'fixtures/*', ignore: path.posix.join(CWD, 'fixtures', '*'), globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: 'fixtures/**', ignore: path.posix.join(CWD, 'fixtures', '*'), globOptions: { absolute: true }, fgOptions: { absolute: true }, broken: true, issue: 47 } ]); smoke.suite('Smoke → Absolute (cwd)', [ { pattern: '*', cwd: 'fixtures', globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: '**', cwd: 'fixtures', globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: '**/*', cwd: 'fixtures', globOptions: { absolute: true }, fgOptions: { absolute: true } } ]); smoke.suite('Smoke → Absolute (cwd & ignore)', [ { pattern: '*/*', ignore: '*/nested', cwd: 'fixtures', globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: '*/*', ignore: '**/nested', cwd: 'fixtures', globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: '*', ignore: path.posix.join(CWD, 'fixtures', '*'), cwd: 'fixtures', globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: '**', ignore: path.posix.join(CWD, 'fixtures', '*'), cwd: 'fixtures', globOptions: { absolute: true }, fgOptions: { absolute: true } }, { pattern: '**', ignore: path.posix.join(CWD, 'fixtures', '**'), cwd: 'fixtures', globOptions: { absolute: true }, fgOptions: { absolute: true } } ]); fast-glob-3.2.12/src/tests/smoke/base-name-match.ts000066400000000000000000000003011430655000000220010ustar00rootroot00000000000000import * as smoke from './smoke'; smoke.suite('Smoke → MatchBase', [ { pattern: '*.md', cwd: 'fixtures', globOptions: { matchBase: true }, fgOptions: { baseNameMatch: true } } ]); fast-glob-3.2.12/src/tests/smoke/case-sensitive-match.smoke.ts000066400000000000000000000007171430655000000242230ustar00rootroot00000000000000import * as smoke from './smoke'; import * as utils from '..'; smoke.suite('Smoke → CaseSensitiveMatch', [ { pattern: 'fixtures/File.md' }, { pattern: 'fixtures/File.md', globOptions: { nocase: true }, fgOptions: { caseSensitiveMatch: false } }, // ISSUE-276 { pattern: '/tmp/*', globOptions: { nocase: true, nodir: false }, fgOptions: { caseSensitiveMatch: false, onlyFiles: false }, condition: () => !utils.platform.isWindows() } ]); fast-glob-3.2.12/src/tests/smoke/deep.smoke.ts000066400000000000000000000012031430655000000211130ustar00rootroot00000000000000/** * @fileoverview * The `glob` package has no `deep` option. * So emulate it with negative patterns. */ import * as smoke from './smoke'; smoke.suite('Smoke → Deep', [ { pattern: 'fixtures/**', globOptions: { ignore: ['!fixtures/*'] }, fgOptions: { deep: 0 } }, { pattern: 'fixtures/**', globOptions: { ignore: ['!fixtures/{*/,}*'] }, fgOptions: { deep: 2 } } ]); smoke.suite('Smoke → Deep (cwd)', [ { pattern: '**', cwd: 'fixtures', globOptions: { ignore: ['!*'] }, fgOptions: { deep: 0 } }, { pattern: '**', cwd: 'fixtures', globOptions: { ignore: ['!{*/,}*'] }, fgOptions: { deep: 2 } } ]); fast-glob-3.2.12/src/tests/smoke/directories.smoke.ts000066400000000000000000000041141430655000000225160ustar00rootroot00000000000000import * as fsUtils from '../utils/fs'; import * as smoke from './smoke'; /** * Here we expect only directories. * Also we skip the root directory due to ISSUE-47. */ function globFilter(entry: string, filepath: string): boolean { const isRootEntry = entry === 'fixtures' || entry === 'fixtures/'; return fsUtils.isDirectory(filepath) && !isRootEntry; } /** * The 'glob' package automatically adds a trailing slash when the pattern ends with it. * We do not support this behavior and do not want to support it in the future because it is implicit behavior. * We need real use cases if someone needs it. */ function globTransform(entry: string): string { return entry.endsWith('/') ? entry.slice(0, -1) : entry; } smoke.suite('Smoke → Directories', [ { pattern: 'fixtures/*', fgOptions: { onlyDirectories: true }, globFilter, globTransform }, { pattern: 'fixtures/**', fgOptions: { onlyDirectories: true }, globFilter, globTransform }, { pattern: 'fixtures/**/*', fgOptions: { onlyDirectories: true }, globFilter, globTransform }, { pattern: 'fixtures/*/', fgOptions: { onlyDirectories: true }, globFilter, globTransform }, { pattern: 'fixtures/**/', fgOptions: { onlyDirectories: true }, globFilter, globTransform }, { pattern: 'fixtures/**/*/', fgOptions: { onlyDirectories: true }, globFilter, globTransform } ]); smoke.suite('Smoke → Directories (cwd)', [ { pattern: '*', cwd: 'fixtures', fgOptions: { onlyDirectories: true }, globFilter, globTransform }, { pattern: '**', cwd: 'fixtures', fgOptions: { onlyDirectories: true }, globFilter, globTransform }, { pattern: '**/*', cwd: 'fixtures', fgOptions: { onlyDirectories: true }, globFilter, globTransform }, { pattern: '*/', cwd: 'fixtures', fgOptions: { onlyDirectories: true }, globFilter, globTransform }, { pattern: '**/', cwd: 'fixtures', fgOptions: { onlyDirectories: true }, globFilter, globTransform }, { pattern: '**/*/', cwd: 'fixtures', fgOptions: { onlyDirectories: true }, globFilter, globTransform } ]); fast-glob-3.2.12/src/tests/smoke/dot.smoke.ts000066400000000000000000000011361430655000000207710ustar00rootroot00000000000000import * as smoke from './smoke'; smoke.suite('Smoke → Dot', [ { pattern: 'fixtures/*', globOptions: { dot: true }, fgOptions: { dot: true } }, { pattern: 'fixtures/**', globOptions: { dot: true }, fgOptions: { dot: true }, broken: true, issue: 47 }, { pattern: 'fixtures/**/*', globOptions: { dot: true }, fgOptions: { dot: true } }, { pattern: 'fixtures/{.,}*' }, { pattern: 'fixtures/{.*,*}' }, { pattern: 'fixtures/**/{.,}*' }, { pattern: 'fixtures/{.**,**}', broken: true, issue: 47 }, { pattern: 'fixtures/{**/.*,**}', broken: true, issue: 47 } ]); fast-glob-3.2.12/src/tests/smoke/errors.smoke.ts000066400000000000000000000003731430655000000215210ustar00rootroot00000000000000import * as smoke from './smoke'; smoke.suite('Smoke → Errors', [ { pattern: 'non-exist-directory/**' }, { pattern: 'non-exist-file.txt' } ]); smoke.suite('Smoke → Errors (cwd)', [ { pattern: '**', cwd: 'non-exist-directory' } ]); fast-glob-3.2.12/src/tests/smoke/files.smoke.ts000066400000000000000000000013361430655000000213070ustar00rootroot00000000000000import * as smoke from './smoke'; smoke.suite('Smoke → Files', [ { pattern: 'fixtures/*', globOptions: { nodir: true }, fgOptions: { onlyFiles: true } }, { pattern: 'fixtures/**', globOptions: { nodir: true }, fgOptions: { onlyFiles: true } }, { pattern: 'fixtures/**/*', globOptions: { nodir: true }, fgOptions: { onlyFiles: true } } ]); smoke.suite('Smoke → Files (cwd)', [ { pattern: '*', cwd: 'fixtures', globOptions: { nodir: true }, fgOptions: { onlyFiles: true } }, { pattern: '**', cwd: 'fixtures', globOptions: { nodir: true }, fgOptions: { onlyFiles: true } }, { pattern: '**/*', cwd: 'fixtures', globOptions: { nodir: true }, fgOptions: { onlyFiles: true } } ]); fast-glob-3.2.12/src/tests/smoke/ignore.smoke.ts000066400000000000000000000004511430655000000214650ustar00rootroot00000000000000import * as smoke from './smoke'; smoke.suite('Smoke → Ignore', [ { pattern: 'fixtures/**/*', globOptions: { ignore: ['**/*.md'] }, fgOptions: { ignore: ['**/*.md'] } }, { pattern: 'fixtures/**/*', globOptions: { ignore: ['**/*.md'] }, fgOptions: { ignore: ['!**/*.md'] } } ]); fast-glob-3.2.12/src/tests/smoke/mark-directories.smoke.ts000066400000000000000000000002721430655000000234470ustar00rootroot00000000000000import * as smoke from './smoke'; smoke.suite('Smoke → MarkDirectories', [ { pattern: 'fixtures/**/*', globOptions: { mark: true }, fgOptions: { markDirectories: true } } ]); fast-glob-3.2.12/src/tests/smoke/regular.smoke.ts000066400000000000000000000535261430655000000216560ustar00rootroot00000000000000import * as smoke from './smoke'; smoke.suite('Smoke → Regular', [ { pattern: 'fixtures/*' }, { pattern: 'fixtures/**', broken: true, issue: 47 }, { pattern: 'fixtures/**/*' }, { pattern: 'fixtures/*/nested' }, { pattern: 'fixtures/*/nested/*' }, { pattern: 'fixtures/*/nested/**' }, { pattern: 'fixtures/*/nested/**/*' }, { pattern: 'fixtures/**/nested/*' }, { pattern: 'fixtures/**/nested/**' }, { pattern: 'fixtures/**/nested/**/*' }, { pattern: 'fixtures/{first,second}' }, { pattern: 'fixtures/{first,second}/*' }, { pattern: 'fixtures/{first,second}/**' }, { pattern: 'fixtures/{first,second}/**/*' }, { pattern: '@(fixtures)/{first,second}' }, { pattern: '@(fixtures)/{first,second}/*' }, { pattern: 'fixtures/*/{first,second}/*' }, { pattern: 'fixtures/*/{first,second}/*/{nested,file.md}' }, { pattern: 'fixtures/**/{first,second}/**' }, { pattern: 'fixtures/**/{first,second}/{nested,file.md}' }, { pattern: 'fixtures/**/{first,second}/**/{nested,file.md}' }, { pattern: 'fixtures/{first,second}/{nested,file.md}' }, { pattern: 'fixtures/{first,second}/*/nested/*' }, { pattern: 'fixtures/{first,second}/**/nested/**' }, { pattern: 'fixtures/*/{nested,file.md}/*' }, { pattern: 'fixtures/**/{nested,file.md}/*' }, { pattern: './fixtures/*' } ]); smoke.suite('Smoke → Regular (cwd)', [ { pattern: '*', cwd: 'fixtures' }, { pattern: '**', cwd: 'fixtures' }, { pattern: '**/*', cwd: 'fixtures' }, { pattern: '*/nested', cwd: 'fixtures' }, { pattern: '*/nested/*', cwd: 'fixtures' }, { pattern: '*/nested/**', cwd: 'fixtures' }, { pattern: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '**/nested/*', cwd: 'fixtures' }, { pattern: '**/nested/**', cwd: 'fixtures' }, { pattern: '**/nested/**/*', cwd: 'fixtures' }, { pattern: '{first,second}', cwd: 'fixtures' }, { pattern: '{first,second}/*', cwd: 'fixtures' }, { pattern: '{first,second}/**', cwd: 'fixtures' }, { pattern: '{first,second}/**/*', cwd: 'fixtures' }, { pattern: '*/{first,second}/*', cwd: 'fixtures' }, { pattern: '*/{first,second}/*/{nested,file.md}', cwd: 'fixtures' }, { pattern: '**/{first,second}/**', cwd: 'fixtures' }, { pattern: '**/{first,second}/{nested,file.md}', cwd: 'fixtures' }, { pattern: '**/{first,second}/**/{nested,file.md}', cwd: 'fixtures' }, { pattern: '{first,second}/{nested,file.md}', cwd: 'fixtures' }, { pattern: '{first,second}/*/nested/*', cwd: 'fixtures' }, { pattern: '{first,second}/**/nested/**', cwd: 'fixtures' }, { pattern: '*/{nested,file.md}/*', cwd: 'fixtures' }, { pattern: '**/{nested,file.md}/*', cwd: 'fixtures' }, { pattern: './*', cwd: 'fixtures', correct: true, reason: 'The `node-glob package returns entries with leading `./`' } ]); smoke.suite('Smoke → Regular (ignore)', [ [ { pattern: 'fixtures/*', ignore: '*' }, { pattern: 'fixtures/*', ignore: '**' }, { pattern: 'fixtures/*', ignore: '**/*' }, { pattern: 'fixtures/**', ignore: '*' }, { pattern: 'fixtures/**', ignore: '**' }, { pattern: 'fixtures/**', ignore: '**/*' }, { pattern: 'fixtures/**/*', ignore: '*' }, { pattern: 'fixtures/**/*', ignore: '**' }, { pattern: 'fixtures/**/*', ignore: '**/*' } ], [ { pattern: 'fixtures/*', ignore: 'fixtures/*' }, { pattern: 'fixtures/*', ignore: 'fixtures/**' }, { pattern: 'fixtures/*', ignore: 'fixtures/**/*' }, { pattern: 'fixtures/**', ignore: 'fixtures/*', broken: true, issue: 47 }, { pattern: 'fixtures/**', ignore: 'fixtures/**' }, { pattern: 'fixtures/**', ignore: 'fixtures/**/*', broken: true, issue: 47 }, { pattern: 'fixtures/**/*', ignore: 'fixtures/*' }, { pattern: 'fixtures/**/*', ignore: 'fixtures/**' }, { pattern: 'fixtures/**/*', ignore: 'fixtures/**/*' } ], [ { pattern: 'fixtures/*', ignore: 'nested' }, { pattern: 'fixtures/**', ignore: 'nested', broken: true, issue: 47 }, { pattern: 'fixtures/**/*', ignore: 'nested' } ], [ { pattern: 'fixtures/*', ignore: '*/nested' }, { pattern: 'fixtures/*', ignore: '**/nested' }, { pattern: 'fixtures/**', ignore: '*/nested', broken: true, issue: 47 }, { pattern: 'fixtures/**', ignore: '**/nested', broken: true, issue: 47, correct: true, reason: 'The negative pattern excludes any entries (files and directories) at any nesting level.' }, { pattern: 'fixtures/**/*', ignore: '*/nested' }, { pattern: 'fixtures/**/*', ignore: '**/nested', correct: true, reason: 'The negative pattern excludes any entries (files and directories) at any nesting level.' } ], [ { pattern: 'fixtures/*', ignore: '*/nested/*' }, { pattern: 'fixtures/*', ignore: '**/nested/*' }, { pattern: 'fixtures/**', ignore: '*/nested/*', broken: true, issue: 47 }, { pattern: 'fixtures/**', ignore: '**/nested/*', broken: true, issue: 47 }, { pattern: 'fixtures/**/*', ignore: '*/nested/*' }, { pattern: 'fixtures/**/*', ignore: '**/nested/*' } ], [ { pattern: 'fixtures/*', ignore: '*/nested/**' }, { pattern: 'fixtures/*', ignore: '**/nested/**' }, { pattern: 'fixtures/**', ignore: '*/nested/**', broken: true, issue: 47 }, { pattern: 'fixtures/**', ignore: '**/nested/**', broken: true, issue: 47 }, { pattern: 'fixtures/**/*', ignore: '*/nested/**' }, { pattern: 'fixtures/**/*', ignore: '**/nested/**' } ], [ { pattern: 'fixtures/*', ignore: '*/nested/**/*' }, { pattern: 'fixtures/*', ignore: '**/nested/**/*' }, { pattern: 'fixtures/**', ignore: '*/nested/**/*', broken: true, issue: 47 }, { pattern: 'fixtures/**', ignore: '**/nested/**/*', broken: true, issue: 47 }, { pattern: 'fixtures/**/*', ignore: '*/nested/**/*' }, { pattern: 'fixtures/**/*', ignore: '**/nested/**/*' } ], [ { pattern: 'fixtures/*/nested', ignore: '*' }, { pattern: 'fixtures/*/nested', ignore: '**' }, { pattern: 'fixtures/*/nested', ignore: '**/*' }, { pattern: 'fixtures/*/nested', ignore: 'nested' }, { pattern: 'fixtures/*/nested', ignore: 'nested/*' }, { pattern: 'fixtures/*/nested', ignore: 'nested/**' }, { pattern: 'fixtures/*/nested', ignore: 'nested/**/*' }, { pattern: 'fixtures/*/nested', ignore: '*/nested/*' }, { pattern: 'fixtures/*/nested', ignore: '*/nested/**' }, { pattern: 'fixtures/*/nested', ignore: '*/nested/**/*' }, { pattern: 'fixtures/*/nested', ignore: '**/nested/*' }, { pattern: 'fixtures/*/nested', ignore: '**/nested/**' }, { pattern: 'fixtures/*/nested', ignore: '**/nested/**/*' } ], [ { pattern: 'fixtures/*/nested/*', ignore: '*' }, { pattern: 'fixtures/*/nested/*', ignore: '**' }, { pattern: 'fixtures/*/nested/*', ignore: '**/*' }, { pattern: 'fixtures/*/nested/*', ignore: 'nested' }, { pattern: 'fixtures/*/nested/*', ignore: 'nested/*' }, { pattern: 'fixtures/*/nested/*', ignore: 'nested/**' }, { pattern: 'fixtures/*/nested/*', ignore: 'nested/**/*' }, { pattern: 'fixtures/*/nested/*', ignore: '*/nested/*' }, { pattern: 'fixtures/*/nested/*', ignore: '*/nested/**' }, { pattern: 'fixtures/*/nested/*', ignore: '*/nested/**/*' }, { pattern: 'fixtures/*/nested/*', ignore: '**/nested/*' }, { pattern: 'fixtures/*/nested/*', ignore: '**/nested/**' }, { pattern: 'fixtures/*/nested/*', ignore: '**/nested/**/*' } ], [ { pattern: 'fixtures/*/nested/**', ignore: '*' }, { pattern: 'fixtures/*/nested/**', ignore: '**' }, { pattern: 'fixtures/*/nested/**', ignore: '**/*' }, { pattern: 'fixtures/*/nested/**', ignore: 'nested' }, { pattern: 'fixtures/*/nested/**', ignore: 'nested/*' }, { pattern: 'fixtures/*/nested/**', ignore: 'nested/**' }, { pattern: 'fixtures/*/nested/**', ignore: 'nested/**/*' }, { pattern: 'fixtures/*/nested/**', ignore: '*/nested/*' }, { pattern: 'fixtures/*/nested/**', ignore: '*/nested/**' }, { pattern: 'fixtures/*/nested/**', ignore: '*/nested/**/*' }, { pattern: 'fixtures/*/nested/**', ignore: '**/nested/*' }, { pattern: 'fixtures/*/nested/**', ignore: '**/nested/**' }, { pattern: 'fixtures/*/nested/**', ignore: '**/nested/**/*' } ], [ { pattern: 'fixtures/*/nested/**/*', ignore: '*' }, { pattern: 'fixtures/*/nested/**/*', ignore: '**' }, { pattern: 'fixtures/*/nested/**/*', ignore: '**/*' }, { pattern: 'fixtures/*/nested/**/*', ignore: 'nested' }, { pattern: 'fixtures/*/nested/**/*', ignore: 'nested/*' }, { pattern: 'fixtures/*/nested/**/*', ignore: 'nested/**' }, { pattern: 'fixtures/*/nested/**/*', ignore: 'nested/**/*' }, { pattern: 'fixtures/*/nested/**/*', ignore: '*/nested/*' }, { pattern: 'fixtures/*/nested/**/*', ignore: '*/nested/**' }, { pattern: 'fixtures/*/nested/**/*', ignore: '*/nested/**/*' }, { pattern: 'fixtures/*/nested/**/*', ignore: '**/nested/*' }, { pattern: 'fixtures/*/nested/**/*', ignore: '**/nested/**' }, { pattern: 'fixtures/*/nested/**/*', ignore: '**/nested/**/*' } ], [ { pattern: 'fixtures/**/nested/*', ignore: '*' }, { pattern: 'fixtures/**/nested/*', ignore: '**' }, { pattern: 'fixtures/**/nested/*', ignore: '**/*' }, { pattern: 'fixtures/**/nested/*', ignore: 'nested' }, { pattern: 'fixtures/**/nested/*', ignore: 'nested/*' }, { pattern: 'fixtures/**/nested/*', ignore: 'nested/**' }, { pattern: 'fixtures/**/nested/*', ignore: 'nested/**/*' }, { pattern: 'fixtures/**/nested/*', ignore: '*/nested/*' }, { pattern: 'fixtures/**/nested/*', ignore: '*/nested/**' }, { pattern: 'fixtures/**/nested/*', ignore: '*/nested/**/*' }, { pattern: 'fixtures/**/nested/*', ignore: '**/nested/*' }, { pattern: 'fixtures/**/nested/*', ignore: '**/nested/**' }, { pattern: 'fixtures/**/nested/*', ignore: '**/nested/**/*' } ], [ { pattern: 'fixtures/**/nested/**', ignore: '*' }, { pattern: 'fixtures/**/nested/**', ignore: '**' }, { pattern: 'fixtures/**/nested/**', ignore: '**/*' }, { pattern: 'fixtures/**/nested/**', ignore: 'nested' }, { pattern: 'fixtures/**/nested/**', ignore: 'nested/*' }, { pattern: 'fixtures/**/nested/**', ignore: 'nested/**' }, { pattern: 'fixtures/**/nested/**', ignore: 'nested/**/*' }, { pattern: 'fixtures/**/nested/**', ignore: '*/nested/*' }, { pattern: 'fixtures/**/nested/**', ignore: '*/nested/**' }, { pattern: 'fixtures/**/nested/**', ignore: '*/nested/**/*' }, { pattern: 'fixtures/**/nested/**', ignore: '**/nested/*' }, { pattern: 'fixtures/**/nested/**', ignore: '**/nested/**' }, { pattern: 'fixtures/**/nested/**', ignore: '**/nested/**/*' } ], [ { pattern: 'fixtures/**/nested/**/*', ignore: '*' }, { pattern: 'fixtures/**/nested/**/*', ignore: '**' }, { pattern: 'fixtures/**/nested/**/*', ignore: '**/*' }, { pattern: 'fixtures/**/nested/**/*', ignore: 'nested' }, { pattern: 'fixtures/**/nested/**/*', ignore: 'nested/*' }, { pattern: 'fixtures/**/nested/**/*', ignore: 'nested/**' }, { pattern: 'fixtures/**/nested/**/*', ignore: 'nested/**/*' }, { pattern: 'fixtures/**/nested/**/*', ignore: '*/nested/*' }, { pattern: 'fixtures/**/nested/**/*', ignore: '*/nested/**' }, { pattern: 'fixtures/**/nested/**/*', ignore: '*/nested/**/*' }, { pattern: 'fixtures/**/nested/**/*', ignore: '**/nested/*' }, { pattern: 'fixtures/**/nested/**/*', ignore: '**/nested/**' }, { pattern: 'fixtures/**/nested/**/*', ignore: '**/nested/**/*' } ] ]); smoke.suite('Smoke → Regular (ignore & cwd)', [ [ { pattern: '*', ignore: '*', cwd: 'fixtures' }, { pattern: '*', ignore: '**', cwd: 'fixtures' }, { pattern: '*', ignore: '**/*', cwd: 'fixtures' }, { pattern: '**', ignore: '*', cwd: 'fixtures' }, { pattern: '**', ignore: '**', cwd: 'fixtures' }, { pattern: '**', ignore: '**/*', cwd: 'fixtures' }, { pattern: '**/*', ignore: '*', cwd: 'fixtures' }, { pattern: '**/*', ignore: '**', cwd: 'fixtures' }, { pattern: '**/*', ignore: '**/*', cwd: 'fixtures' } ], [ { pattern: '*', ignore: 'fixtures/*', cwd: 'fixtures' }, { pattern: '*', ignore: 'fixtures/**', cwd: 'fixtures' }, { pattern: '*', ignore: 'fixtures/**/*', cwd: 'fixtures' }, { pattern: '**', ignore: 'fixtures/*', cwd: 'fixtures' }, { pattern: '**', ignore: 'fixtures/**', cwd: 'fixtures' }, { pattern: '**', ignore: 'fixtures/**/*', cwd: 'fixtures' }, { pattern: '**/*', ignore: 'fixtures/*', cwd: 'fixtures' }, { pattern: '**/*', ignore: 'fixtures/**', cwd: 'fixtures' }, { pattern: '**/*', ignore: 'fixtures/**/*', cwd: 'fixtures' } ], [ { pattern: '*', ignore: 'nested', cwd: 'fixtures' }, { pattern: '*', ignore: 'nested', cwd: 'fixtures' }, { pattern: '**', ignore: 'nested', cwd: 'fixtures' }, { pattern: '**', ignore: 'nested', cwd: 'fixtures' }, { pattern: '**/*', ignore: 'nested', cwd: 'fixtures' }, { pattern: '**/*', ignore: 'nested', cwd: 'fixtures' } ], [ { pattern: '*', ignore: '*/nested', cwd: 'fixtures' }, { pattern: '*', ignore: '**/nested', cwd: 'fixtures' }, { pattern: '**', ignore: '*/nested', cwd: 'fixtures', correct: true, reason: 'The negative pattern excludes any entries (files and directories) at any nesting level.' }, { pattern: '**', ignore: '**/nested', cwd: 'fixtures', correct: true, reason: 'The negative pattern excludes any entries (files and directories) at any nesting level.' }, { pattern: '**/*', ignore: '*/nested', cwd: 'fixtures', correct: true, reason: 'The negative pattern excludes any entries (files and directories) at any nesting level.' }, { pattern: '**/*', ignore: '**/nested', cwd: 'fixtures', correct: true, reason: 'The negative pattern excludes any entries (files and directories) at any nesting level.' } ], [ { pattern: '*', ignore: '*/nested/*', cwd: 'fixtures' }, { pattern: '*', ignore: '**/nested/*', cwd: 'fixtures' }, { pattern: '**', ignore: '*/nested/*', cwd: 'fixtures' }, { pattern: '**', ignore: '**/nested/*', cwd: 'fixtures' }, { pattern: '**/*', ignore: '*/nested/*', cwd: 'fixtures' }, { pattern: '**/*', ignore: '**/nested/*', cwd: 'fixtures' } ], [ { pattern: '*', ignore: '*/nested/**', cwd: 'fixtures' }, { pattern: '*', ignore: '**/nested/**', cwd: 'fixtures' }, { pattern: '**', ignore: '*/nested/**', cwd: 'fixtures' }, { pattern: '**', ignore: '**/nested/**', cwd: 'fixtures' }, { pattern: '**/*', ignore: '*/nested/**', cwd: 'fixtures' }, { pattern: '**/*', ignore: '**/nested/**', cwd: 'fixtures' } ], [ { pattern: '*', ignore: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '*', ignore: '**/nested/**/*', cwd: 'fixtures' }, { pattern: '**', ignore: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '**', ignore: '**/nested/**/*', cwd: 'fixtures' }, { pattern: '**/*', ignore: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '**/*', ignore: '**/nested/**/*', cwd: 'fixtures' } ], [ { pattern: '*/nested', ignore: '*', cwd: 'fixtures' }, { pattern: '*/nested', ignore: '**', cwd: 'fixtures' }, { pattern: '*/nested', ignore: '**/*', cwd: 'fixtures' }, { pattern: '*/nested', ignore: 'nested', cwd: 'fixtures' }, { pattern: '*/nested', ignore: 'nested/*', cwd: 'fixtures' }, { pattern: '*/nested', ignore: 'nested/**', cwd: 'fixtures' }, { pattern: '*/nested', ignore: 'nested/**/*', cwd: 'fixtures' }, { pattern: '*/nested', ignore: '*/nested/*', cwd: 'fixtures' }, { pattern: '*/nested', ignore: '*/nested/**', cwd: 'fixtures' }, { pattern: '*/nested', ignore: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '*/nested', ignore: '**/nested/*', cwd: 'fixtures' }, { pattern: '*/nested', ignore: '**/nested/**', cwd: 'fixtures' }, { pattern: '*/nested', ignore: '**/nested/**/*', cwd: 'fixtures' } ], [ { pattern: '*/nested/*', ignore: '*', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: '**', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: '**/*', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: 'nested', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: 'nested/*', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: 'nested/**', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: 'nested/**/*', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: '*/nested/*', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: '*/nested/**', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: '**/nested/*', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: '**/nested/**', cwd: 'fixtures' }, { pattern: '*/nested/*', ignore: '**/nested/**/*', cwd: 'fixtures' } ], [ { pattern: '*/nested/**', ignore: '*', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: '**', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: '**/*', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: 'nested', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: 'nested/*', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: 'nested/**', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: 'nested/**/*', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: '*/nested/*', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: '*/nested/**', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: '**/nested/*', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: '**/nested/**', cwd: 'fixtures' }, { pattern: '*/nested/**', ignore: '**/nested/**/*', cwd: 'fixtures' } ], [ { pattern: '*/nested/**/*', ignore: '*', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: '**', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: '**/*', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: 'nested', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: 'nested/*', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: 'nested/**', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: 'nested/**/*', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: '*/nested/*', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: '*/nested/**', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: '**/nested/*', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: '**/nested/**', cwd: 'fixtures' }, { pattern: '*/nested/**/*', ignore: '**/nested/**/*', cwd: 'fixtures' } ], [ { pattern: '**/nested/*', ignore: '*', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: '**', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: '**/*', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: 'nested', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: 'nested/*', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: 'nested/**', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: 'nested/**/*', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: '*/nested/*', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: '*/nested/**', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: '**/nested/*', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: '**/nested/**', cwd: 'fixtures' }, { pattern: '**/nested/*', ignore: '**/nested/**/*', cwd: 'fixtures' } ], [ { pattern: '**/nested/**', ignore: '*', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: '**', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: '**/*', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: 'nested', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: 'nested/*', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: 'nested/**', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: 'nested/**/*', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: '*/nested/*', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: '*/nested/**', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: '**/nested/*', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: '**/nested/**', cwd: 'fixtures' }, { pattern: '**/nested/**', ignore: '**/nested/**/*', cwd: 'fixtures' } ], [ { pattern: '**/nested/**/*', ignore: '*', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: '**', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: '**/*', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: 'nested', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: 'nested/*', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: 'nested/**', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: 'nested/**/*', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: '*/nested/*', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: '*/nested/**', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: '*/nested/**/*', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: '**/nested/*', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: '**/nested/**', cwd: 'fixtures' }, { pattern: '**/nested/**/*', ignore: '**/nested/**/*', cwd: 'fixtures' } ] ]); smoke.suite('Smoke → Regular (relative)', [ { pattern: '../*', cwd: 'fixtures/first' }, { pattern: '../**', cwd: 'fixtures/first', broken: true, issue: 47 }, { pattern: '../../*', cwd: 'fixtures/first/nested' }, { pattern: '../{first,second}', cwd: 'fixtures/first' }, { pattern: './../*', cwd: 'fixtures/first' } ]); smoke.suite('Smoke → Regular (relative & ignore)', [ { pattern: './../*', cwd: 'fixtures/first', ignore: '../*', correct: true, reason: 'The `node-glob` package does not exclude files, although the `../*` pattern can be applied here.' }, { pattern: './../*', cwd: 'fixtures/first', ignore: './../*' }, { pattern: './../*', cwd: 'fixtures/first', ignore: '**' }, { pattern: '../*', cwd: 'fixtures/first', ignore: '../*' }, { pattern: '../*', cwd: 'fixtures/first', ignore: '**' }, { pattern: '../../*', cwd: 'fixtures/first/nested', ignore: '../../*' }, { pattern: '../../*', cwd: 'fixtures/first/nested', ignore: '**' }, { pattern: '../{first,second}', cwd: 'fixtures/first', ignore: '../first/**' }, { pattern: '../{first,second}', cwd: 'fixtures/first', ignore: '**/first/**' } ]); smoke.suite('Smoke -> Regular (negative group)', [ { pattern: '**/!(*.md)', cwd: 'fixtures/first' } ]); smoke.suite('Smoke -> Regular (segmented lists)', [ { pattern: '{book.xml,**/library/*/book.md}', cwd: 'fixtures/third', broken: true, issue: 365 }, { pattern: '{book.xml,library/**/a/book.md}', cwd: 'fixtures/third' } ]); fast-glob-3.2.12/src/tests/smoke/root.smoke.ts000066400000000000000000000022741430655000000211720ustar00rootroot00000000000000import * as path from 'path'; import * as smoke from './smoke'; import * as utils from '..'; const CWD = process.cwd().replace(/\\/g, '/'); const ROOT = path.parse(CWD).root; smoke.suite('Smoke → Root', [ { pattern: '/*', condition: () => !utils.platform.isWindows() }, { pattern: '/tmp/*', condition: () => !utils.platform.isWindows() }, { pattern: '/*', condition: () => utils.platform.isSupportReaddirWithFileTypes() && utils.platform.isWindows(), correct: true, reason: 'The `node-glob` packages returns items with resolve path for the current disk letter' }, // UNC pattern without dynamic sections in the base section { pattern: `//?/${ROOT}*`, condition: () => utils.platform.isSupportReaddirWithFileTypes() && utils.platform.isWindows(), correct: true, reason: 'The `node-glob` package does not allow to use UNC in patterns' } ]); smoke.suite('Smoke → Root (cwd)', [ { pattern: '*', cwd: ROOT, condition: () => !utils.platform.isWindows() || utils.platform.isSupportReaddirWithFileTypes() }, // UNC on Windows { pattern: '*', cwd: `//?/${ROOT}`, condition: () => utils.platform.isSupportReaddirWithFileTypes() && utils.platform.isWindows() } ]); fast-glob-3.2.12/src/tests/smoke/smoke.ts000066400000000000000000000131121430655000000202010ustar00rootroot00000000000000import * as assert from 'assert'; import * as path from 'path'; import * as glob from 'glob'; import * as fg from '../..'; import { Options } from '../../settings'; import { Pattern } from '../../types'; import Table = require('easy-table'); // eslint-disable-line @typescript-eslint/no-require-imports export type SmokeTest = { pattern: Pattern; ignore?: Pattern; cwd?: string; globOptions?: glob.IOptions; globFilter?: (entry: string, filepath: string) => boolean; globTransform?: (entry: string) => string; fgOptions?: Options; /** * Allow to run only one test case with debug information. */ debug?: boolean; /** * Mark test case as broken. This is requires a issue to repair. */ broken?: boolean; issue?: number | number[]; /** * Mark test case as correct. This is requires a reason why is true. */ correct?: boolean; reason?: string; /** * The ability to conditionally run the test. */ condition?: () => boolean; }; type MochaDefinition = Mocha.TestFunction | Mocha.ExclusiveTestFunction; type DebugCompareTestMarker = '+' | '-'; export function suite(name: string, tests: Array): void { const testCases = getTestCases(tests); describe(name, () => { for (const test of testCases) { const title = getTestCaseTitle(test); const definition = getTestCaseMochaDefinition(test); definition(`${title} (sync)`, () => testCaseRunner(test, getFastGlobEntriesSync)); definition(`${title} (async)`, () => testCaseRunner(test, getFastGlobEntriesAsync)); definition(`${title} (stream)`, () => testCaseRunner(test, getFastGlobEntriesStream)); } }); } function getTestCases(tests: Array): SmokeTest[] { return ([] as SmokeTest[]).concat(...tests); } function getTestCaseTitle(test: SmokeTest): string { let title = `pattern: '${test.pattern}'`; if (test.ignore !== undefined) { title += `, ignore: '${test.ignore}'`; } if (test.broken !== undefined) { title += ` (broken - ${test.issue})`; } if (test.correct !== undefined) { title += ' (correct)'; } return title; } function getTestCaseMochaDefinition(test: SmokeTest): MochaDefinition { if (test.debug === true) { return it.only; } if (test.condition?.() === false) { return it.skip; } return it; } async function testCaseRunner(test: SmokeTest, func: typeof getFastGlobEntriesSync | typeof getFastGlobEntriesAsync): Promise { const expected = getNodeGlobEntries(test); const actual = await func(test.pattern, test.ignore, test.cwd, test.fgOptions); if (test.debug === true) { const report = generateDebugReport(expected, actual); console.log(report); } if (test.broken === true && test.issue === undefined) { assert.fail("This test is marked as «broken», but it doesn't have a issue key."); } if (test.correct === true && test.reason === undefined) { assert.fail("This test is marked as «correct», but it doesn't have a reason."); } const isInvertedTest = test.broken === true || test.correct === true; const assertAction: typeof assert.deepStrictEqual = isInvertedTest ? assert.notDeepStrictEqual : assert.deepStrictEqual; assertAction(actual, expected); } function generateDebugReport(expected: string[], actual: string[]): string | null { const table = new Table(); const items = actual.length > expected.length ? actual : expected; if (items.length === 0) { return null; } for (const item of items) { table.cell('FIXTURES', item); table.cell('NODE_GLOB', getTestMarker(expected, item)); table.cell('FAST_GLOB', getTestMarker(actual, item)); table.newRow(); } return table.toString(); } function getTestMarker(items: string[], item: string): DebugCompareTestMarker { return items.includes(item) ? '+' : '-'; } function getNodeGlobEntries(options: SmokeTest): string[] { const pattern = options.pattern; const cwd = options.cwd === undefined ? process.cwd() : options.cwd; const ignore = options.ignore === undefined ? [] : [options.ignore]; const globFilter = options.globFilter; const globTransform = options.globTransform; let entries = glob.sync(pattern, { cwd, ignore, ...options.globOptions }); if (globFilter !== undefined) { entries = entries.filter((entry) => { const filepath = path.join(cwd, entry); return globFilter(entry, filepath); }); } if (globTransform !== undefined) { entries = entries.map((entry) => globTransform(entry)); } entries.sort((a, b) => a.localeCompare(b)); return entries; } function getFastGlobEntriesSync(pattern: Pattern, ignore?: Pattern, cwd?: string, options?: Options): string[] { return fg.sync(pattern, getFastGlobOptions(ignore, cwd, options)).sort((a, b) => a.localeCompare(b)); } function getFastGlobEntriesAsync(pattern: Pattern, ignore?: Pattern, cwd?: string, options?: Options): Promise { return fg(pattern, getFastGlobOptions(ignore, cwd, options)).then((entries) => { entries.sort((a, b) => a.localeCompare(b)); return entries; }); } function getFastGlobEntriesStream(pattern: Pattern, ignore?: Pattern, cwd?: string, options?: Options): Promise { const entries: string[] = []; const stream = fg.stream(pattern, getFastGlobOptions(ignore, cwd, options)); return new Promise((resolve, reject) => { stream.on('data', (entry: string) => entries.push(entry)); stream.once('error', reject); stream.once('end', () => { entries.sort((a, b) => a.localeCompare(b)); resolve(entries); }); }); } function getFastGlobOptions(ignore?: Pattern, cwd?: string, options?: Options): Options { return { cwd: cwd === undefined ? process.cwd() : cwd, ignore: ignore === undefined ? [] : [ignore], onlyFiles: false, ...options }; } fast-glob-3.2.12/src/tests/smoke/static.smoke.ts000066400000000000000000000042261430655000000214750ustar00rootroot00000000000000import * as smoke from './smoke'; smoke.suite('Smoke → Static', [ { pattern: 'fixtures' }, { pattern: 'fixtures/file.md' }, { pattern: 'fixtures/first' } ]); smoke.suite('Smoke → Static (cwd)', [ { pattern: 'file.md', cwd: 'fixtures' }, { pattern: 'first', cwd: 'fixtures' } ]); smoke.suite('Smoke → Static (ignore)', [ // Files [ { pattern: 'fixtures/file.md', ignore: 'file.md' }, { pattern: 'fixtures/file.md', ignore: '*.md' }, { pattern: 'fixtures/file.md', ignore: '*' }, { pattern: 'fixtures/file.md', ignore: '**' }, { pattern: 'fixtures/file.md', ignore: '**/*' }, { pattern: 'fixtures/file.md', ignore: 'fixtures/file.md' }, { pattern: 'fixtures/file.md', ignore: 'fixtures/*.md' }, { pattern: 'fixtures/file.md', ignore: 'fixtures/*' }, { pattern: 'fixtures/file.md', ignore: 'fixtures/**' }, { pattern: 'fixtures/file.md', ignore: 'fixtures/**/*' } ], // Directories [ { pattern: 'fixtures/first', ignore: 'first' }, { pattern: 'fixtures/first', ignore: '*' }, { pattern: 'fixtures/first', ignore: '**' }, { pattern: 'fixtures/first', ignore: '**/*' }, { pattern: 'fixtures/first', ignore: 'fixtures/first' }, { pattern: 'fixtures/first', ignore: 'fixtures/*' }, { pattern: 'fixtures/first', ignore: 'fixtures/**' }, { pattern: 'fixtures/first', ignore: 'fixtures/**/*' } ] ]); smoke.suite('Smoke → Static (ignore & cwd)', [ // Files [ { pattern: 'fixtures/file.md', ignore: 'file.md', cwd: 'fixtures' }, { pattern: 'fixtures/file.md', ignore: '*.md', cwd: 'fixtures' }, { pattern: 'fixtures/file.md', ignore: '*', cwd: 'fixtures' }, { pattern: 'fixtures/file.md', ignore: '**', cwd: 'fixtures' }, { pattern: 'fixtures/file.md', ignore: '**/*', cwd: 'fixtures' } ], // Directories [ { pattern: 'fixtures/first', ignore: 'first', cwd: 'fixtures' }, { pattern: 'fixtures/first', ignore: '*', cwd: 'fixtures' }, { pattern: 'fixtures/first', ignore: '**', cwd: 'fixtures' }, { pattern: 'fixtures/first', ignore: '**/*', cwd: 'fixtures' } ] ]); smoke.suite('Smoke → Static (relative)', [ { pattern: '../file.md', cwd: 'fixtures/first' }, { pattern: '../../file.md', cwd: 'fixtures/first/nested' } ]); fast-glob-3.2.12/src/tests/utils/000077500000000000000000000000001430655000000165375ustar00rootroot00000000000000fast-glob-3.2.12/src/tests/utils/entry.ts000066400000000000000000000021561430655000000202540ustar00rootroot00000000000000import * as path from 'path'; import { Dirent, Stats } from '@nodelib/fs.macchiato'; import { Entry } from '../../types'; class EntryBuilder { private _isFile: boolean = true; private _isDirectory: boolean = false; private _isSymbolicLink: boolean = false; private readonly _entry: Entry = { name: '', path: '', dirent: new Dirent() }; public path(filepath: string): this { this._entry.name = path.basename(filepath); this._entry.path = filepath; return this; } public file(): this { this._isFile = true; this._isDirectory = false; return this; } public directory(): this { this._isDirectory = true; this._isFile = false; return this; } public symlink(): this { this._isSymbolicLink = true; return this; } public stats(): this { this._entry.stats = new Stats(); return this; } public build(): Entry { this._entry.dirent = new Dirent({ name: this._entry.name, isFile: this._isFile, isDirectory: this._isDirectory, isSymbolicLink: this._isSymbolicLink }); return this._entry; } } export function builder(): EntryBuilder { return new EntryBuilder(); } fast-glob-3.2.12/src/tests/utils/errno.ts000066400000000000000000000007111430655000000202330ustar00rootroot00000000000000import { ErrnoException } from '../../types'; class SystemError extends Error implements ErrnoException { constructor(public readonly code: string, message: string) { super(`${code}: ${message}`); this.name = 'SystemError'; } } export function getEnoent(): ErrnoException { return new SystemError('ENOENT', 'no such file or directory'); } export function getEperm(): ErrnoException { return new SystemError('EPERM', 'operation not permitted'); } fast-glob-3.2.12/src/tests/utils/fs.ts000066400000000000000000000002331430655000000175150ustar00rootroot00000000000000import * as fs from 'fs'; export function isDirectory(filepath: string): boolean { const stats = fs.lstatSync(filepath); return stats.isDirectory(); } fast-glob-3.2.12/src/tests/utils/pattern.ts000066400000000000000000000031611430655000000205650ustar00rootroot00000000000000import { Pattern, MicromatchOptions } from '../../types'; import * as utils from '../../utils'; import { PatternSegment, PatternInfo } from '../../providers/matchers/matcher'; class PatternSegmentBuilder { private readonly _segment: PatternSegment = { dynamic: false, pattern: '' }; public dynamic(): this { this._segment.dynamic = true; return this; } public pattern(pattern: Pattern): this { this._segment.pattern = pattern; return this; } public build(options: MicromatchOptions = {}): PatternSegment { if (!this._segment.dynamic) { return this._segment; } return { ...this._segment, patternRe: utils.pattern.makeRe(this._segment.pattern, options) }; } } class PatternInfoBuilder { private readonly _section: PatternInfo = { complete: true, pattern: '', segments: [], sections: [] }; public section(...segments: PatternSegment[]): this { this._section.sections.push(segments); if (this._section.segments.length === 0) { this._section.complete = true; this._section.segments.push(...segments); } else { this._section.complete = false; const globstar = segment().dynamic().pattern('**').build(); this._section.segments.push(globstar, ...segments); } return this; } public build(): PatternInfo { return { ...this._section, pattern: this._buildPattern() }; } private _buildPattern(): Pattern { return this._section.segments.map((segment) => segment.pattern).join('/'); } } export function segment(): PatternSegmentBuilder { return new PatternSegmentBuilder(); } export function info(): PatternInfoBuilder { return new PatternInfoBuilder(); } fast-glob-3.2.12/src/tests/utils/platform.ts000066400000000000000000000015071430655000000207360ustar00rootroot00000000000000import * as os from 'os'; const NODE_PROCESS_VERSION_PARTS = process.versions.node.split('.'); const MAJOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[0], 10); const MINOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[1], 10); const SUPPORTED_MAJOR_VERSION = 10; const SUPPORTED_MINOR_VERSION = 10; const IS_MATCHED_BY_MAJOR = MAJOR_VERSION > SUPPORTED_MAJOR_VERSION; const IS_MATCHED_BY_MAJOR_AND_MINOR = MAJOR_VERSION === SUPPORTED_MAJOR_VERSION && MINOR_VERSION >= SUPPORTED_MINOR_VERSION; /** * IS `true` for Node.js 10.10 and greater. */ export const IS_SUPPORT_READDIR_WITH_FILE_TYPES = IS_MATCHED_BY_MAJOR || IS_MATCHED_BY_MAJOR_AND_MINOR; export function isWindows(): boolean { return os.platform() === 'win32'; } export function isSupportReaddirWithFileTypes(): boolean { return IS_SUPPORT_READDIR_WITH_FILE_TYPES; } fast-glob-3.2.12/src/tests/utils/task.ts000066400000000000000000000014111430655000000200460ustar00rootroot00000000000000import { Task } from '../../managers/tasks'; import { Pattern } from '../../types'; class TaskBuilder { private readonly _task: Task = { base: '', dynamic: true, patterns: [], positive: [], negative: [] }; public base(base: string): this { this._task.base = base; return this; } public static(): this { this._task.dynamic = false; return this; } public positive(pattern: Pattern): this { this._task.patterns.push(pattern); this._task.positive.push(pattern); return this; } public negative(pattern: Pattern): this { this._task.patterns.push(`!${pattern}`); this._task.negative.push(pattern); return this; } public build(): Task { return this._task; } } export function builder(): TaskBuilder { return new TaskBuilder(); } fast-glob-3.2.12/src/types/000077500000000000000000000000001430655000000154015ustar00rootroot00000000000000fast-glob-3.2.12/src/types/index.ts000066400000000000000000000020121430655000000170530ustar00rootroot00000000000000import * as fsWalk from '@nodelib/fs.walk'; export type ErrnoException = NodeJS.ErrnoException; export type Entry = fsWalk.Entry; export type EntryItem = string | Entry; export type Pattern = string; export type PatternRe = RegExp; export type PatternsGroup = Record; export type ReaderOptions = fsWalk.Options & { transform(entry: Entry): EntryItem; deepFilter: DeepFilterFunction; entryFilter: EntryFilterFunction; errorFilter: ErrorFilterFunction; fs: FileSystemAdapter; stats: boolean; }; export type ErrorFilterFunction = fsWalk.ErrorFilterFunction; export type EntryFilterFunction = fsWalk.EntryFilterFunction; export type DeepFilterFunction = fsWalk.DeepFilterFunction; export type EntryTransformerFunction = (entry: Entry) => EntryItem; export type MicromatchOptions = { dot?: boolean; matchBase?: boolean; nobrace?: boolean; nocase?: boolean; noext?: boolean; noglobstar?: boolean; posix?: boolean; strictSlashes?: boolean; }; export type FileSystemAdapter = fsWalk.FileSystemAdapter; fast-glob-3.2.12/src/utils/000077500000000000000000000000001430655000000153755ustar00rootroot00000000000000fast-glob-3.2.12/src/utils/array.spec.ts000066400000000000000000000016711430655000000200210ustar00rootroot00000000000000import * as assert from 'assert'; import * as util from './array'; describe('Utils → Array', () => { describe('.flatten', () => { it('should return non-nested array', () => { const expected = ['a', 'b']; const actual = util.flatten([['a'], ['b']]); assert.deepStrictEqual(actual, expected); }); }); describe('.splitWhen', () => { it('should return one group', () => { const expected = [[1, 2]]; const actual = util.splitWhen([1, 2], () => false); assert.deepStrictEqual(actual, expected); }); it('should return group for each item of array', () => { const expected = [[], [], [], []]; const actual = util.splitWhen([1, 2, 3], () => true); assert.deepStrictEqual(actual, expected); }); it('should return two group', () => { const expected = [[1, 2], [4, 5]]; const actual = util.splitWhen([1, 2, 3, 4, 5], (item) => item === 3); assert.deepStrictEqual(actual, expected); }); }); }); fast-glob-3.2.12/src/utils/array.ts000066400000000000000000000007011430655000000170610ustar00rootroot00000000000000export function flatten(items: T[][]): T[] { return items.reduce((collection, item) => ([] as T[]).concat(collection, item), [] as T[]); } export function splitWhen(items: T[], predicate: (item: T) => boolean): T[][] { const result: T[][] = [[]]; let groupIndex = 0; for (const item of items) { if (predicate(item)) { groupIndex++; result[groupIndex] = []; } else { result[groupIndex].push(item); } } return result; } fast-glob-3.2.12/src/utils/errno.spec.ts000066400000000000000000000006601430655000000200250ustar00rootroot00000000000000import * as assert from 'assert'; import * as tests from '../tests'; import * as util from './errno'; describe('Utils → Errno', () => { describe('.isEnoentCodeError', () => { it('should return true for ENOENT error', () => { assert.ok(util.isEnoentCodeError(tests.errno.getEnoent())); }); it('should return false for EPERM error', () => { assert.ok(!util.isEnoentCodeError(tests.errno.getEperm())); }); }); }); fast-glob-3.2.12/src/utils/errno.ts000066400000000000000000000002231430655000000170670ustar00rootroot00000000000000import { ErrnoException } from '../types'; export function isEnoentCodeError(error: ErrnoException): boolean { return error.code === 'ENOENT'; } fast-glob-3.2.12/src/utils/fs.spec.ts000066400000000000000000000011621430655000000173060ustar00rootroot00000000000000import * as assert from 'assert'; import { Stats } from '@nodelib/fs.macchiato'; import * as util from './fs'; describe('Utils → FS', () => { describe('.createDirentFromStats', () => { it('should convert fs.Stats to fs.Dirent', () => { const actual = util.createDirentFromStats('name', new Stats()); assert.strictEqual(actual.name, 'name'); assert.ok(!actual.isBlockDevice()); assert.ok(!actual.isCharacterDevice()); assert.ok(!actual.isDirectory()); assert.ok(!actual.isFIFO()); assert.ok(actual.isFile()); assert.ok(!actual.isSocket()); assert.ok(!actual.isSymbolicLink()); }); }); }); fast-glob-3.2.12/src/utils/fs.ts000066400000000000000000000017211430655000000163560ustar00rootroot00000000000000import * as fs from 'fs'; import { Dirent } from '@nodelib/fs.walk'; class DirentFromStats implements fs.Dirent { public isBlockDevice: fs.Stats['isBlockDevice']; public isCharacterDevice: fs.Stats['isCharacterDevice']; public isDirectory: fs.Stats['isDirectory']; public isFIFO: fs.Stats['isFIFO']; public isFile: fs.Stats['isFile']; public isSocket: fs.Stats['isSocket']; public isSymbolicLink: fs.Stats['isSymbolicLink']; constructor(public name: string, stats: fs.Stats) { this.isBlockDevice = stats.isBlockDevice.bind(stats); this.isCharacterDevice = stats.isCharacterDevice.bind(stats); this.isDirectory = stats.isDirectory.bind(stats); this.isFIFO = stats.isFIFO.bind(stats); this.isFile = stats.isFile.bind(stats); this.isSocket = stats.isSocket.bind(stats); this.isSymbolicLink = stats.isSymbolicLink.bind(stats); } } export function createDirentFromStats(name: string, stats: fs.Stats): Dirent { return new DirentFromStats(name, stats); } fast-glob-3.2.12/src/utils/index.ts000066400000000000000000000004621430655000000170560ustar00rootroot00000000000000import * as array from './array'; import * as errno from './errno'; import * as fs from './fs'; import * as path from './path'; import * as pattern from './pattern'; import * as stream from './stream'; import * as string from './string'; export { array, errno, fs, path, pattern, stream, string }; fast-glob-3.2.12/src/utils/path.spec.ts000066400000000000000000000054541430655000000176420ustar00rootroot00000000000000import * as assert from 'assert'; import * as path from 'path'; import * as util from './path'; describe('Utils → Path', () => { describe('.unixify', () => { it('should return path with converted slashes', () => { const expected = 'directory/nested/file.md'; const actual = util.unixify('directory\\nested/file.md'); assert.strictEqual(actual, expected); }); }); describe('.makeAbsolute', () => { it('should return absolute filepath', () => { const expected = path.join(process.cwd(), 'file.md'); const actual = util.makeAbsolute(process.cwd(), 'file.md'); assert.strictEqual(actual, expected); }); }); describe('.escapePattern', () => { it('should return pattern with escaped glob symbols', () => { assert.strictEqual(util.escape('!abc'), '\\!abc'); assert.strictEqual(util.escape('*'), '\\*'); assert.strictEqual(util.escape('?'), '\\?'); assert.strictEqual(util.escape('()'), '\\(\\)'); assert.strictEqual(util.escape('{}'), '\\{\\}'); assert.strictEqual(util.escape('[]'), '\\[\\]'); assert.strictEqual(util.escape('@('), '\\@\\('); assert.strictEqual(util.escape('!('), '\\!\\('); assert.strictEqual(util.escape('*('), '\\*\\('); assert.strictEqual(util.escape('?('), '\\?\\('); assert.strictEqual(util.escape('+('), '\\+\\('); }); it('should return pattern without additional escape characters', () => { assert.strictEqual(util.escape('\\!abc'), '\\!abc'); assert.strictEqual(util.escape('\\*'), '\\*'); assert.strictEqual(util.escape('\\!\\('), '\\!\\('); }); it('should return pattern without escape characters', () => { assert.strictEqual(util.escape('abc!'), 'abc!'); assert.strictEqual(util.escape('abc/!abc'), 'abc/!abc'); assert.strictEqual(util.escape('+abc'), '+abc'); assert.strictEqual(util.escape('abc+'), 'abc+'); assert.strictEqual(util.escape('@abc'), '@abc'); assert.strictEqual(util.escape('abc@'), 'abc@'); }); }); describe('.removeLeadingDotCharacters', () => { it('should return path without changes', () => { assert.strictEqual(util.removeLeadingDotSegment('../a/b'), '../a/b'); assert.strictEqual(util.removeLeadingDotSegment('~/a/b'), '~/a/b'); assert.strictEqual(util.removeLeadingDotSegment('/a/b'), '/a/b'); assert.strictEqual(util.removeLeadingDotSegment('a/b'), 'a/b'); assert.strictEqual(util.removeLeadingDotSegment('..\\a\\b'), '..\\a\\b'); assert.strictEqual(util.removeLeadingDotSegment('~\\a\\b'), '~\\a\\b'); assert.strictEqual(util.removeLeadingDotSegment('\\a\\b'), '\\a\\b'); assert.strictEqual(util.removeLeadingDotSegment('a\\b'), 'a\\b'); }); it('should return path without leading dit characters', () => { assert.strictEqual(util.removeLeadingDotSegment('./a/b'), 'a/b'); assert.strictEqual(util.removeLeadingDotSegment('.\\a\\b'), 'a\\b'); }); }); }); fast-glob-3.2.12/src/utils/path.ts000066400000000000000000000020421430655000000166770ustar00rootroot00000000000000import * as path from 'path'; import { Pattern } from '../types'; const LEADING_DOT_SEGMENT_CHARACTERS_COUNT = 2; // ./ or .\\ const UNESCAPED_GLOB_SYMBOLS_RE = /(\\?)([()*?[\]{|}]|^!|[!+@](?=\())/g; /** * Designed to work only with simple paths: `dir\\file`. */ export function unixify(filepath: string): string { return filepath.replace(/\\/g, '/'); } export function makeAbsolute(cwd: string, filepath: string): string { return path.resolve(cwd, filepath); } export function escape(pattern: Pattern): Pattern { return pattern.replace(UNESCAPED_GLOB_SYMBOLS_RE, '\\$2'); } export function removeLeadingDotSegment(entry: string): string { // We do not use `startsWith` because this is 10x slower than current implementation for some cases. // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with if (entry.charAt(0) === '.') { const secondCharactery = entry.charAt(1); if (secondCharactery === '/' || secondCharactery === '\\') { return entry.slice(LEADING_DOT_SEGMENT_CHARACTERS_COUNT); } } return entry; } fast-glob-3.2.12/src/utils/pattern.spec.ts000066400000000000000000000365131430655000000203630ustar00rootroot00000000000000import * as assert from 'assert'; import { Pattern } from '../types'; import * as util from './pattern'; describe('Utils → Pattern', () => { describe('.isStaticPattern', () => { it('should return true for static pattern', () => { const actual = util.isStaticPattern('dir'); assert.ok(actual); }); it('should return false for dynamic pattern', () => { const actual = util.isStaticPattern('*'); assert.ok(!actual); }); }); describe('.isDynamicPattern', () => { describe('Without options', () => { it('should return false for an empty string', () => { assert.ok(!util.isDynamicPattern('')); }); it('should return true for patterns that include the escape symbol', () => { assert.ok(util.isDynamicPattern('\\')); }); it('should return true for everything when the `caseSensitiveMatch` option is disabled', () => { assert.ok(util.isDynamicPattern('abc', { caseSensitiveMatch: false })); }); it('should return true for patterns that include common glob symbols', () => { assert.ok(util.isDynamicPattern('*')); assert.ok(util.isDynamicPattern('abc/*')); assert.ok(util.isDynamicPattern('?')); assert.ok(util.isDynamicPattern('abc/?')); assert.ok(util.isDynamicPattern('!abc')); }); it('should return true for patterns that include regex group symbols', () => { assert.ok(util.isDynamicPattern('(a|)')); assert.ok(util.isDynamicPattern('(a|b)')); assert.ok(util.isDynamicPattern('abc/(a|b)')); }); it('should return true for patterns that include regex character class symbols', () => { assert.ok(util.isDynamicPattern('[abc]')); assert.ok(util.isDynamicPattern('abc/[abc]')); assert.ok(util.isDynamicPattern('[^abc]')); assert.ok(util.isDynamicPattern('abc/[^abc]')); assert.ok(util.isDynamicPattern('[1-3]')); assert.ok(util.isDynamicPattern('abc/[1-3]')); assert.ok(util.isDynamicPattern('[[:alpha:][:digit:]]')); assert.ok(util.isDynamicPattern('abc/[[:alpha:][:digit:]]')); }); it('should return true for patterns that include glob extension symbols', () => { assert.ok(util.isDynamicPattern('@()')); assert.ok(util.isDynamicPattern('@(a)')); assert.ok(util.isDynamicPattern('@(a|b)')); assert.ok(util.isDynamicPattern('abc/!(a|b)')); assert.ok(util.isDynamicPattern('*(a|b)')); assert.ok(util.isDynamicPattern('?(a|b)')); assert.ok(util.isDynamicPattern('+(a|b)')); }); it('should return false for glob extension when the `extglob` option is disabled', () => { assert.ok(!util.isDynamicPattern('@()', { extglob: false })); assert.ok(!util.isDynamicPattern('@(a)', { extglob: false })); assert.ok(!util.isDynamicPattern('@(a|b)', { extglob: false })); assert.ok(!util.isDynamicPattern('abc/!(a|b)', { extglob: false })); assert.ok(util.isDynamicPattern('*(a|b)', { extglob: true })); assert.ok(util.isDynamicPattern('?(a|b)', { extglob: true })); assert.ok(!util.isDynamicPattern('+(a|b)', { extglob: false })); }); it('should return true for patterns that include brace expansions symbols', () => { assert.ok(util.isDynamicPattern('{,}')); assert.ok(util.isDynamicPattern('abc/{a.txt,}')); assert.ok(util.isDynamicPattern('{a,}')); assert.ok(util.isDynamicPattern('{,b}')); assert.ok(util.isDynamicPattern('{a,b}')); assert.ok(util.isDynamicPattern('{a,b,c}')); assert.ok(util.isDynamicPattern('{a' + ','.repeat(999999) + 'b}')); assert.ok(util.isDynamicPattern('{a,b,{c,d}}')); // The second braces pass assert.ok(util.isDynamicPattern('{a,b,{c,d}')); assert.ok(util.isDynamicPattern('{1..3}')); assert.ok(util.isDynamicPattern('abc/{1..3}')); assert.ok(util.isDynamicPattern('{2..10..2}')); }); it('should return false for brace extension when the `braceExpansion` option is disabled', () => { assert.ok(!util.isDynamicPattern('{,}', { braceExpansion: false })); assert.ok(!util.isDynamicPattern('{a,}', { braceExpansion: false })); assert.ok(!util.isDynamicPattern('{,b}', { braceExpansion: false })); assert.ok(!util.isDynamicPattern('{a,b}', { braceExpansion: false })); assert.ok(!util.isDynamicPattern('{1..3}', { braceExpansion: false })); }); it('should return false for "!" symbols when a symbol is not specified first in the string', () => { assert.ok(!util.isDynamicPattern('abc!')); }); it('should return false for a completely static pattern', () => { assert.ok(!util.isDynamicPattern('')); assert.ok(!util.isDynamicPattern('.')); assert.ok(!util.isDynamicPattern('abc')); assert.ok(!util.isDynamicPattern('~abc')); assert.ok(!util.isDynamicPattern('~/abc')); assert.ok(!util.isDynamicPattern('+~/abc')); assert.ok(!util.isDynamicPattern('@.(abc)')); assert.ok(!util.isDynamicPattern('(a b)')); assert.ok(!util.isDynamicPattern('(a b)')); assert.ok(!util.isDynamicPattern('[abc')); }); it('should return false for unfinished regex character class', () => { assert.ok(!util.isDynamicPattern('[')); assert.ok(!util.isDynamicPattern('['.repeat(999999))); assert.ok(!util.isDynamicPattern('[abc')); }); it('should return false for unfinished regex group', () => { assert.ok(!util.isDynamicPattern('(a|b')); assert.ok(!util.isDynamicPattern('('.repeat(999999) + 'a|b')); assert.ok(!util.isDynamicPattern('(a' + '|'.repeat(999999) + 'b')); assert.ok(!util.isDynamicPattern('abc/(a|b')); }); it('should return false for unfinished glob extension', () => { assert.ok(!util.isDynamicPattern('@(')); assert.ok(!util.isDynamicPattern('@' + '('.repeat(999999) + 'a')); assert.ok(!util.isDynamicPattern('@(a')); assert.ok(!util.isDynamicPattern('@(a|')); assert.ok(!util.isDynamicPattern('@(a|b')); }); it('should return false for unfinished brace expansions', () => { assert.ok(!util.isDynamicPattern('{')); assert.ok(!util.isDynamicPattern('{'.repeat(999999))); assert.ok(!util.isDynamicPattern('{a')); assert.ok(!util.isDynamicPattern('{a}')); assert.ok(!util.isDynamicPattern('{,')); assert.ok(!util.isDynamicPattern('{a,')); assert.ok(!util.isDynamicPattern('{a,b')); assert.ok(!util.isDynamicPattern('{a' + ','.repeat(999999) + 'b')); assert.ok(!util.isDynamicPattern('{1..')); assert.ok(!util.isDynamicPattern('{1.' + '.'.repeat(999999) + '2')); assert.ok(!util.isDynamicPattern('{2..10')); assert.ok(!util.isDynamicPattern('{2..10.')); assert.ok(!util.isDynamicPattern('{2..10..')); assert.ok(!util.isDynamicPattern('{2..10..2')); }); }); describe('With options', () => { it('should return true for patterns that include "*?" symbols even when the "extglob" option is disabled', () => { assert.ok(util.isDynamicPattern('*(a|b)', { extglob: false })); assert.ok(util.isDynamicPattern('?(a|b)', { extglob: false })); }); it('should return true when the "caseSensitiveMatch" option is enabled', () => { assert.ok(util.isDynamicPattern('a', { caseSensitiveMatch: false })); }); it('should return false for glob extension when the "extglob" option is disabled', () => { assert.ok(!util.isDynamicPattern('@(a|b)', { extglob: false })); assert.ok(!util.isDynamicPattern('abc/!(a|b)', { extglob: false })); assert.ok(!util.isDynamicPattern('+(a|b)', { extglob: false })); }); it('should return false for brace expansions when the "braceExpansion" option is disabled', () => { assert.ok(!util.isDynamicPattern('{a,b}', { braceExpansion: false })); assert.ok(!util.isDynamicPattern('{1..3}', { braceExpansion: false })); }); }); }); describe('.convertToPositivePattern', () => { it('should returns converted positive pattern', () => { const expected = '*.js'; const actual = util.convertToPositivePattern('!*.js'); assert.strictEqual(actual, expected); }); it('should returns pattern without changes', () => { const expected = '*.js'; const actual = util.convertToPositivePattern('*.js'); assert.strictEqual(actual, expected); }); }); describe('.convertToNegativePattern', () => { it('should returns converted negative pattern', () => { const expected = '!*.js'; const actual = util.convertToNegativePattern('*.js'); assert.strictEqual(actual, expected); }); }); describe('.isNegativePattern', () => { it('should returns true', () => { const actual = util.isNegativePattern('!*.md'); assert.ok(actual); }); it('should returns false', () => { const actual = util.isNegativePattern('*.md'); assert.ok(!actual); }); it('should returns false for extglob', () => { const actual = util.isNegativePattern('!(a|b|c)'); assert.ok(!actual); }); }); describe('.isPositivePattern', () => { it('should returns true', () => { const actual = util.isPositivePattern('*.md'); assert.ok(actual); }); it('should returns false', () => { const actual = util.isPositivePattern('!*.md'); assert.ok(!actual); }); }); describe('.getNegativePatterns', () => { it('should returns only negative patterns', () => { const expected = ['!*.spec.js']; const actual = util.getNegativePatterns(['*.js', '!*.spec.js', '*.ts']); assert.deepStrictEqual(actual, expected); }); it('should returns empty array', () => { const expected: Pattern[] = []; const actual = util.getNegativePatterns(['*.js', '*.ts']); assert.deepStrictEqual(actual, expected); }); }); describe('.getPositivePatterns', () => { it('should returns only positive patterns', () => { const expected = ['*.js', '*.ts']; const actual = util.getPositivePatterns(['*.js', '!*.spec.js', '*.ts']); assert.deepStrictEqual(actual, expected); }); it('should returns empty array', () => { const expected: Pattern[] = []; const actual = util.getPositivePatterns(['!*.js', '!*.ts']); assert.deepStrictEqual(actual, expected); }); }); describe('.getPatternsInsideCurrentDirectory', () => { it('should return patterns', () => { const expected: Pattern[] = ['.', './*', '*', 'a/*']; const actual = util.getPatternsInsideCurrentDirectory(['.', './*', '*', 'a/*', '..', '../*', './..', './../*']); assert.deepStrictEqual(actual, expected); }); }); describe('.getPatternsOutsideCurrentDirectory', () => { it('should return patterns', () => { const expected: Pattern[] = ['..', '../*', './..', './../*']; const actual = util.getPatternsOutsideCurrentDirectory(['.', './*', '*', 'a/*', '..', '../*', './..', './../*']); assert.deepStrictEqual(actual, expected); }); }); describe('.isPatternRelatedToParentDirectory', () => { it('should be `false` when the pattern refers to the current directory', () => { const actual = util.isPatternRelatedToParentDirectory('.'); assert.ok(!actual); }); it('should be `true` when the pattern equals to `..`', () => { const actual = util.isPatternRelatedToParentDirectory('..'); assert.ok(actual); }); it('should be `true` when the pattern starts with `..` segment', () => { const actual = util.isPatternRelatedToParentDirectory('../*'); assert.ok(actual); }); it('should be `true` when the pattern starts with `./..` segment', () => { const actual = util.isPatternRelatedToParentDirectory('./../*'); assert.ok(actual); }); }); describe('.getBaseDirectory', () => { it('should returns base directory', () => { const expected = 'root'; const actual = util.getBaseDirectory('root/*.js'); assert.strictEqual(actual, expected); }); it('should returns base directory without slash transformation', () => { const expected = '.'; const actual = util.getBaseDirectory('file-\\(suffix\\).md'); assert.strictEqual(actual, expected); }); }); describe('.hasGlobStar', () => { it('should returns true for pattern that includes globstar', () => { const actual = util.hasGlobStar('**/*.js'); assert.ok(actual); }); it('should returns false for pattern that has no globstar', () => { const actual = util.hasGlobStar('*.js'); assert.ok(!actual); }); }); describe('.endsWithSlashGlobStar', () => { it('should returns true for pattern that ends with slash and globstar', () => { const actual = util.endsWithSlashGlobStar('name/**'); assert.ok(actual); }); it('should returns false for pattern that has no slash, but ends with globstar', () => { const actual = util.endsWithSlashGlobStar('**'); assert.ok(!actual); }); it('should returns false for pattern that does not ends with globstar', () => { const actual = util.endsWithSlashGlobStar('name/**/*'); assert.ok(!actual); }); }); describe('.isAffectDepthOfReadingPattern', () => { it('should return true for pattern that ends with slash and globstar', () => { const actual = util.isAffectDepthOfReadingPattern('name/**'); assert.ok(actual); }); it('should return true for pattern when the last partial of the pattern is static pattern', () => { const actual = util.isAffectDepthOfReadingPattern('**/name'); assert.ok(actual); }); it('should return false', () => { const actual = util.isAffectDepthOfReadingPattern('**/name/*'); assert.ok(!actual); }); }); describe('.expandPatternsWithBraceExpansion', () => { it('should return an array of expanded patterns with brace expansion', () => { const expected = ['a/b/d', 'a/c/d', 'a/*', 'a/b/c']; const actual = util.expandPatternsWithBraceExpansion(['a/{b,c}/d', 'a/{*,b/c}']); assert.deepStrictEqual(actual, expected); }); }); describe('.expandBraceExpansion', () => { it('should return an array of expanded patterns with brace expansion without dupes', () => { const expected = ['a/b', 'a/c/d', 'a/c']; const actual = util.expandBraceExpansion('a/{b,c/d,{b,c}}'); assert.deepStrictEqual(actual, expected); }); }); describe('.getPatternParts', () => { it('should return an array with a single item when the pattern is an empty string', () => { const expected: Pattern[] = ['']; const actual = util.getPatternParts('', {}); assert.deepStrictEqual(actual, expected); }); it('should return an array with a single item (micromatch/picomatch#58)', () => { const expected: Pattern[] = ['a*']; const actual = util.getPatternParts('a*', {}); assert.deepStrictEqual(actual, expected); }); it('should return the correct set of parts for the pattern with a forward slash (micromatch/picomatch#58)', () => { const expected: Pattern[] = ['', 'lib', '*']; const actual = util.getPatternParts('/lib/*', {}); assert.deepStrictEqual(actual, expected); }); it('should return an array of pattern parts', () => { const expected: Pattern[] = ['a', '*', 'b', '**', 'c']; const actual = util.getPatternParts('a/*/b/**/c', {}); assert.deepStrictEqual(actual, expected); }); }); describe('.makeRe', () => { it('should return regexp for provided pattern', () => { const actual = util.makeRe('*.js', {}); assert.ok(actual instanceof RegExp); }); }); describe('.convertPatternsToRe', () => { it('should return regexps for provided patterns', () => { const [actual] = util.convertPatternsToRe(['*.js'], {}); assert.ok(actual instanceof RegExp); }); }); describe('.matchAny', () => { it('should return true', () => { const actual = util.matchAny('fixtures/nested/file.txt', [/fixture/, /fixtures\/nested\/file/]); assert.ok(actual); }); it('should return false', () => { const actual = util.matchAny('fixtures/directory', [/fixtures\/file/]); assert.ok(!actual); }); }); }); fast-glob-3.2.12/src/utils/pattern.ts000066400000000000000000000130371430655000000174260ustar00rootroot00000000000000import * as path from 'path'; import * as globParent from 'glob-parent'; import * as micromatch from 'micromatch'; import { MicromatchOptions, Pattern, PatternRe } from '../types'; const GLOBSTAR = '**'; const ESCAPE_SYMBOL = '\\'; const COMMON_GLOB_SYMBOLS_RE = /[*?]|^!/; const REGEX_CHARACTER_CLASS_SYMBOLS_RE = /\[[^[]*]/; const REGEX_GROUP_SYMBOLS_RE = /(?:^|[^!*+?@])\([^(]*\|[^|]*\)/; const GLOB_EXTENSION_SYMBOLS_RE = /[!*+?@]\([^(]*\)/; const BRACE_EXPANSION_SEPARATORS_RE = /,|\.\./; type PatternTypeOptions = { braceExpansion?: boolean; caseSensitiveMatch?: boolean; extglob?: boolean; }; export function isStaticPattern(pattern: Pattern, options: PatternTypeOptions = {}): boolean { return !isDynamicPattern(pattern, options); } export function isDynamicPattern(pattern: Pattern, options: PatternTypeOptions = {}): boolean { /** * A special case with an empty string is necessary for matching patterns that start with a forward slash. * An empty string cannot be a dynamic pattern. * For example, the pattern `/lib/*` will be spread into parts: '', 'lib', '*'. */ if (pattern === '') { return false; } /** * When the `caseSensitiveMatch` option is disabled, all patterns must be marked as dynamic, because we cannot check * filepath directly (without read directory). */ if (options.caseSensitiveMatch === false || pattern.includes(ESCAPE_SYMBOL)) { return true; } if (COMMON_GLOB_SYMBOLS_RE.test(pattern) || REGEX_CHARACTER_CLASS_SYMBOLS_RE.test(pattern) || REGEX_GROUP_SYMBOLS_RE.test(pattern)) { return true; } if (options.extglob !== false && GLOB_EXTENSION_SYMBOLS_RE.test(pattern)) { return true; } if (options.braceExpansion !== false && hasBraceExpansion(pattern)) { return true; } return false; } function hasBraceExpansion(pattern: string): boolean { const openingBraceIndex = pattern.indexOf('{'); if (openingBraceIndex === -1) { return false; } const closingBraceIndex = pattern.indexOf('}', openingBraceIndex + 1); if (closingBraceIndex === -1) { return false; } const braceContent = pattern.slice(openingBraceIndex, closingBraceIndex); return BRACE_EXPANSION_SEPARATORS_RE.test(braceContent); } export function convertToPositivePattern(pattern: Pattern): Pattern { return isNegativePattern(pattern) ? pattern.slice(1) : pattern; } export function convertToNegativePattern(pattern: Pattern): Pattern { return '!' + pattern; } export function isNegativePattern(pattern: Pattern): boolean { return pattern.startsWith('!') && pattern[1] !== '('; } export function isPositivePattern(pattern: Pattern): boolean { return !isNegativePattern(pattern); } export function getNegativePatterns(patterns: Pattern[]): Pattern[] { return patterns.filter(isNegativePattern); } export function getPositivePatterns(patterns: Pattern[]): Pattern[] { return patterns.filter(isPositivePattern); } /** * Returns patterns that can be applied inside the current directory. * * @example * // ['./*', '*', 'a/*'] * getPatternsInsideCurrentDirectory(['./*', '*', 'a/*', '../*', './../*']) */ export function getPatternsInsideCurrentDirectory(patterns: Pattern[]): Pattern[] { return patterns.filter((pattern) => !isPatternRelatedToParentDirectory(pattern)); } /** * Returns patterns to be expanded relative to (outside) the current directory. * * @example * // ['../*', './../*'] * getPatternsInsideCurrentDirectory(['./*', '*', 'a/*', '../*', './../*']) */ export function getPatternsOutsideCurrentDirectory(patterns: Pattern[]): Pattern[] { return patterns.filter(isPatternRelatedToParentDirectory); } export function isPatternRelatedToParentDirectory(pattern: Pattern): boolean { return pattern.startsWith('..') || pattern.startsWith('./..'); } export function getBaseDirectory(pattern: Pattern): string { return globParent(pattern, { flipBackslashes: false }); } export function hasGlobStar(pattern: Pattern): boolean { return pattern.includes(GLOBSTAR); } export function endsWithSlashGlobStar(pattern: Pattern): boolean { return pattern.endsWith('/' + GLOBSTAR); } export function isAffectDepthOfReadingPattern(pattern: Pattern): boolean { const basename = path.basename(pattern); return endsWithSlashGlobStar(pattern) || isStaticPattern(basename); } export function expandPatternsWithBraceExpansion(patterns: Pattern[]): Pattern[] { return patterns.reduce((collection, pattern) => { return collection.concat(expandBraceExpansion(pattern)); }, [] as Pattern[]); } export function expandBraceExpansion(pattern: Pattern): Pattern[] { return micromatch.braces(pattern, { expand: true, nodupes: true }); } export function getPatternParts(pattern: Pattern, options: MicromatchOptions): Pattern[] { let { parts } = micromatch.scan(pattern, { ...options, parts: true }); /** * The scan method returns an empty array in some cases. * See micromatch/picomatch#58 for more details. */ if (parts.length === 0) { parts = [pattern]; } /** * The scan method does not return an empty part for the pattern with a forward slash. * This is another part of micromatch/picomatch#58. */ if (parts[0].startsWith('/')) { parts[0] = parts[0].slice(1); parts.unshift(''); } return parts; } export function makeRe(pattern: Pattern, options: MicromatchOptions): PatternRe { return micromatch.makeRe(pattern, options); } export function convertPatternsToRe(patterns: Pattern[], options: MicromatchOptions): PatternRe[] { return patterns.map((pattern) => makeRe(pattern, options)); } export function matchAny(entry: string, patternsRe: PatternRe[]): boolean { return patternsRe.some((patternRe) => patternRe.test(entry)); } fast-glob-3.2.12/src/utils/stream.spec.ts000066400000000000000000000030221430655000000201660ustar00rootroot00000000000000import * as assert from 'assert'; import * as stream from 'stream'; import * as util from './stream'; describe('Utils → Stream', () => { describe('.merge', () => { it('should merge two streams into one stream', () => { const first = new stream.PassThrough(); const second = new stream.PassThrough(); const expected = 3; const mergedStream = util.merge([first, second]); const actual = mergedStream.listenerCount('close'); assert.strictEqual(actual, expected); }); it('should propagate errors into merged stream', (done) => { const first = new stream.PassThrough(); const second = new stream.PassThrough(); const expected = [1, 2, 3]; const mergedStream = util.merge([first, second]); const actual: number[] = []; mergedStream.on('error', (error: number) => actual.push(error)); mergedStream.once('finish', () => { assert.deepStrictEqual(actual, expected); done(); }); first.emit('error', 1); second.emit('error', 2); mergedStream.emit('error', 3); }); it('should propagate close event to source streams', (done) => { const first = new stream.PassThrough(); const second = new stream.PassThrough(); const mergedStream = util.merge([first, second]); const expected = [1, 2]; const actual: number[] = []; first.once('close', () => actual.push(1)); second.once('close', () => actual.push(2)); mergedStream.once('finish', () => { assert.deepStrictEqual(actual, expected); done(); }); mergedStream.emit('close'); }); }); }); fast-glob-3.2.12/src/utils/stream.ts000066400000000000000000000011031430655000000172330ustar00rootroot00000000000000import { Readable } from 'stream'; import * as merge2 from 'merge2'; export function merge(streams: Readable[]): NodeJS.ReadableStream { const mergedStream = merge2(streams); streams.forEach((stream) => { stream.once('error', (error) => mergedStream.emit('error', error)); }); mergedStream.once('close', () => propagateCloseEventToSources(streams)); mergedStream.once('end', () => propagateCloseEventToSources(streams)); return mergedStream; } function propagateCloseEventToSources(streams: Readable[]): void { streams.forEach((stream) => stream.emit('close')); } fast-glob-3.2.12/src/utils/string.spec.ts000066400000000000000000000011611430655000000202030ustar00rootroot00000000000000import * as assert from 'assert'; import * as util from './string'; describe('Utils → String', () => { describe('.isString', () => { it('should return true', () => { const actual = util.isString(''); assert.ok(actual); }); it('should return false', () => { const actual = util.isString(undefined as unknown as string); assert.ok(!actual); }); }); describe('.isEmpty', () => { it('should return true', () => { const actual = util.isEmpty(''); assert.ok(actual); }); it('should return false', () => { const actual = util.isEmpty('string'); assert.ok(!actual); }); }); }); fast-glob-3.2.12/src/utils/string.ts000066400000000000000000000002541430655000000172540ustar00rootroot00000000000000export function isString(input: unknown): input is string { return typeof input === 'string'; } export function isEmpty(input: string): boolean { return input === ''; } fast-glob-3.2.12/tsconfig.json000066400000000000000000000012611430655000000161550ustar00rootroot00000000000000{ "compilerOptions": { "target": "es2017", "module": "commonjs", "moduleResolution": "node", "rootDir": "src", "outDir": "out", "strict": true, "alwaysStrict": true, "strictFunctionTypes": true, "strictNullChecks": true, "strictPropertyInitialization": true, "forceConsistentCasingInFileNames": true, "noImplicitAny": true, "noImplicitReturns": true, "noImplicitThis": true, "noFallthroughCasesInSwitch": true, "noUnusedLocals": true, "noUnusedParameters": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "downlevelIteration": true, "declaration": true, "pretty": true }, "include": [ "typings", "src/**/*" ] }