index.cjs 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539
  1. 'use strict';
  2. var posixPath = require('./std__path/posix.cjs');
  3. var windowsPath = require('./std__path/windows.cjs');
  4. var minimatch = require('minimatch');
  5. var createDebug = require('debug');
  6. var objectSchema = require('@eslint/object-schema');
  7. function _interopNamespaceDefault(e) {
  8. var n = Object.create(null);
  9. if (e) {
  10. Object.keys(e).forEach(function (k) {
  11. if (k !== 'default') {
  12. var d = Object.getOwnPropertyDescriptor(e, k);
  13. Object.defineProperty(n, k, d.get ? d : {
  14. enumerable: true,
  15. get: function () { return e[k]; }
  16. });
  17. }
  18. });
  19. }
  20. n.default = e;
  21. return Object.freeze(n);
  22. }
  23. var posixPath__namespace = /*#__PURE__*/_interopNamespaceDefault(posixPath);
  24. var windowsPath__namespace = /*#__PURE__*/_interopNamespaceDefault(windowsPath);
  25. /**
  26. * @fileoverview ConfigSchema
  27. * @author Nicholas C. Zakas
  28. */
  29. //------------------------------------------------------------------------------
  30. // Types
  31. //------------------------------------------------------------------------------
  32. /** @import * as $eslintobjectschema from "@eslint/object-schema"; */
  33. /** @typedef {$eslintobjectschema.PropertyDefinition} PropertyDefinition */
  34. /** @typedef {$eslintobjectschema.ObjectDefinition} ObjectDefinition */
  35. //------------------------------------------------------------------------------
  36. // Helpers
  37. //------------------------------------------------------------------------------
  38. /**
  39. * A strategy that does nothing.
  40. * @type {PropertyDefinition}
  41. */
  42. const NOOP_STRATEGY = {
  43. required: false,
  44. merge() {
  45. return undefined;
  46. },
  47. validate() {},
  48. };
  49. //------------------------------------------------------------------------------
  50. // Exports
  51. //------------------------------------------------------------------------------
  52. /**
  53. * The base schema that every ConfigArray uses.
  54. * @type {ObjectDefinition}
  55. */
  56. const baseSchema = Object.freeze({
  57. name: {
  58. required: false,
  59. merge() {
  60. return undefined;
  61. },
  62. validate(value) {
  63. if (typeof value !== "string") {
  64. throw new TypeError("Property must be a string.");
  65. }
  66. },
  67. },
  68. basePath: NOOP_STRATEGY,
  69. files: NOOP_STRATEGY,
  70. ignores: NOOP_STRATEGY,
  71. });
  72. /**
  73. * @fileoverview ConfigSchema
  74. * @author Nicholas C. Zakas
  75. */
  76. //------------------------------------------------------------------------------
  77. // Types
  78. //------------------------------------------------------------------------------
  79. //------------------------------------------------------------------------------
  80. // Helpers
  81. //------------------------------------------------------------------------------
  82. /**
  83. * Asserts that a given value is an array.
  84. * @param {*} value The value to check.
  85. * @returns {void}
  86. * @throws {TypeError} When the value is not an array.
  87. */
  88. function assertIsArray(value) {
  89. if (!Array.isArray(value)) {
  90. throw new TypeError("Expected value to be an array.");
  91. }
  92. }
  93. /**
  94. * Asserts that a given value is an array containing only strings and functions.
  95. * @param {*} value The value to check.
  96. * @returns {void}
  97. * @throws {TypeError} When the value is not an array of strings and functions.
  98. */
  99. function assertIsArrayOfStringsAndFunctions(value) {
  100. assertIsArray(value);
  101. if (
  102. value.some(
  103. item => typeof item !== "string" && typeof item !== "function",
  104. )
  105. ) {
  106. throw new TypeError(
  107. "Expected array to only contain strings and functions.",
  108. );
  109. }
  110. }
  111. /**
  112. * Asserts that a given value is a non-empty array.
  113. * @param {*} value The value to check.
  114. * @returns {void}
  115. * @throws {TypeError} When the value is not an array or an empty array.
  116. */
  117. function assertIsNonEmptyArray(value) {
  118. if (!Array.isArray(value) || value.length === 0) {
  119. throw new TypeError("Expected value to be a non-empty array.");
  120. }
  121. }
  122. //------------------------------------------------------------------------------
  123. // Exports
  124. //------------------------------------------------------------------------------
  125. /**
  126. * The schema for `files` and `ignores` that every ConfigArray uses.
  127. * @type {ObjectDefinition}
  128. */
  129. const filesAndIgnoresSchema = Object.freeze({
  130. basePath: {
  131. required: false,
  132. merge() {
  133. return undefined;
  134. },
  135. validate(value) {
  136. if (typeof value !== "string") {
  137. throw new TypeError("Expected value to be a string.");
  138. }
  139. },
  140. },
  141. files: {
  142. required: false,
  143. merge() {
  144. return undefined;
  145. },
  146. validate(value) {
  147. // first check if it's an array
  148. assertIsNonEmptyArray(value);
  149. // then check each member
  150. value.forEach(item => {
  151. if (Array.isArray(item)) {
  152. assertIsArrayOfStringsAndFunctions(item);
  153. } else if (
  154. typeof item !== "string" &&
  155. typeof item !== "function"
  156. ) {
  157. throw new TypeError(
  158. "Items must be a string, a function, or an array of strings and functions.",
  159. );
  160. }
  161. });
  162. },
  163. },
  164. ignores: {
  165. required: false,
  166. merge() {
  167. return undefined;
  168. },
  169. validate: assertIsArrayOfStringsAndFunctions,
  170. },
  171. });
  172. /**
  173. * @fileoverview ConfigArray
  174. * @author Nicholas C. Zakas
  175. */
  176. //------------------------------------------------------------------------------
  177. // Types
  178. //------------------------------------------------------------------------------
  179. /** @import * as $typests from "./types.ts"; */
  180. /** @typedef {$typests.ConfigObject} ConfigObject */
  181. /** @import * as $minimatch from "minimatch"; */
  182. /** @typedef {$minimatch.IMinimatchStatic} IMinimatchStatic */
  183. /** @typedef {$minimatch.IMinimatch} IMinimatch */
  184. /** @import * as PathImpl from "@jsr/std__path" */
  185. /*
  186. * This is a bit of a hack to make TypeScript happy with the Rollup-created
  187. * CommonJS file. Rollup doesn't do object destructuring for imported files
  188. * and instead imports the default via `require()`. This messes up type checking
  189. * for `ObjectSchema`. To work around that, we just import the type manually
  190. * and give it a different name to use in the JSDoc comments.
  191. */
  192. /** @typedef {ObjectSchema} ObjectSchemaInstance */
  193. //------------------------------------------------------------------------------
  194. // Helpers
  195. //------------------------------------------------------------------------------
  196. const Minimatch = minimatch.Minimatch;
  197. const debug = createDebug("@eslint/config-array");
  198. /**
  199. * A cache for minimatch instances.
  200. * @type {Map<string, IMinimatch>}
  201. */
  202. const minimatchCache = new Map();
  203. /**
  204. * A cache for negated minimatch instances.
  205. * @type {Map<string, IMinimatch>}
  206. */
  207. const negatedMinimatchCache = new Map();
  208. /**
  209. * Options to use with minimatch.
  210. * @type {Object}
  211. */
  212. const MINIMATCH_OPTIONS = {
  213. // matchBase: true,
  214. dot: true,
  215. allowWindowsEscape: true,
  216. };
  217. /**
  218. * The types of config objects that are supported.
  219. * @type {Set<string>}
  220. */
  221. const CONFIG_TYPES = new Set(["array", "function"]);
  222. /**
  223. * Fields that are considered metadata and not part of the config object.
  224. * @type {Set<string>}
  225. */
  226. const META_FIELDS = new Set(["name", "basePath"]);
  227. /**
  228. * A schema containing just files and ignores for early validation.
  229. * @type {ObjectSchemaInstance}
  230. */
  231. const FILES_AND_IGNORES_SCHEMA = new objectSchema.ObjectSchema(filesAndIgnoresSchema);
  232. // Precomputed constant objects returned by `ConfigArray.getConfigWithStatus`.
  233. const CONFIG_WITH_STATUS_EXTERNAL = Object.freeze({ status: "external" });
  234. const CONFIG_WITH_STATUS_IGNORED = Object.freeze({ status: "ignored" });
  235. const CONFIG_WITH_STATUS_UNCONFIGURED = Object.freeze({
  236. status: "unconfigured",
  237. });
  238. // Match two leading dots followed by a slash or the end of input.
  239. const EXTERNAL_PATH_REGEX = /^\.\.(?:\/|$)/u;
  240. /**
  241. * Wrapper error for config validation errors that adds a name to the front of the
  242. * error message.
  243. */
  244. class ConfigError extends Error {
  245. /**
  246. * Creates a new instance.
  247. * @param {string} name The config object name causing the error.
  248. * @param {number} index The index of the config object in the array.
  249. * @param {Object} options The options for the error.
  250. * @param {Error} [options.cause] The error that caused this error.
  251. * @param {string} [options.message] The message to use for the error.
  252. */
  253. constructor(name, index, { cause, message }) {
  254. const finalMessage = message || cause.message;
  255. super(`Config ${name}: ${finalMessage}`, { cause });
  256. // copy over custom properties that aren't represented
  257. if (cause) {
  258. for (const key of Object.keys(cause)) {
  259. if (!(key in this)) {
  260. this[key] = cause[key];
  261. }
  262. }
  263. }
  264. /**
  265. * The name of the error.
  266. * @type {string}
  267. * @readonly
  268. */
  269. this.name = "ConfigError";
  270. /**
  271. * The index of the config object in the array.
  272. * @type {number}
  273. * @readonly
  274. */
  275. this.index = index;
  276. }
  277. }
  278. /**
  279. * Gets the name of a config object.
  280. * @param {ConfigObject} config The config object to get the name of.
  281. * @returns {string} The name of the config object.
  282. */
  283. function getConfigName(config) {
  284. if (config && typeof config.name === "string" && config.name) {
  285. return `"${config.name}"`;
  286. }
  287. return "(unnamed)";
  288. }
  289. /**
  290. * Rethrows a config error with additional information about the config object.
  291. * @param {object} config The config object to get the name of.
  292. * @param {number} index The index of the config object in the array.
  293. * @param {Error} error The error to rethrow.
  294. * @throws {ConfigError} When the error is rethrown for a config.
  295. */
  296. function rethrowConfigError(config, index, error) {
  297. const configName = getConfigName(config);
  298. throw new ConfigError(configName, index, { cause: error });
  299. }
  300. /**
  301. * Shorthand for checking if a value is a string.
  302. * @param {any} value The value to check.
  303. * @returns {boolean} True if a string, false if not.
  304. */
  305. function isString(value) {
  306. return typeof value === "string";
  307. }
  308. /**
  309. * Creates a function that asserts that the config is valid
  310. * during normalization. This checks that the config is not nullish
  311. * and that files and ignores keys of a config object are valid as per base schema.
  312. * @param {Object} config The config object to check.
  313. * @param {number} index The index of the config object in the array.
  314. * @returns {void}
  315. * @throws {ConfigError} If the files and ignores keys of a config object are not valid.
  316. */
  317. function assertValidBaseConfig(config, index) {
  318. if (config === null) {
  319. throw new ConfigError(getConfigName(config), index, {
  320. message: "Unexpected null config.",
  321. });
  322. }
  323. if (config === undefined) {
  324. throw new ConfigError(getConfigName(config), index, {
  325. message: "Unexpected undefined config.",
  326. });
  327. }
  328. if (typeof config !== "object") {
  329. throw new ConfigError(getConfigName(config), index, {
  330. message: "Unexpected non-object config.",
  331. });
  332. }
  333. const validateConfig = {};
  334. if ("basePath" in config) {
  335. validateConfig.basePath = config.basePath;
  336. }
  337. if ("files" in config) {
  338. validateConfig.files = config.files;
  339. }
  340. if ("ignores" in config) {
  341. validateConfig.ignores = config.ignores;
  342. }
  343. try {
  344. FILES_AND_IGNORES_SCHEMA.validate(validateConfig);
  345. } catch (validationError) {
  346. rethrowConfigError(config, index, validationError);
  347. }
  348. }
  349. /**
  350. * Wrapper around minimatch that caches minimatch patterns for
  351. * faster matching speed over multiple file path evaluations.
  352. * @param {string} filepath The file path to match.
  353. * @param {string} pattern The glob pattern to match against.
  354. * @param {object} options The minimatch options to use.
  355. * @returns
  356. */
  357. function doMatch(filepath, pattern, options = {}) {
  358. let cache = minimatchCache;
  359. if (options.flipNegate) {
  360. cache = negatedMinimatchCache;
  361. }
  362. let matcher = cache.get(pattern);
  363. if (!matcher) {
  364. matcher = new Minimatch(
  365. pattern,
  366. Object.assign({}, MINIMATCH_OPTIONS, options),
  367. );
  368. cache.set(pattern, matcher);
  369. }
  370. return matcher.match(filepath);
  371. }
  372. /**
  373. * Normalizes a pattern by removing the leading "./" if present.
  374. * @param {string} pattern The pattern to normalize.
  375. * @returns {string} The normalized pattern.
  376. */
  377. function normalizePattern(pattern) {
  378. if (isString(pattern)) {
  379. if (pattern.startsWith("./")) {
  380. return pattern.slice(2);
  381. }
  382. if (pattern.startsWith("!./")) {
  383. return `!${pattern.slice(3)}`;
  384. }
  385. }
  386. return pattern;
  387. }
  388. /**
  389. * Checks if a given pattern requires normalization.
  390. * @param {any} pattern The pattern to check.
  391. * @returns {boolean} True if the pattern needs normalization, false otherwise.
  392. *
  393. */
  394. function needsPatternNormalization(pattern) {
  395. return (
  396. isString(pattern) &&
  397. (pattern.startsWith("./") || pattern.startsWith("!./"))
  398. );
  399. }
  400. /**
  401. * Normalizes `files` and `ignores` patterns in a config by removing "./" prefixes.
  402. * @param {Object} config The config object to normalize patterns in.
  403. * @param {string} namespacedBasePath The namespaced base path of the directory to which config base path is relative.
  404. * @param {PathImpl} path Path-handling implementation.
  405. * @returns {Object} The normalized config object.
  406. */
  407. function normalizeConfigPatterns(config, namespacedBasePath, path) {
  408. if (!config) {
  409. return config;
  410. }
  411. const hasBasePath = typeof config.basePath === "string";
  412. let needsNormalization = false;
  413. if (hasBasePath) {
  414. needsNormalization = true;
  415. }
  416. if (!needsNormalization && Array.isArray(config.files)) {
  417. needsNormalization = config.files.some(pattern => {
  418. if (Array.isArray(pattern)) {
  419. return pattern.some(needsPatternNormalization);
  420. }
  421. return needsPatternNormalization(pattern);
  422. });
  423. }
  424. if (!needsNormalization && Array.isArray(config.ignores)) {
  425. needsNormalization = config.ignores.some(needsPatternNormalization);
  426. }
  427. if (!needsNormalization) {
  428. return config;
  429. }
  430. const newConfig = { ...config };
  431. if (hasBasePath) {
  432. if (path.isAbsolute(config.basePath)) {
  433. newConfig.basePath = path.toNamespacedPath(config.basePath);
  434. } else {
  435. newConfig.basePath = path.resolve(
  436. namespacedBasePath,
  437. config.basePath,
  438. );
  439. }
  440. }
  441. if (Array.isArray(newConfig.files)) {
  442. newConfig.files = newConfig.files.map(pattern => {
  443. if (Array.isArray(pattern)) {
  444. return pattern.map(normalizePattern);
  445. }
  446. return normalizePattern(pattern);
  447. });
  448. }
  449. if (Array.isArray(newConfig.ignores)) {
  450. newConfig.ignores = newConfig.ignores.map(normalizePattern);
  451. }
  452. return newConfig;
  453. }
  454. /**
  455. * Normalizes a `ConfigArray` by flattening it and executing any functions
  456. * that are found inside.
  457. * @param {Array} items The items in a `ConfigArray`.
  458. * @param {Object} context The context object to pass into any function
  459. * found.
  460. * @param {Array<string>} extraConfigTypes The config types to check.
  461. * @param {string} namespacedBasePath The namespaced base path of the directory to which config base paths are relative.
  462. * @param {PathImpl} path Path-handling implementation.
  463. * @returns {Promise<Array>} A flattened array containing only config objects.
  464. * @throws {TypeError} When a config function returns a function.
  465. */
  466. async function normalize(
  467. items,
  468. context,
  469. extraConfigTypes,
  470. namespacedBasePath,
  471. path,
  472. ) {
  473. const allowFunctions = extraConfigTypes.includes("function");
  474. const allowArrays = extraConfigTypes.includes("array");
  475. async function* flatTraverse(array) {
  476. for (let item of array) {
  477. if (typeof item === "function") {
  478. if (!allowFunctions) {
  479. throw new TypeError("Unexpected function.");
  480. }
  481. item = item(context);
  482. if (item.then) {
  483. item = await item;
  484. }
  485. }
  486. if (Array.isArray(item)) {
  487. if (!allowArrays) {
  488. throw new TypeError("Unexpected array.");
  489. }
  490. yield* flatTraverse(item);
  491. } else if (typeof item === "function") {
  492. throw new TypeError(
  493. "A config function can only return an object or array.",
  494. );
  495. } else {
  496. yield item;
  497. }
  498. }
  499. }
  500. /*
  501. * Async iterables cannot be used with the spread operator, so we need to manually
  502. * create the array to return.
  503. */
  504. const asyncIterable = await flatTraverse(items);
  505. const configs = [];
  506. for await (const config of asyncIterable) {
  507. configs.push(normalizeConfigPatterns(config, namespacedBasePath, path));
  508. }
  509. return configs;
  510. }
  511. /**
  512. * Normalizes a `ConfigArray` by flattening it and executing any functions
  513. * that are found inside.
  514. * @param {Array} items The items in a `ConfigArray`.
  515. * @param {Object} context The context object to pass into any function
  516. * found.
  517. * @param {Array<string>} extraConfigTypes The config types to check.
  518. * @param {string} namespacedBasePath The namespaced base path of the directory to which config base paths are relative.
  519. * @param {PathImpl} path Path-handling implementation
  520. * @returns {Array} A flattened array containing only config objects.
  521. * @throws {TypeError} When a config function returns a function.
  522. */
  523. function normalizeSync(
  524. items,
  525. context,
  526. extraConfigTypes,
  527. namespacedBasePath,
  528. path,
  529. ) {
  530. const allowFunctions = extraConfigTypes.includes("function");
  531. const allowArrays = extraConfigTypes.includes("array");
  532. function* flatTraverse(array) {
  533. for (let item of array) {
  534. if (typeof item === "function") {
  535. if (!allowFunctions) {
  536. throw new TypeError("Unexpected function.");
  537. }
  538. item = item(context);
  539. if (item.then) {
  540. throw new TypeError(
  541. "Async config functions are not supported.",
  542. );
  543. }
  544. }
  545. if (Array.isArray(item)) {
  546. if (!allowArrays) {
  547. throw new TypeError("Unexpected array.");
  548. }
  549. yield* flatTraverse(item);
  550. } else if (typeof item === "function") {
  551. throw new TypeError(
  552. "A config function can only return an object or array.",
  553. );
  554. } else {
  555. yield item;
  556. }
  557. }
  558. }
  559. const configs = [];
  560. for (const config of flatTraverse(items)) {
  561. configs.push(normalizeConfigPatterns(config, namespacedBasePath, path));
  562. }
  563. return configs;
  564. }
  565. /**
  566. * Converts a given path to a relative path with all separator characters replaced by forward slashes (`"/"`).
  567. * @param {string} fileOrDirPath The unprocessed path to convert.
  568. * @param {string} namespacedBasePath The namespaced base path of the directory to which the calculated path shall be relative.
  569. * @param {PathImpl} path Path-handling implementations.
  570. * @returns {string} A relative path with all separator characters replaced by forward slashes.
  571. */
  572. function toRelativePath(fileOrDirPath, namespacedBasePath, path) {
  573. const fullPath = path.resolve(namespacedBasePath, fileOrDirPath);
  574. const namespacedFullPath = path.toNamespacedPath(fullPath);
  575. const relativePath = path.relative(namespacedBasePath, namespacedFullPath);
  576. return relativePath.replaceAll(path.SEPARATOR, "/");
  577. }
  578. /**
  579. * Determines if a given file path should be ignored based on the given
  580. * matcher.
  581. * @param {Array<{ basePath?: string, ignores: Array<string|((string) => boolean)>}>} configs Configuration objects containing `ignores`.
  582. * @param {string} filePath The unprocessed file path to check.
  583. * @param {string} relativeFilePath The path of the file to check relative to the base path,
  584. * using forward slash (`"/"`) as a separator.
  585. * @param {Object} [basePathData] Additional data needed to recalculate paths for configuration objects
  586. * that have `basePath` property.
  587. * @param {string} [basePathData.basePath] Namespaced path to witch `relativeFilePath` is relative.
  588. * @param {PathImpl} [basePathData.path] Path-handling implementation.
  589. * @returns {boolean} True if the path should be ignored and false if not.
  590. */
  591. function shouldIgnorePath(
  592. configs,
  593. filePath,
  594. relativeFilePath,
  595. { basePath, path } = {},
  596. ) {
  597. let shouldIgnore = false;
  598. for (const config of configs) {
  599. let relativeFilePathToCheck = relativeFilePath;
  600. if (config.basePath) {
  601. relativeFilePathToCheck = toRelativePath(
  602. path.resolve(basePath, relativeFilePath),
  603. config.basePath,
  604. path,
  605. );
  606. if (
  607. relativeFilePathToCheck === "" ||
  608. EXTERNAL_PATH_REGEX.test(relativeFilePathToCheck)
  609. ) {
  610. continue;
  611. }
  612. if (relativeFilePath.endsWith("/")) {
  613. relativeFilePathToCheck += "/";
  614. }
  615. }
  616. shouldIgnore = config.ignores.reduce((ignored, matcher) => {
  617. if (!ignored) {
  618. if (typeof matcher === "function") {
  619. return matcher(filePath);
  620. }
  621. // don't check negated patterns because we're not ignored yet
  622. if (!matcher.startsWith("!")) {
  623. return doMatch(relativeFilePathToCheck, matcher);
  624. }
  625. // otherwise we're still not ignored
  626. return false;
  627. }
  628. // only need to check negated patterns because we're ignored
  629. if (typeof matcher === "string" && matcher.startsWith("!")) {
  630. return !doMatch(relativeFilePathToCheck, matcher, {
  631. flipNegate: true,
  632. });
  633. }
  634. return ignored;
  635. }, shouldIgnore);
  636. }
  637. return shouldIgnore;
  638. }
  639. /**
  640. * Determines if a given file path is matched by a config. If the config
  641. * has no `files` field, then it matches; otherwise, if a `files` field
  642. * is present then we match the globs in `files` and exclude any globs in
  643. * `ignores`.
  644. * @param {string} filePath The unprocessed file path to check.
  645. * @param {string} relativeFilePath The path of the file to check relative to the base path,
  646. * using forward slash (`"/"`) as a separator.
  647. * @param {Object} config The config object to check.
  648. * @returns {boolean} True if the file path is matched by the config,
  649. * false if not.
  650. */
  651. function pathMatches(filePath, relativeFilePath, config) {
  652. // match both strings and functions
  653. function match(pattern) {
  654. if (isString(pattern)) {
  655. return doMatch(relativeFilePath, pattern);
  656. }
  657. if (typeof pattern === "function") {
  658. return pattern(filePath);
  659. }
  660. throw new TypeError(`Unexpected matcher type ${pattern}.`);
  661. }
  662. // check for all matches to config.files
  663. let filePathMatchesPattern = config.files.some(pattern => {
  664. if (Array.isArray(pattern)) {
  665. return pattern.every(match);
  666. }
  667. return match(pattern);
  668. });
  669. /*
  670. * If the file path matches the config.files patterns, then check to see
  671. * if there are any files to ignore.
  672. */
  673. if (filePathMatchesPattern && config.ignores) {
  674. /*
  675. * Pass config object without `basePath`, because `relativeFilePath` is already
  676. * calculated as relative to it.
  677. */
  678. filePathMatchesPattern = !shouldIgnorePath(
  679. [{ ignores: config.ignores }],
  680. filePath,
  681. relativeFilePath,
  682. );
  683. }
  684. return filePathMatchesPattern;
  685. }
  686. /**
  687. * Ensures that a ConfigArray has been normalized.
  688. * @param {ConfigArray} configArray The ConfigArray to check.
  689. * @returns {void}
  690. * @throws {Error} When the `ConfigArray` is not normalized.
  691. */
  692. function assertNormalized(configArray) {
  693. // TODO: Throw more verbose error
  694. if (!configArray.isNormalized()) {
  695. throw new Error(
  696. "ConfigArray must be normalized to perform this operation.",
  697. );
  698. }
  699. }
  700. /**
  701. * Ensures that config types are valid.
  702. * @param {Array<string>} extraConfigTypes The config types to check.
  703. * @returns {void}
  704. * @throws {TypeError} When the config types array is invalid.
  705. */
  706. function assertExtraConfigTypes(extraConfigTypes) {
  707. if (extraConfigTypes.length > 2) {
  708. throw new TypeError(
  709. "configTypes must be an array with at most two items.",
  710. );
  711. }
  712. for (const configType of extraConfigTypes) {
  713. if (!CONFIG_TYPES.has(configType)) {
  714. throw new TypeError(
  715. `Unexpected config type "${configType}" found. Expected one of: "object", "array", "function".`,
  716. );
  717. }
  718. }
  719. }
  720. /**
  721. * Returns path-handling implementations for Unix or Windows, depending on a given absolute path.
  722. * @param {string} fileOrDirPath The absolute path to check.
  723. * @returns {PathImpl} Path-handling implementations for the specified path.
  724. * @throws {Error} An error is thrown if the specified argument is not an absolute path.
  725. */
  726. function getPathImpl(fileOrDirPath) {
  727. // Posix absolute paths always start with a slash.
  728. if (fileOrDirPath.startsWith("/")) {
  729. return posixPath__namespace;
  730. }
  731. // Windows absolute paths start with a letter followed by a colon and at least one backslash,
  732. // or with two backslashes in the case of UNC paths.
  733. // Forward slashed are automatically normalized to backslashes.
  734. if (/^(?:[A-Za-z]:[/\\]|[/\\]{2})/u.test(fileOrDirPath)) {
  735. return windowsPath__namespace;
  736. }
  737. throw new Error(
  738. `Expected an absolute path but received "${fileOrDirPath}"`,
  739. );
  740. }
  741. //------------------------------------------------------------------------------
  742. // Public Interface
  743. //------------------------------------------------------------------------------
  744. const ConfigArraySymbol = {
  745. isNormalized: Symbol("isNormalized"),
  746. configCache: Symbol("configCache"),
  747. schema: Symbol("schema"),
  748. finalizeConfig: Symbol("finalizeConfig"),
  749. preprocessConfig: Symbol("preprocessConfig"),
  750. };
  751. // used to store calculate data for faster lookup
  752. const dataCache = new WeakMap();
  753. /**
  754. * Represents an array of config objects and provides method for working with
  755. * those config objects.
  756. */
  757. class ConfigArray extends Array {
  758. /**
  759. * The namespaced path of the config file directory.
  760. * @type {string}
  761. */
  762. #namespacedBasePath;
  763. /**
  764. * Path-handling implementations.
  765. * @type {PathImpl}
  766. */
  767. #path;
  768. /**
  769. * Creates a new instance of ConfigArray.
  770. * @param {Iterable|Function|Object} configs An iterable yielding config
  771. * objects, or a config function, or a config object.
  772. * @param {Object} options The options for the ConfigArray.
  773. * @param {string} [options.basePath="/"] The absolute path of the config file directory.
  774. * Defaults to `"/"`.
  775. * @param {boolean} [options.normalized=false] Flag indicating if the
  776. * configs have already been normalized.
  777. * @param {Object} [options.schema] The additional schema
  778. * definitions to use for the ConfigArray schema.
  779. * @param {Array<string>} [options.extraConfigTypes] List of config types supported.
  780. * @throws {TypeError} When the `basePath` is not a non-empty string,
  781. */
  782. constructor(
  783. configs,
  784. {
  785. basePath = "/",
  786. normalized = false,
  787. schema: customSchema,
  788. extraConfigTypes = [],
  789. } = {},
  790. ) {
  791. super();
  792. /**
  793. * Tracks if the array has been normalized.
  794. * @property isNormalized
  795. * @type {boolean}
  796. * @private
  797. */
  798. this[ConfigArraySymbol.isNormalized] = normalized;
  799. /**
  800. * The schema used for validating and merging configs.
  801. * @property schema
  802. * @type {ObjectSchemaInstance}
  803. * @private
  804. */
  805. this[ConfigArraySymbol.schema] = new objectSchema.ObjectSchema(
  806. Object.assign({}, customSchema, baseSchema),
  807. );
  808. if (!isString(basePath) || !basePath) {
  809. throw new TypeError("basePath must be a non-empty string");
  810. }
  811. /**
  812. * The path of the config file that this array was loaded from.
  813. * This is used to calculate filename matches.
  814. * @property basePath
  815. * @type {string}
  816. */
  817. this.basePath = basePath;
  818. assertExtraConfigTypes(extraConfigTypes);
  819. /**
  820. * The supported config types.
  821. * @type {Array<string>}
  822. */
  823. this.extraConfigTypes = [...extraConfigTypes];
  824. Object.freeze(this.extraConfigTypes);
  825. /**
  826. * A cache to store calculated configs for faster repeat lookup.
  827. * @property configCache
  828. * @type {Map<string, Object>}
  829. * @private
  830. */
  831. this[ConfigArraySymbol.configCache] = new Map();
  832. // init cache
  833. dataCache.set(this, {
  834. explicitMatches: new Map(),
  835. directoryMatches: new Map(),
  836. files: undefined,
  837. ignores: undefined,
  838. });
  839. // load the configs into this array
  840. if (Array.isArray(configs)) {
  841. this.push(...configs);
  842. } else {
  843. this.push(configs);
  844. }
  845. // select path-handling implementations depending on the base path
  846. this.#path = getPathImpl(basePath);
  847. // On Windows, `path.relative()` returns an absolute path when given two paths on different drives.
  848. // The namespaced base path is useful to make sure that calculated relative paths are always relative.
  849. // On Unix, it is identical to the base path.
  850. this.#namespacedBasePath = this.#path.toNamespacedPath(basePath);
  851. }
  852. /**
  853. * Prevent normal array methods from creating a new `ConfigArray` instance.
  854. * This is to ensure that methods such as `slice()` won't try to create a
  855. * new instance of `ConfigArray` behind the scenes as doing so may throw
  856. * an error due to the different constructor signature.
  857. * @type {ArrayConstructor} The `Array` constructor.
  858. */
  859. static get [Symbol.species]() {
  860. return Array;
  861. }
  862. /**
  863. * Returns the `files` globs from every config object in the array.
  864. * This can be used to determine which files will be matched by a
  865. * config array or to use as a glob pattern when no patterns are provided
  866. * for a command line interface.
  867. * @returns {Array<string|Function>} An array of matchers.
  868. */
  869. get files() {
  870. assertNormalized(this);
  871. // if this data has been cached, retrieve it
  872. const cache = dataCache.get(this);
  873. if (cache.files) {
  874. return cache.files;
  875. }
  876. // otherwise calculate it
  877. const result = [];
  878. for (const config of this) {
  879. if (config.files) {
  880. config.files.forEach(filePattern => {
  881. result.push(filePattern);
  882. });
  883. }
  884. }
  885. // store result
  886. cache.files = result;
  887. dataCache.set(this, cache);
  888. return result;
  889. }
  890. /**
  891. * Returns ignore matchers that should always be ignored regardless of
  892. * the matching `files` fields in any configs. This is necessary to mimic
  893. * the behavior of things like .gitignore and .eslintignore, allowing a
  894. * globbing operation to be faster.
  895. * @returns {Object[]} An array of config objects representing global ignores.
  896. */
  897. get ignores() {
  898. assertNormalized(this);
  899. // if this data has been cached, retrieve it
  900. const cache = dataCache.get(this);
  901. if (cache.ignores) {
  902. return cache.ignores;
  903. }
  904. // otherwise calculate it
  905. const result = [];
  906. for (const config of this) {
  907. /*
  908. * We only count ignores if there are no other keys in the object.
  909. * In this case, it acts list a globally ignored pattern. If there
  910. * are additional keys, then ignores act like exclusions.
  911. */
  912. if (
  913. config.ignores &&
  914. Object.keys(config).filter(key => !META_FIELDS.has(key))
  915. .length === 1
  916. ) {
  917. result.push(config);
  918. }
  919. }
  920. // store result
  921. cache.ignores = result;
  922. dataCache.set(this, cache);
  923. return result;
  924. }
  925. /**
  926. * Indicates if the config array has been normalized.
  927. * @returns {boolean} True if the config array is normalized, false if not.
  928. */
  929. isNormalized() {
  930. return this[ConfigArraySymbol.isNormalized];
  931. }
  932. /**
  933. * Normalizes a config array by flattening embedded arrays and executing
  934. * config functions.
  935. * @param {Object} [context] The context object for config functions.
  936. * @returns {Promise<ConfigArray>} The current ConfigArray instance.
  937. */
  938. async normalize(context = {}) {
  939. if (!this.isNormalized()) {
  940. const normalizedConfigs = await normalize(
  941. this,
  942. context,
  943. this.extraConfigTypes,
  944. this.#namespacedBasePath,
  945. this.#path,
  946. );
  947. this.length = 0;
  948. this.push(
  949. ...normalizedConfigs.map(
  950. this[ConfigArraySymbol.preprocessConfig].bind(this),
  951. ),
  952. );
  953. this.forEach(assertValidBaseConfig);
  954. this[ConfigArraySymbol.isNormalized] = true;
  955. // prevent further changes
  956. Object.freeze(this);
  957. }
  958. return this;
  959. }
  960. /**
  961. * Normalizes a config array by flattening embedded arrays and executing
  962. * config functions.
  963. * @param {Object} [context] The context object for config functions.
  964. * @returns {ConfigArray} The current ConfigArray instance.
  965. */
  966. normalizeSync(context = {}) {
  967. if (!this.isNormalized()) {
  968. const normalizedConfigs = normalizeSync(
  969. this,
  970. context,
  971. this.extraConfigTypes,
  972. this.#namespacedBasePath,
  973. this.#path,
  974. );
  975. this.length = 0;
  976. this.push(
  977. ...normalizedConfigs.map(
  978. this[ConfigArraySymbol.preprocessConfig].bind(this),
  979. ),
  980. );
  981. this.forEach(assertValidBaseConfig);
  982. this[ConfigArraySymbol.isNormalized] = true;
  983. // prevent further changes
  984. Object.freeze(this);
  985. }
  986. return this;
  987. }
  988. /* eslint-disable class-methods-use-this -- Desired as instance methods */
  989. /**
  990. * Finalizes the state of a config before being cached and returned by
  991. * `getConfig()`. Does nothing by default but is provided to be
  992. * overridden by subclasses as necessary.
  993. * @param {Object} config The config to finalize.
  994. * @returns {Object} The finalized config.
  995. */
  996. // Cast key to `never` to prevent TypeScript from adding the signature `[x: symbol]: (config: any) => any` to the type of the class.
  997. [/** @type {never} */ (ConfigArraySymbol.finalizeConfig)](config) {
  998. return config;
  999. }
  1000. /**
  1001. * Preprocesses a config during the normalization process. This is the
  1002. * method to override if you want to convert an array item before it is
  1003. * validated for the first time. For example, if you want to replace a
  1004. * string with an object, this is the method to override.
  1005. * @param {Object} config The config to preprocess.
  1006. * @returns {Object} The config to use in place of the argument.
  1007. */
  1008. // Cast key to `never` to prevent TypeScript from adding the signature `[x: symbol]: (config: any) => any` to the type of the class.
  1009. [/** @type {never} */ (ConfigArraySymbol.preprocessConfig)](config) {
  1010. return config;
  1011. }
  1012. /* eslint-enable class-methods-use-this -- Desired as instance methods */
  1013. /**
  1014. * Returns the config object for a given file path and a status that can be used to determine why a file has no config.
  1015. * @param {string} filePath The path of a file to get a config for.
  1016. * @returns {{ config?: Object, status: "ignored"|"external"|"unconfigured"|"matched" }}
  1017. * An object with an optional property `config` and property `status`.
  1018. * `config` is the config object for the specified file as returned by {@linkcode ConfigArray.getConfig},
  1019. * `status` a is one of the constants returned by {@linkcode ConfigArray.getConfigStatus}.
  1020. */
  1021. getConfigWithStatus(filePath) {
  1022. assertNormalized(this);
  1023. const cache = this[ConfigArraySymbol.configCache];
  1024. // first check the cache for a filename match to avoid duplicate work
  1025. if (cache.has(filePath)) {
  1026. return cache.get(filePath);
  1027. }
  1028. // check to see if the file is outside the base path
  1029. const relativeToBaseFilePath = toRelativePath(
  1030. filePath,
  1031. this.#namespacedBasePath,
  1032. this.#path,
  1033. );
  1034. if (EXTERNAL_PATH_REGEX.test(relativeToBaseFilePath)) {
  1035. debug(`No config for file ${filePath} outside of base path`);
  1036. // cache and return result
  1037. cache.set(filePath, CONFIG_WITH_STATUS_EXTERNAL);
  1038. return CONFIG_WITH_STATUS_EXTERNAL;
  1039. }
  1040. // next check to see if the file should be ignored
  1041. // check if this should be ignored due to its directory
  1042. if (this.isDirectoryIgnored(this.#path.dirname(filePath))) {
  1043. debug(`Ignoring ${filePath} based on directory pattern`);
  1044. // cache and return result
  1045. cache.set(filePath, CONFIG_WITH_STATUS_IGNORED);
  1046. return CONFIG_WITH_STATUS_IGNORED;
  1047. }
  1048. if (
  1049. shouldIgnorePath(this.ignores, filePath, relativeToBaseFilePath, {
  1050. basePath: this.#namespacedBasePath,
  1051. path: this.#path,
  1052. })
  1053. ) {
  1054. debug(`Ignoring ${filePath} based on file pattern`);
  1055. // cache and return result
  1056. cache.set(filePath, CONFIG_WITH_STATUS_IGNORED);
  1057. return CONFIG_WITH_STATUS_IGNORED;
  1058. }
  1059. // filePath isn't automatically ignored, so try to construct config
  1060. const matchingConfigIndices = [];
  1061. let matchFound = false;
  1062. const universalPattern = /^\*$|^!|\/\*{1,2}$/u;
  1063. this.forEach((config, index) => {
  1064. const relativeFilePath = config.basePath
  1065. ? toRelativePath(
  1066. this.#path.resolve(this.#namespacedBasePath, filePath),
  1067. config.basePath,
  1068. this.#path,
  1069. )
  1070. : relativeToBaseFilePath;
  1071. if (config.basePath && EXTERNAL_PATH_REGEX.test(relativeFilePath)) {
  1072. debug(
  1073. `Skipped config found for ${filePath} (based on config's base path: ${config.basePath}`,
  1074. );
  1075. return;
  1076. }
  1077. if (!config.files) {
  1078. if (!config.ignores) {
  1079. debug(`Universal config found for ${filePath}`);
  1080. matchingConfigIndices.push(index);
  1081. return;
  1082. }
  1083. if (
  1084. Object.keys(config).filter(key => !META_FIELDS.has(key))
  1085. .length === 1
  1086. ) {
  1087. debug(
  1088. `Skipped config found for ${filePath} (global ignores)`,
  1089. );
  1090. return;
  1091. }
  1092. /*
  1093. * Pass config object without `basePath`, because `relativeFilePath` is already
  1094. * calculated as relative to it.
  1095. */
  1096. if (
  1097. shouldIgnorePath(
  1098. [{ ignores: config.ignores }],
  1099. filePath,
  1100. relativeFilePath,
  1101. )
  1102. ) {
  1103. debug(
  1104. `Skipped config found for ${filePath} (based on ignores: ${config.ignores})`,
  1105. );
  1106. return;
  1107. }
  1108. debug(
  1109. `Matching config found for ${filePath} (based on ignores: ${config.ignores})`,
  1110. );
  1111. matchingConfigIndices.push(index);
  1112. return;
  1113. }
  1114. /*
  1115. * If a config has a files pattern * or patterns ending in /** or /*,
  1116. * and the filePath only matches those patterns, then the config is only
  1117. * applied if there is another config where the filePath matches
  1118. * a file with a specific extensions such as *.js.
  1119. */
  1120. const nonUniversalFiles = [];
  1121. const universalFiles = config.files.filter(element => {
  1122. if (Array.isArray(element)) {
  1123. /*
  1124. * filePath matches an element that is an array only if it matches
  1125. * all patterns in it (AND operation). Therefore, if there is at least
  1126. * one non-universal pattern in the array, and filePath matches the array,
  1127. * then we know for sure that filePath matches at least one non-universal
  1128. * pattern, so we can consider the entire array to be non-universal.
  1129. * In other words, all patterns in the array need to be universal
  1130. * for it to be considered universal.
  1131. */
  1132. if (
  1133. element.every(pattern => universalPattern.test(pattern))
  1134. ) {
  1135. return true;
  1136. }
  1137. nonUniversalFiles.push(element);
  1138. return false;
  1139. }
  1140. // element is a string
  1141. if (universalPattern.test(element)) {
  1142. return true;
  1143. }
  1144. nonUniversalFiles.push(element);
  1145. return false;
  1146. });
  1147. // universal patterns were found so we need to check the config twice
  1148. if (universalFiles.length) {
  1149. debug("Universal files patterns found. Checking carefully.");
  1150. // check that the config matches without the non-universal files first
  1151. if (
  1152. nonUniversalFiles.length &&
  1153. pathMatches(filePath, relativeFilePath, {
  1154. files: nonUniversalFiles,
  1155. ignores: config.ignores,
  1156. })
  1157. ) {
  1158. debug(`Matching config found for ${filePath}`);
  1159. matchingConfigIndices.push(index);
  1160. matchFound = true;
  1161. return;
  1162. }
  1163. // if there wasn't a match then check if it matches with universal files
  1164. if (
  1165. universalFiles.length &&
  1166. pathMatches(filePath, relativeFilePath, {
  1167. files: universalFiles,
  1168. ignores: config.ignores,
  1169. })
  1170. ) {
  1171. debug(`Matching config found for ${filePath}`);
  1172. matchingConfigIndices.push(index);
  1173. return;
  1174. }
  1175. // if we make here, then there was no match
  1176. return;
  1177. }
  1178. // the normal case
  1179. if (pathMatches(filePath, relativeFilePath, config)) {
  1180. debug(`Matching config found for ${filePath}`);
  1181. matchingConfigIndices.push(index);
  1182. matchFound = true;
  1183. }
  1184. });
  1185. // if matching both files and ignores, there will be no config to create
  1186. if (!matchFound) {
  1187. debug(`No matching configs found for ${filePath}`);
  1188. // cache and return result
  1189. cache.set(filePath, CONFIG_WITH_STATUS_UNCONFIGURED);
  1190. return CONFIG_WITH_STATUS_UNCONFIGURED;
  1191. }
  1192. // check to see if there is a config cached by indices
  1193. const indicesKey = matchingConfigIndices.toString();
  1194. let configWithStatus = cache.get(indicesKey);
  1195. if (configWithStatus) {
  1196. // also store for filename for faster lookup next time
  1197. cache.set(filePath, configWithStatus);
  1198. return configWithStatus;
  1199. }
  1200. // otherwise construct the config
  1201. // eslint-disable-next-line array-callback-return, consistent-return -- rethrowConfigError always throws an error
  1202. let finalConfig = matchingConfigIndices.reduce((result, index) => {
  1203. try {
  1204. return this[ConfigArraySymbol.schema].merge(
  1205. result,
  1206. this[index],
  1207. );
  1208. } catch (validationError) {
  1209. rethrowConfigError(this[index], index, validationError);
  1210. }
  1211. }, {});
  1212. finalConfig = this[ConfigArraySymbol.finalizeConfig](finalConfig);
  1213. configWithStatus = Object.freeze({
  1214. config: finalConfig,
  1215. status: "matched",
  1216. });
  1217. cache.set(filePath, configWithStatus);
  1218. cache.set(indicesKey, configWithStatus);
  1219. return configWithStatus;
  1220. }
  1221. /**
  1222. * Returns the config object for a given file path.
  1223. * @param {string} filePath The path of a file to get a config for.
  1224. * @returns {Object|undefined} The config object for this file or `undefined`.
  1225. */
  1226. getConfig(filePath) {
  1227. return this.getConfigWithStatus(filePath).config;
  1228. }
  1229. /**
  1230. * Determines whether a file has a config or why it doesn't.
  1231. * @param {string} filePath The path of the file to check.
  1232. * @returns {"ignored"|"external"|"unconfigured"|"matched"} One of the following values:
  1233. * * `"ignored"`: the file is ignored
  1234. * * `"external"`: the file is outside the base path
  1235. * * `"unconfigured"`: the file is not matched by any config
  1236. * * `"matched"`: the file has a matching config
  1237. */
  1238. getConfigStatus(filePath) {
  1239. return this.getConfigWithStatus(filePath).status;
  1240. }
  1241. /**
  1242. * Determines if the given filepath is ignored based on the configs.
  1243. * @param {string} filePath The path of a file to check.
  1244. * @returns {boolean} True if the path is ignored, false if not.
  1245. * @deprecated Use `isFileIgnored` instead.
  1246. */
  1247. isIgnored(filePath) {
  1248. return this.isFileIgnored(filePath);
  1249. }
  1250. /**
  1251. * Determines if the given filepath is ignored based on the configs.
  1252. * @param {string} filePath The path of a file to check.
  1253. * @returns {boolean} True if the path is ignored, false if not.
  1254. */
  1255. isFileIgnored(filePath) {
  1256. return this.getConfigStatus(filePath) === "ignored";
  1257. }
  1258. /**
  1259. * Determines if the given directory is ignored based on the configs.
  1260. * This checks only default `ignores` that don't have `files` in the
  1261. * same config. A pattern such as `/foo` be considered to ignore the directory
  1262. * while a pattern such as `/foo/**` is not considered to ignore the
  1263. * directory because it is matching files.
  1264. * @param {string} directoryPath The path of a directory to check.
  1265. * @returns {boolean} True if the directory is ignored, false if not. Will
  1266. * return true for any directory that is not inside of `basePath`.
  1267. * @throws {Error} When the `ConfigArray` is not normalized.
  1268. */
  1269. isDirectoryIgnored(directoryPath) {
  1270. assertNormalized(this);
  1271. const relativeDirectoryPath = toRelativePath(
  1272. directoryPath,
  1273. this.#namespacedBasePath,
  1274. this.#path,
  1275. );
  1276. // basePath directory can never be ignored
  1277. if (relativeDirectoryPath === "") {
  1278. return false;
  1279. }
  1280. if (EXTERNAL_PATH_REGEX.test(relativeDirectoryPath)) {
  1281. return true;
  1282. }
  1283. // first check the cache
  1284. const cache = dataCache.get(this).directoryMatches;
  1285. if (cache.has(relativeDirectoryPath)) {
  1286. return cache.get(relativeDirectoryPath);
  1287. }
  1288. const directoryParts = relativeDirectoryPath.split("/");
  1289. let relativeDirectoryToCheck = "";
  1290. let result;
  1291. /*
  1292. * In order to get the correct gitignore-style ignores, where an
  1293. * ignored parent directory cannot have any descendants unignored,
  1294. * we need to check every directory starting at the parent all
  1295. * the way down to the actual requested directory.
  1296. *
  1297. * We aggressively cache all of this info to make sure we don't
  1298. * have to recalculate everything for every call.
  1299. */
  1300. do {
  1301. relativeDirectoryToCheck += `${directoryParts.shift()}/`;
  1302. result = shouldIgnorePath(
  1303. this.ignores,
  1304. this.#path.join(this.basePath, relativeDirectoryToCheck),
  1305. relativeDirectoryToCheck,
  1306. {
  1307. basePath: this.#namespacedBasePath,
  1308. path: this.#path,
  1309. },
  1310. );
  1311. cache.set(relativeDirectoryToCheck, result);
  1312. } while (!result && directoryParts.length);
  1313. // also cache the result for the requested path
  1314. cache.set(relativeDirectoryPath, result);
  1315. return result;
  1316. }
  1317. }
  1318. Object.defineProperty(exports, "ObjectSchema", {
  1319. enumerable: true,
  1320. get: function () { return objectSchema.ObjectSchema; }
  1321. });
  1322. exports.ConfigArray = ConfigArray;
  1323. exports.ConfigArraySymbol = ConfigArraySymbol;