'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var debugOrig = require('debug'); var fs = require('fs'); var importFresh = require('import-fresh'); var Module = require('module'); var path = require('path'); var stripComments = require('strip-json-comments'); var assert = require('assert'); var ignore = require('ignore'); var util = require('util'); var minimatch = require('minimatch'); var Ajv = require('ajv'); var globals = require('globals'); var os = require('os'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var debugOrig__default = /*#__PURE__*/_interopDefaultLegacy(debugOrig); var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); var importFresh__default = /*#__PURE__*/_interopDefaultLegacy(importFresh); var Module__default = /*#__PURE__*/_interopDefaultLegacy(Module); var path__default = /*#__PURE__*/_interopDefaultLegacy(path); var stripComments__default = /*#__PURE__*/_interopDefaultLegacy(stripComments); var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert); var ignore__default = /*#__PURE__*/_interopDefaultLegacy(ignore); var util__default = /*#__PURE__*/_interopDefaultLegacy(util); var minimatch__default = /*#__PURE__*/_interopDefaultLegacy(minimatch); var Ajv__default = /*#__PURE__*/_interopDefaultLegacy(Ajv); var globals__default = /*#__PURE__*/_interopDefaultLegacy(globals); var os__default = /*#__PURE__*/_interopDefaultLegacy(os); /** * @fileoverview `IgnorePattern` class. * * `IgnorePattern` class has the set of glob patterns and the base path. * * It provides two static methods. * * - `IgnorePattern.createDefaultIgnore(cwd)` * Create the default predicate function. * - `IgnorePattern.createIgnore(ignorePatterns)` * Create the predicate function from multiple `IgnorePattern` objects. * * It provides two properties and a method. * * - `patterns` * The glob patterns that ignore to lint. * - `basePath` * The base path of the glob patterns. If absolute paths existed in the * glob patterns, those are handled as relative paths to the base path. * - `getPatternsRelativeTo(basePath)` * Get `patterns` as modified for a given base path. It modifies the * absolute paths in the patterns as prepending the difference of two base * paths. * * `ConfigArrayFactory` creates `IgnorePattern` objects when it processes * `ignorePatterns` properties. * * @author Toru Nagashima */ const debug$3 = debugOrig__default["default"]("eslintrc:ignore-pattern"); /** @typedef {ReturnType} Ignore */ //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * Get the path to the common ancestor directory of given paths. * @param {string[]} sourcePaths The paths to calculate the common ancestor. * @returns {string} The path to the common ancestor directory. */ function getCommonAncestorPath(sourcePaths) { let result = sourcePaths[0]; for (let i = 1; i < sourcePaths.length; ++i) { const a = result; const b = sourcePaths[i]; // Set the shorter one (it's the common ancestor if one includes the other). result = a.length < b.length ? a : b; // Set the common ancestor. for (let j = 0, lastSepPos = 0; j < a.length && j < b.length; ++j) { if (a[j] !== b[j]) { result = a.slice(0, lastSepPos); break; } if (a[j] === path__default["default"].sep) { lastSepPos = j; } } } let resolvedResult = result || path__default["default"].sep; // if Windows common ancestor is root of drive must have trailing slash to be absolute. if (resolvedResult && resolvedResult.endsWith(":") && process.platform === "win32") { resolvedResult += path__default["default"].sep; } return resolvedResult; } /** * Make relative path. * @param {string} from The source path to get relative path. * @param {string} to The destination path to get relative path. * @returns {string} The relative path. */ function relative(from, to) { const relPath = path__default["default"].relative(from, to); if (path__default["default"].sep === "/") { return relPath; } return relPath.split(path__default["default"].sep).join("/"); } /** * Get the trailing slash if existed. * @param {string} filePath The path to check. * @returns {string} The trailing slash if existed. */ function dirSuffix(filePath) { const isDir = ( filePath.endsWith(path__default["default"].sep) || (process.platform === "win32" && filePath.endsWith("/")) ); return isDir ? "/" : ""; } const DefaultPatterns = Object.freeze(["/**/node_modules/*"]); const DotPatterns = Object.freeze([".*", "!.eslintrc.*", "!../"]); //------------------------------------------------------------------------------ // Public //------------------------------------------------------------------------------ class IgnorePattern { /** * The default patterns. * @type {string[]} */ static get DefaultPatterns() { return DefaultPatterns; } /** * Create the default predicate function. * @param {string} cwd The current working directory. * @returns {((filePath:string, dot:boolean) => boolean) & {basePath:string; patterns:string[]}} * The preficate function. * The first argument is an absolute path that is checked. * The second argument is the flag to not ignore dotfiles. * If the predicate function returned `true`, it means the path should be ignored. */ static createDefaultIgnore(cwd) { return this.createIgnore([new IgnorePattern(DefaultPatterns, cwd)]); } /** * Create the predicate function from multiple `IgnorePattern` objects. * @param {IgnorePattern[]} ignorePatterns The list of ignore patterns. * @returns {((filePath:string, dot?:boolean) => boolean) & {basePath:string; patterns:string[]}} * The preficate function. * The first argument is an absolute path that is checked. * The second argument is the flag to not ignore dotfiles. * If the predicate function returned `true`, it means the path should be ignored. */ static createIgnore(ignorePatterns) { debug$3("Create with: %o", ignorePatterns); const basePath = getCommonAncestorPath(ignorePatterns.map(p => p.basePath)); const patterns = [].concat( ...ignorePatterns.map(p => p.getPatternsRelativeTo(basePath)) ); const ig = ignore__default["default"]({ allowRelativePaths: true }).add([...DotPatterns, ...patterns]); const dotIg = ignore__default["default"]({ allowRelativePaths: true }).add(patterns); debug$3(" processed: %o", { basePath, patterns }); return Object.assign( (filePath, dot = false) => { assert__default["default"](path__default["default"].isAbsolute(filePath), "'filePath' should be an absolute path."); const relPathRaw = relative(basePath, filePath); const relPath = relPathRaw && (relPathRaw + dirSuffix(filePath)); const adoptedIg = dot ? dotIg : ig; const result = relPath !== "" && adoptedIg.ignores(relPath); debug$3("Check", { filePath, dot, relativePath: relPath, result }); return result; }, { basePath, patterns } ); } /** * Initialize a new `IgnorePattern` instance. * @param {string[]} patterns The glob patterns that ignore to lint. * @param {string} basePath The base path of `patterns`. */ constructor(patterns, basePath) { assert__default["default"](path__default["default"].isAbsolute(basePath), "'basePath' should be an absolute path."); /** * The glob patterns that ignore to lint. * @type {string[]} */ this.patterns = patterns; /** * The base path of `patterns`. * @type {string} */ this.basePath = basePath; /** * If `true` then patterns which don't start with `/` will match the paths to the outside of `basePath`. Defaults to `false`. * * It's set `true` for `.eslintignore`, `package.json`, and `--ignore-path` for backward compatibility. * It's `false` as-is for `ignorePatterns` property in config files. * @type {boolean} */ this.loose = false; } /** * Get `patterns` as modified for a given base path. It modifies the * absolute paths in the patterns as prepending the difference of two base * paths. * @param {string} newBasePath The base path. * @returns {string[]} Modifired patterns. */ getPatternsRelativeTo(newBasePath) { assert__default["default"](path__default["default"].isAbsolute(newBasePath), "'newBasePath' should be an absolute path."); const { basePath, loose, patterns } = this; if (newBasePath === basePath) { return patterns; } const prefix = `/${relative(newBasePath, basePath)}`; return patterns.map(pattern => { const negative = pattern.startsWith("!"); const head = negative ? "!" : ""; const body = negative ? pattern.slice(1) : pattern; if (body.startsWith("/") || body.startsWith("../")) { return `${head}${prefix}${body}`; } return loose ? pattern : `${head}${prefix}/**/${body}`; }); } } /** * @fileoverview `ExtractedConfig` class. * * `ExtractedConfig` class expresses a final configuration for a specific file. * * It provides one method. * * - `toCompatibleObjectAsConfigFileContent()` * Convert this configuration to the compatible object as the content of * config files. It converts the loaded parser and plugins to strings. * `CLIEngine#getConfigForFile(filePath)` method uses this method. * * `ConfigArray#extractConfig(filePath)` creates a `ExtractedConfig` instance. * * @author Toru Nagashima */ // For VSCode intellisense /** @typedef {import("../../shared/types").ConfigData} ConfigData */ /** @typedef {import("../../shared/types").GlobalConf} GlobalConf */ /** @typedef {import("../../shared/types").SeverityConf} SeverityConf */ /** @typedef {import("./config-dependency").DependentParser} DependentParser */ /** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */ /** * Check if `xs` starts with `ys`. * @template T * @param {T[]} xs The array to check. * @param {T[]} ys The array that may be the first part of `xs`. * @returns {boolean} `true` if `xs` starts with `ys`. */ function startsWith(xs, ys) { return xs.length >= ys.length && ys.every((y, i) => y === xs[i]); } /** * The class for extracted config data. */ class ExtractedConfig { constructor() { /** * The config name what `noInlineConfig` setting came from. * @type {string} */ this.configNameOfNoInlineConfig = ""; /** * Environments. * @type {Record} */ this.env = {}; /** * Global variables. * @type {Record} */ this.globals = {}; /** * The glob patterns that ignore to lint. * @type {(((filePath:string, dot?:boolean) => boolean) & { basePath:string; patterns:string[] }) | undefined} */ this.ignores = void 0; /** * The flag that disables directive comments. * @type {boolean|undefined} */ this.noInlineConfig = void 0; /** * Parser definition. * @type {DependentParser|null} */ this.parser = null; /** * Options for the parser. * @type {Object} */ this.parserOptions = {}; /** * Plugin definitions. * @type {Record} */ this.plugins = {}; /** * Processor ID. * @type {string|null} */ this.processor = null; /** * The flag that reports unused `eslint-disable` directive comments. * @type {boolean|undefined} */ this.reportUnusedDisableDirectives = void 0; /** * Rule settings. * @type {Record} */ this.rules = {}; /** * Shared settings. * @type {Object} */ this.settings = {}; } /** * Convert this config to the compatible object as a config file content. * @returns {ConfigData} The converted object. */ toCompatibleObjectAsConfigFileContent() { const { /* eslint-disable no-unused-vars */ configNameOfNoInlineConfig: _ignore1, processor: _ignore2, /* eslint-enable no-unused-vars */ ignores, ...config } = this; config.parser = config.parser && config.parser.filePath; config.plugins = Object.keys(config.plugins).filter(Boolean).reverse(); config.ignorePatterns = ignores ? ignores.patterns : []; // Strip the default patterns from `ignorePatterns`. if (startsWith(config.ignorePatterns, IgnorePattern.DefaultPatterns)) { config.ignorePatterns = config.ignorePatterns.slice(IgnorePattern.DefaultPatterns.length); } return config; } } /** * @fileoverview `ConfigArray` class. * * `ConfigArray` class expresses the full of a configuration. It has the entry * config file, base config files that were extended, loaded parsers, and loaded * plugins. * * `ConfigArray` class provides three properties and two methods. * * - `pluginEnvironments` * - `pluginProcessors` * - `pluginRules` * The `Map` objects that contain the members of all plugins that this * config array contains. Those map objects don't have mutation methods. * Those keys are the member ID such as `pluginId/memberName`. * - `isRoot()` * If `true` then this configuration has `root:true` property. * - `extractConfig(filePath)` * Extract the final configuration for a given file. This means merging * every config array element which that `criteria` property matched. The * `filePath` argument must be an absolute path. * * `ConfigArrayFactory` provides the loading logic of config files. * * @author Toru Nagashima */ //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ // Define types for VSCode IntelliSense. /** @typedef {import("../../shared/types").Environment} Environment */ /** @typedef {import("../../shared/types").GlobalConf} GlobalConf */ /** @typedef {import("../../shared/types").RuleConf} RuleConf */ /** @typedef {import("../../shared/types").Rule} Rule */ /** @typedef {import("../../shared/types").Plugin} Plugin */ /** @typedef {import("../../shared/types").Processor} Processor */ /** @typedef {import("./config-dependency").DependentParser} DependentParser */ /** @typedef {import("./config-dependency").DependentPlugin} DependentPlugin */ /** @typedef {import("./override-tester")["OverrideTester"]} OverrideTester */ /** * @typedef {Object} ConfigArrayElement * @property {string} name The name of this config element. * @property {string} filePath The path to the source file of this config element. * @property {InstanceType|null} criteria The tester for the `files` and `excludedFiles` of this config element. * @property {Record|undefined} env The environment settings. * @property {Record|undefined} globals The global variable settings. * @property {IgnorePattern|undefined} ignorePattern The ignore patterns. * @property {boolean|undefined} noInlineConfig The flag that disables directive comments. * @property {DependentParser|undefined} parser The parser loader. * @property {Object|undefined} parserOptions The parser options. * @property {Record|undefined} plugins The plugin loaders. * @property {string|undefined} processor The processor name to refer plugin's processor. * @property {boolean|undefined} reportUnusedDisableDirectives The flag to report unused `eslint-disable` comments. * @property {boolean|undefined} root The flag to express root. * @property {Record|undefined} rules The rule settings * @property {Object|undefined} settings The shared settings. * @property {"config" | "ignore" | "implicit-processor"} type The element type. */ /** * @typedef {Object} ConfigArrayInternalSlots * @property {Map} cache The cache to extract configs. * @property {ReadonlyMap|null} envMap The map from environment ID to environment definition. * @property {ReadonlyMap|null} processorMap The map from processor ID to environment definition. * @property {ReadonlyMap|null} ruleMap The map from rule ID to rule definition. */ /** @type {WeakMap} */ const internalSlotsMap$2 = new class extends WeakMap { get(key) { let value = super.get(key); if (!value) { value = { cache: new Map(), envMap: null, processorMap: null, ruleMap: null }; super.set(key, value); } return value; } }(); /** * Get the indices which are matched to a given file. * @param {ConfigArrayElement[]} elements The elements. * @param {string} filePath The path to a target file. * @returns {number[]} The indices. */ function getMatchedIndices(elements, filePath) { const indices = []; for (let i = elements.length - 1; i >= 0; --i) { const element = elements[i]; if (!element.criteria || (filePath && element.criteria.test(filePath))) { indices.push(i); } } return indices; } /** * Check if a value is a non-null object. * @param {any} x The value to check. * @returns {boolean} `true` if the value is a non-null object. */ function isNonNullObject(x) { return typeof x === "object" && x !== null; } /** * Merge two objects. * * Assign every property values of `y` to `x` if `x` doesn't have the property. * If `x`'s property value is an object, it does recursive. * @param {Object} target The destination to merge * @param {Object|undefined} source The source to merge. * @returns {void} */ function mergeWithoutOverwrite(target, source) { if (!isNonNullObject(source)) { return; } for (const key of Object.keys(source)) { if (key === "__proto__") { continue; } if (isNonNullObject(target[key])) { mergeWithoutOverwrite(target[key], source[key]); } else if (target[key] === void 0) { if (isNonNullObject(source[key])) { target[key] = Array.isArray(source[key]) ? [] : {}; mergeWithoutOverwrite(target[key], source[key]); } else if (source[key] !== void 0) { target[key] = source[key]; } } } } /** * The error for plugin conflicts. */ class PluginConflictError extends Error { /** * Initialize this error object. * @param {string} pluginId The plugin ID. * @param {{filePath:string, importerName:string}[]} plugins The resolved plugins. */ constructor(pluginId, plugins) { super(`Plugin "${pluginId}" was conflicted between ${plugins.map(p => `"${p.importerName}"`).join(" and ")}.`); this.messageTemplate = "plugin-conflict"; this.messageData = { pluginId, plugins }; } } /** * Merge plugins. * `target`'s definition is prior to `source`'s. * @param {Record} target The destination to merge * @param {Record|undefined} source The source to merge. * @returns {void} */ function mergePlugins(target, source) { if (!isNonNullObject(source)) { return; } for (const key of Object.keys(source)) { if (key === "__proto__") { continue; } const targetValue = target[key]; const sourceValue = source[key]; // Adopt the plugin which was found at first. if (targetValue === void 0) { if (sourceValue.error) { throw sourceValue.error; } target[key] = sourceValue; } else if (sourceValue.filePath !== targetValue.filePath) { throw new PluginConflictError(key, [ { filePath: targetValue.filePath, importerName: targetValue.importerName }, { filePath: sourceValue.filePath, importerName: sourceValue.importerName } ]); } } } /** * Merge rule configs. * `target`'s definition is prior to `source`'s. * @param {Record} target The destination to merge * @param {Record|undefined} source The source to merge. * @returns {void} */ function mergeRuleConfigs(target, source) { if (!isNonNullObject(source)) { return; } for (const key of Object.keys(source)) { if (key === "__proto__") { continue; } const targetDef = target[key]; const sourceDef = source[key]; // Adopt the rule config which was found at first. if (targetDef === void 0) { if (Array.isArray(sourceDef)) { target[key] = [...sourceDef]; } else { target[key] = [sourceDef]; } /* * If the first found rule config is severity only and the current rule * config has options, merge the severity and the options. */ } else if ( targetDef.length === 1 && Array.isArray(sourceDef) && sourceDef.length >= 2 ) { targetDef.push(...sourceDef.slice(1)); } } } /** * Create the extracted config. * @param {ConfigArray} instance The config elements. * @param {number[]} indices The indices to use. * @returns {ExtractedConfig} The extracted config. */ function createConfig(instance, indices) { const config = new ExtractedConfig(); const ignorePatterns = []; // Merge elements. for (const index of indices) { const element = instance[index]; // Adopt the parser which was found at first. if (!config.parser && element.parser) { if (element.parser.error) { throw element.parser.error; } config.parser = element.parser; } // Adopt the processor which was found at first. if (!config.processor && element.processor) { config.processor = element.processor; } // Adopt the noInlineConfig which was found at first. if (config.noInlineConfig === void 0 && element.noInlineConfig !== void 0) { config.noInlineConfig = element.noInlineConfig; config.configNameOfNoInlineConfig = element.name; } // Adopt the reportUnusedDisableDirectives which was found at first. if (config.reportUnusedDisableDirectives === void 0 && element.reportUnusedDisableDirectives !== void 0) { config.reportUnusedDisableDirectives = element.reportUnusedDisableDirectives; } // Collect ignorePatterns if (element.ignorePattern) { ignorePatterns.push(element.ignorePattern); } // Merge others. mergeWithoutOverwrite(config.env, element.env); mergeWithoutOverwrite(config.globals, element.globals); mergeWithoutOverwrite(config.parserOptions, element.parserOptions); mergeWithoutOverwrite(config.settings, element.settings); mergePlugins(config.plugins, element.plugins); mergeRuleConfigs(config.rules, element.rules); } // Create the predicate function for ignore patterns. if (ignorePatterns.length > 0) { config.ignores = IgnorePattern.createIgnore(ignorePatterns.reverse()); } return config; } /** * Collect definitions. * @template T, U * @param {string} pluginId The plugin ID for prefix. * @param {Record} defs The definitions to collect. * @param {Map} map The map to output. * @param {function(T): U} [normalize] The normalize function for each value. * @returns {void} */ function collect(pluginId, defs, map, normalize) { if (defs) { const prefix = pluginId && `${pluginId}/`; for (const [key, value] of Object.entries(defs)) { map.set( `${prefix}${key}`, normalize ? normalize(value) : value ); } } } /** * Normalize a rule definition. * @param {Function|Rule} rule The rule definition to normalize. * @returns {Rule} The normalized rule definition. */ function normalizePluginRule(rule) { return typeof rule === "function" ? { create: rule } : rule; } /** * Delete the mutation methods from a given map. * @param {Map} map The map object to delete. * @returns {void} */ function deleteMutationMethods(map) { Object.defineProperties(map, { clear: { configurable: true, value: void 0 }, delete: { configurable: true, value: void 0 }, set: { configurable: true, value: void 0 } }); } /** * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array. * @param {ConfigArrayElement[]} elements The config elements. * @param {ConfigArrayInternalSlots} slots The internal slots. * @returns {void} */ function initPluginMemberMaps(elements, slots) { const processed = new Set(); slots.envMap = new Map(); slots.processorMap = new Map(); slots.ruleMap = new Map(); for (const element of elements) { if (!element.plugins) { continue; } for (const [pluginId, value] of Object.entries(element.plugins)) { const plugin = value.definition; if (!plugin || processed.has(pluginId)) { continue; } processed.add(pluginId); collect(pluginId, plugin.environments, slots.envMap); collect(pluginId, plugin.processors, slots.processorMap); collect(pluginId, plugin.rules, slots.ruleMap, normalizePluginRule); } } dele