| /** | 
|  * @fileoverview `OverrideTester` class. | 
|  * | 
|  * `OverrideTester` class handles `files` property and `excludedFiles` property | 
|  * of `overrides` config. | 
|  * | 
|  * It provides one method. | 
|  * | 
|  * - `test(filePath)` | 
|  *      Test if a file path matches the pair of `files` property and | 
|  *      `excludedFiles` property. The `filePath` argument must be an absolute | 
|  *      path. | 
|  * | 
|  * `ConfigArrayFactory` creates `OverrideTester` objects when it processes | 
|  * `overrides` properties. | 
|  * | 
|  * @author Toru Nagashima <https://github.com/mysticatea> | 
|  */ | 
| "use strict"; | 
|   | 
| const assert = require("assert"); | 
| const path = require("path"); | 
| const util = require("util"); | 
| const { Minimatch } = require("minimatch"); | 
| const minimatchOpts = { dot: true, matchBase: true }; | 
|   | 
| /** | 
|  * @typedef {Object} Pattern | 
|  * @property {InstanceType<Minimatch>[] | null} includes The positive matchers. | 
|  * @property {InstanceType<Minimatch>[] | null} excludes The negative matchers. | 
|  */ | 
|   | 
| /** | 
|  * Normalize a given pattern to an array. | 
|  * @param {string|string[]|undefined} patterns A glob pattern or an array of glob patterns. | 
|  * @returns {string[]|null} Normalized patterns. | 
|  * @private | 
|  */ | 
| function normalizePatterns(patterns) { | 
|     if (Array.isArray(patterns)) { | 
|         return patterns.filter(Boolean); | 
|     } | 
|     if (typeof patterns === "string" && patterns) { | 
|         return [patterns]; | 
|     } | 
|     return []; | 
| } | 
|   | 
| /** | 
|  * Create the matchers of given patterns. | 
|  * @param {string[]} patterns The patterns. | 
|  * @returns {InstanceType<Minimatch>[] | null} The matchers. | 
|  */ | 
| function toMatcher(patterns) { | 
|     if (patterns.length === 0) { | 
|         return null; | 
|     } | 
|     return patterns.map(pattern => { | 
|         if (/^\.[/\\]/u.test(pattern)) { | 
|             return new Minimatch( | 
|                 pattern.slice(2), | 
|   | 
|                 // `./*.js` should not match with `subdir/foo.js` | 
|                 { ...minimatchOpts, matchBase: false } | 
|             ); | 
|         } | 
|         return new Minimatch(pattern, minimatchOpts); | 
|     }); | 
| } | 
|   | 
| /** | 
|  * Convert a given matcher to string. | 
|  * @param {Pattern} matchers The matchers. | 
|  * @returns {string} The string expression of the matcher. | 
|  */ | 
| function patternToJson({ includes, excludes }) { | 
|     return { | 
|         includes: includes && includes.map(m => m.pattern), | 
|         excludes: excludes && excludes.map(m => m.pattern) | 
|     }; | 
| } | 
|   | 
| /** | 
|  * The class to test given paths are matched by the patterns. | 
|  */ | 
| class OverrideTester { | 
|   | 
|     /** | 
|      * Create a tester with given criteria. | 
|      * If there are no criteria, returns `null`. | 
|      * @param {string|string[]} files The glob patterns for included files. | 
|      * @param {string|string[]} excludedFiles The glob patterns for excluded files. | 
|      * @param {string} basePath The path to the base directory to test paths. | 
|      * @returns {OverrideTester|null} The created instance or `null`. | 
|      */ | 
|     static create(files, excludedFiles, basePath) { | 
|         const includePatterns = normalizePatterns(files); | 
|         const excludePatterns = normalizePatterns(excludedFiles); | 
|         let endsWithWildcard = false; | 
|   | 
|         if (includePatterns.length === 0) { | 
|             return null; | 
|         } | 
|   | 
|         // Rejects absolute paths or relative paths to parents. | 
|         for (const pattern of includePatterns) { | 
|             if (path.isAbsolute(pattern) || pattern.includes("..")) { | 
|                 throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`); | 
|             } | 
|             if (pattern.endsWith("*")) { | 
|                 endsWithWildcard = true; | 
|             } | 
|         } | 
|         for (const pattern of excludePatterns) { | 
|             if (path.isAbsolute(pattern) || pattern.includes("..")) { | 
|                 throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`); | 
|             } | 
|         } | 
|   | 
|         const includes = toMatcher(includePatterns); | 
|         const excludes = toMatcher(excludePatterns); | 
|   | 
|         return new OverrideTester( | 
|             [{ includes, excludes }], | 
|             basePath, | 
|             endsWithWildcard | 
|         ); | 
|     } | 
|   | 
|     /** | 
|      * Combine two testers by logical and. | 
|      * If either of the testers was `null`, returns the other tester. | 
|      * The `basePath` property of the two must be the same value. | 
|      * @param {OverrideTester|null} a A tester. | 
|      * @param {OverrideTester|null} b Another tester. | 
|      * @returns {OverrideTester|null} Combined tester. | 
|      */ | 
|     static and(a, b) { | 
|         if (!b) { | 
|             return a && new OverrideTester( | 
|                 a.patterns, | 
|                 a.basePath, | 
|                 a.endsWithWildcard | 
|             ); | 
|         } | 
|         if (!a) { | 
|             return new OverrideTester( | 
|                 b.patterns, | 
|                 b.basePath, | 
|                 b.endsWithWildcard | 
|             ); | 
|         } | 
|   | 
|         assert.strictEqual(a.basePath, b.basePath); | 
|         return new OverrideTester( | 
|             a.patterns.concat(b.patterns), | 
|             a.basePath, | 
|             a.endsWithWildcard || b.endsWithWildcard | 
|         ); | 
|     } | 
|   | 
|     /** | 
|      * Initialize this instance. | 
|      * @param {Pattern[]} patterns The matchers. | 
|      * @param {string} basePath The base path. | 
|      * @param {boolean} endsWithWildcard If `true` then a pattern ends with `*`. | 
|      */ | 
|     constructor(patterns, basePath, endsWithWildcard = false) { | 
|   | 
|         /** @type {Pattern[]} */ | 
|         this.patterns = patterns; | 
|   | 
|         /** @type {string} */ | 
|         this.basePath = basePath; | 
|   | 
|         /** @type {boolean} */ | 
|         this.endsWithWildcard = endsWithWildcard; | 
|     } | 
|   | 
|     /** | 
|      * Test if a given path is matched or not. | 
|      * @param {string} filePath The absolute path to the target file. | 
|      * @returns {boolean} `true` if the path was matched. | 
|      */ | 
|     test(filePath) { | 
|         if (typeof filePath !== "string" || !path.isAbsolute(filePath)) { | 
|             throw new Error(`'filePath' should be an absolute path, but got ${filePath}.`); | 
|         } | 
|         const relativePath = path.relative(this.basePath, filePath); | 
|   | 
|         return this.patterns.every(({ includes, excludes }) => ( | 
|             (!includes || includes.some(m => m.match(relativePath))) && | 
|             (!excludes || !excludes.some(m => m.match(relativePath))) | 
|         )); | 
|     } | 
|   | 
|     // eslint-disable-next-line jsdoc/require-description | 
|     /** | 
|      * @returns {Object} a JSON compatible object. | 
|      */ | 
|     toJSON() { | 
|         if (this.patterns.length === 1) { | 
|             return { | 
|                 ...patternToJson(this.patterns[0]), | 
|                 basePath: this.basePath | 
|             }; | 
|         } | 
|         return { | 
|             AND: this.patterns.map(patternToJson), | 
|             basePath: this.basePath | 
|         }; | 
|     } | 
|   | 
|     // eslint-disable-next-line jsdoc/require-description | 
|     /** | 
|      * @returns {Object} an object to display by `console.log()`. | 
|      */ | 
|     [util.inspect.custom]() { | 
|         return this.toJSON(); | 
|     } | 
| } | 
|   | 
| module.exports = { OverrideTester }; |