123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- /**
- * @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 };
|