.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / @eslint / eslintrc / lib / config-array / override-tester.js
1 /**
2  * @fileoverview `OverrideTester` class.
3  *
4  * `OverrideTester` class handles `files` property and `excludedFiles` property
5  * of `overrides` config.
6  *
7  * It provides one method.
8  *
9  * - `test(filePath)`
10  *      Test if a file path matches the pair of `files` property and
11  *      `excludedFiles` property. The `filePath` argument must be an absolute
12  *      path.
13  *
14  * `ConfigArrayFactory` creates `OverrideTester` objects when it processes
15  * `overrides` properties.
16  *
17  * @author Toru Nagashima <https://github.com/mysticatea>
18  */
19 "use strict";
20
21 const assert = require("assert");
22 const path = require("path");
23 const util = require("util");
24 const { Minimatch } = require("minimatch");
25 const minimatchOpts = { dot: true, matchBase: true };
26
27 /**
28  * @typedef {Object} Pattern
29  * @property {InstanceType<Minimatch>[] | null} includes The positive matchers.
30  * @property {InstanceType<Minimatch>[] | null} excludes The negative matchers.
31  */
32
33 /**
34  * Normalize a given pattern to an array.
35  * @param {string|string[]|undefined} patterns A glob pattern or an array of glob patterns.
36  * @returns {string[]|null} Normalized patterns.
37  * @private
38  */
39 function normalizePatterns(patterns) {
40     if (Array.isArray(patterns)) {
41         return patterns.filter(Boolean);
42     }
43     if (typeof patterns === "string" && patterns) {
44         return [patterns];
45     }
46     return [];
47 }
48
49 /**
50  * Create the matchers of given patterns.
51  * @param {string[]} patterns The patterns.
52  * @returns {InstanceType<Minimatch>[] | null} The matchers.
53  */
54 function toMatcher(patterns) {
55     if (patterns.length === 0) {
56         return null;
57     }
58     return patterns.map(pattern => {
59         if (/^\.[/\\]/u.test(pattern)) {
60             return new Minimatch(
61                 pattern.slice(2),
62
63                 // `./*.js` should not match with `subdir/foo.js`
64                 { ...minimatchOpts, matchBase: false }
65             );
66         }
67         return new Minimatch(pattern, minimatchOpts);
68     });
69 }
70
71 /**
72  * Convert a given matcher to string.
73  * @param {Pattern} matchers The matchers.
74  * @returns {string} The string expression of the matcher.
75  */
76 function patternToJson({ includes, excludes }) {
77     return {
78         includes: includes && includes.map(m => m.pattern),
79         excludes: excludes && excludes.map(m => m.pattern)
80     };
81 }
82
83 /**
84  * The class to test given paths are matched by the patterns.
85  */
86 class OverrideTester {
87
88     /**
89      * Create a tester with given criteria.
90      * If there are no criteria, returns `null`.
91      * @param {string|string[]} files The glob patterns for included files.
92      * @param {string|string[]} excludedFiles The glob patterns for excluded files.
93      * @param {string} basePath The path to the base directory to test paths.
94      * @returns {OverrideTester|null} The created instance or `null`.
95      */
96     static create(files, excludedFiles, basePath) {
97         const includePatterns = normalizePatterns(files);
98         const excludePatterns = normalizePatterns(excludedFiles);
99         let endsWithWildcard = false;
100
101         if (includePatterns.length === 0) {
102             return null;
103         }
104
105         // Rejects absolute paths or relative paths to parents.
106         for (const pattern of includePatterns) {
107             if (path.isAbsolute(pattern) || pattern.includes("..")) {
108                 throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`);
109             }
110             if (pattern.endsWith("*")) {
111                 endsWithWildcard = true;
112             }
113         }
114         for (const pattern of excludePatterns) {
115             if (path.isAbsolute(pattern) || pattern.includes("..")) {
116                 throw new Error(`Invalid override pattern (expected relative path not containing '..'): ${pattern}`);
117             }
118         }
119
120         const includes = toMatcher(includePatterns);
121         const excludes = toMatcher(excludePatterns);
122
123         return new OverrideTester(
124             [{ includes, excludes }],
125             basePath,
126             endsWithWildcard
127         );
128     }
129
130     /**
131      * Combine two testers by logical and.
132      * If either of the testers was `null`, returns the other tester.
133      * The `basePath` property of the two must be the same value.
134      * @param {OverrideTester|null} a A tester.
135      * @param {OverrideTester|null} b Another tester.
136      * @returns {OverrideTester|null} Combined tester.
137      */
138     static and(a, b) {
139         if (!b) {
140             return a && new OverrideTester(
141                 a.patterns,
142                 a.basePath,
143                 a.endsWithWildcard
144             );
145         }
146         if (!a) {
147             return new OverrideTester(
148                 b.patterns,
149                 b.basePath,
150                 b.endsWithWildcard
151             );
152         }
153
154         assert.strictEqual(a.basePath, b.basePath);
155         return new OverrideTester(
156             a.patterns.concat(b.patterns),
157             a.basePath,
158             a.endsWithWildcard || b.endsWithWildcard
159         );
160     }
161
162     /**
163      * Initialize this instance.
164      * @param {Pattern[]} patterns The matchers.
165      * @param {string} basePath The base path.
166      * @param {boolean} endsWithWildcard If `true` then a pattern ends with `*`.
167      */
168     constructor(patterns, basePath, endsWithWildcard = false) {
169
170         /** @type {Pattern[]} */
171         this.patterns = patterns;
172
173         /** @type {string} */
174         this.basePath = basePath;
175
176         /** @type {boolean} */
177         this.endsWithWildcard = endsWithWildcard;
178     }
179
180     /**
181      * Test if a given path is matched or not.
182      * @param {string} filePath The absolute path to the target file.
183      * @returns {boolean} `true` if the path was matched.
184      */
185     test(filePath) {
186         if (typeof filePath !== "string" || !path.isAbsolute(filePath)) {
187             throw new Error(`'filePath' should be an absolute path, but got ${filePath}.`);
188         }
189         const relativePath = path.relative(this.basePath, filePath);
190
191         return this.patterns.every(({ includes, excludes }) => (
192             (!includes || includes.some(m => m.match(relativePath))) &&
193             (!excludes || !excludes.some(m => m.match(relativePath)))
194         ));
195     }
196
197     // eslint-disable-next-line jsdoc/require-description
198     /**
199      * @returns {Object} a JSON compatible object.
200      */
201     toJSON() {
202         if (this.patterns.length === 1) {
203             return {
204                 ...patternToJson(this.patterns[0]),
205                 basePath: this.basePath
206             };
207         }
208         return {
209             AND: this.patterns.map(patternToJson),
210             basePath: this.basePath
211         };
212     }
213
214     // eslint-disable-next-line jsdoc/require-description
215     /**
216      * @returns {Object} an object to display by `console.log()`.
217      */
218     [util.inspect.custom]() {
219         return this.toJSON();
220     }
221 }
222
223 module.exports = { OverrideTester };