Spaces:
Sleeping
Sleeping
| /** | |
| * @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> | |
| */ | |
| import assert from "assert"; | |
| import path from "path"; | |
| import util from "util"; | |
| import minimatch from "minimatch"; | |
| const { Minimatch } = 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(); | |
| } | |
| } | |
| export { OverrideTester }; | |