UNPKG

recoder-code

Version:

🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!

1,520 lines (1,331 loc) • 148 kB
'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 <https://github.com/mysticatea> */ const debug$3 = debugOrig__default["default"]("eslintrc:ignore-pattern"); /** @typedef {ReturnType<import("ignore").default>} 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 <https://github.com/mysticatea> */ // 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<string, boolean>} */ this.env = {}; /** * Global variables. * @type {Record<string, GlobalConf>} */ 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<string, DependentPlugin>} */ 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<string, [SeverityConf, ...any[]]>} */ 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 <https://github.com/mysticatea> */ //------------------------------------------------------------------------------ // 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<OverrideTester>|null} criteria The tester for the `files` and `excludedFiles` of this config element. * @property {Record<string, boolean>|undefined} env The environment settings. * @property {Record<string, GlobalConf>|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<string, DependentPlugin>|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<string, RuleConf>|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<string, ExtractedConfig>} cache The cache to extract configs. * @property {ReadonlyMap<string, Environment>|null} envMap The map from environment ID to environment definition. * @property {ReadonlyMap<string, Processor>|null} processorMap The map from processor ID to environment definition. * @property {ReadonlyMap<string, Rule>|null} ruleMap The map from rule ID to rule definition. */ /** @type {WeakMap<ConfigArray, ConfigArrayInternalSlots>} */ 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<string, DependentPlugin>} target The destination to merge * @param {Record<string, DependentPlugin>|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<string, Array>} target The destination to merge * @param {Record<string, RuleConf>|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<string,T>} defs The definitions to collect. * @param {Map<string, U>} 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<any, any>} 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); } } deleteMutationMethods(slots.envMap); deleteMutationMethods(slots.processorMap); deleteMutationMethods(slots.ruleMap); } /** * Create `envMap`, `processorMap`, `ruleMap` with the plugins in the config array. * @param {ConfigArray} instance The config elements. * @returns {ConfigArrayInternalSlots} The extracted config. */ function ensurePluginMemberMaps(instance) { const slots = internalSlotsMap$2.get(instance); if (!slots.ruleMap) { initPluginMemberMaps(instance, slots); } return slots; } //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ /** * The Config Array. * * `ConfigArray` instance contains all settings, parsers, and plugins. * You need to call `ConfigArray#extractConfig(filePath)` method in order to * extract, merge and get only the config data which is related to an arbitrary * file. * @extends {Array<ConfigArrayElement>} */ class ConfigArray extends Array { /** * Get the plugin environments. * The returned map cannot be mutated. * @type {ReadonlyMap<string, Environment>} The plugin environments. */ get pluginEnvironments() { return ensurePluginMemberMaps(this).envMap; } /** * Get the plugin processors. * The returned map cannot be mutated. * @type {ReadonlyMap<string, Processor>} The plugin processors. */ get pluginProcessors() { return ensurePluginMemberMaps(this).processorMap; } /** * Get the plugin rules. * The returned map cannot be mutated. * @returns {ReadonlyMap<string, Rule>} The plugin rules. */ get pluginRules() { return ensurePluginMemberMaps(this).ruleMap; } /** * Check if this config has `root` flag. * @returns {boolean} `true` if this config array is root. */ isRoot() { for (let i = this.length - 1; i >= 0; --i) { const root = this[i].root; if (typeof root === "boolean") { return root; } } return false; } /** * Extract the config data which is related to a given file. * @param {string} filePath The absolute path to the target file. * @returns {ExtractedConfig} The extracted config data. */ extractConfig(filePath) { const { cache } = internalSlotsMap$2.get(this); const indices = getMatchedIndices(this, filePath); const cacheKey = indices.join(","); if (!cache.has(cacheKey)) { cache.set(cacheKey, createConfig(this, indices)); } return cache.get(cacheKey); } /** * Check if a given path is an additional lint target. * @param {string} filePath The absolute path to the target file. * @returns {boolean} `true` if the file is an additional lint target. */ isAdditionalTargetPath(filePath) { for (const { criteria, type } of this) { if ( type === "config" && criteria && !criteria.endsWithWildcard && criteria.test(filePath) ) { return true; } } return false; } } /** * Get the used extracted configs. * CLIEngine will use this method to collect used deprecated rules. * @param {ConfigArray} instance The config array object to get. * @returns {ExtractedConfig[]} The used extracted configs. * @private */ function getUsedExtractedConfigs(instance) { const { cache } = internalSlotsMap$2.get(instance); return Array.from(cache.values()); } /** * @fileoverview `ConfigDependency` class. * * `ConfigDependency` class expresses a loaded parser or plugin. * * If the parser or plugin was loaded successfully, it has `definition` property * and `filePath` property. Otherwise, it has `error` property. * * When `JSON.stringify()` converted a `ConfigDependency` object to a JSON, it * omits `definition` property. * * `ConfigArrayFactory` creates `ConfigDependency` objects when it loads parsers * or plugins. * * @author Toru Nagashima <https://github.com/mysticatea> */ /** * The class is to store parsers or plugins. * This class hides the loaded object from `JSON.stringify()` and `console.log`. * @template T */ class ConfigDependency { /** * Initialize this instance. * @param {Object} data The dependency data. * @param {T} [data.definition] The dependency if the loading succeeded. * @param {T} [data.original] The original, non-normalized dependency if the loading succeeded. * @param {Error} [data.error] The error object if the loading failed. * @param {string} [data.filePath] The actual path to the dependency if the loading succeeded. * @param {string} data.id The ID of this dependency. * @param {string} data.importerName The name of the config file which loads this dependency. * @param {string} data.importerPath The path to the config file which loads this dependency. */ constructor({ definition = null, original = null, error = null, filePath = null, id, importerName, importerPath }) { /** * The loaded dependency if the loading succeeded. * @type {T|null} */ this.definition = definition; /** * The original dependency as loaded directly from disk if the loading succeeded. * @type {T|null} */ this.original = original; /** * The error object if the loading failed. * @type {Error|null} */ this.error = error; /** * The loaded dependency if the loading succeeded. * @type {string|null} */ this.filePath = filePath; /** * The ID of this dependency. * @type {string} */ this.id = id; /** * The name of the config file which loads this dependency. * @type {string} */ this.importerName = importerName; /** * The path to the config file which loads this dependency. * @type {string} */ this.importerPath = importerPath; } // eslint-disable-next-line jsdoc/require-description /** * @returns {Object} a JSON compatible object. */ toJSON() { const obj = this[util__default["default"].inspect.custom](); // Display `error.message` (`Error#message` is unenumerable). if (obj.error instanceof Error) { obj.error = { ...obj.error, message: obj.error.message }; } return obj; } // eslint-disable-next-line jsdoc/require-description /** * @returns {Object} an object to display by `console.log()`. */ [util__default["default"].inspect.custom]() { const { definition: _ignore1, // eslint-disable-line no-unused-vars original: _ignore2, // eslint-disable-line no-unused-vars ...obj } = this; return obj; } } /** * @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> */ const { Minimatch } = minimatch__default["default"]; 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__default["default"].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__default["default"].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__default["default"].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__default["default"].isAbsolute(filePath)) { throw new Error(`'filePath' should be an absolute path, but got ${filePath}.`); } const relativePath = path__default["default"].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__default["default"].inspect.custom]() { return this.toJSON(); } } /** * @fileoverview `ConfigArray` class. * @author Toru Nagashima <https://github.com/mysticatea> */ /** * @fileoverview Config file operations. This file must be usable in the browser, * so no Node-specific code can be here. * @author Nicholas C. Zakas */ //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ const RULE_SEVERITY_STRINGS = ["off", "warn", "error"], RULE_SEVERITY = RULE_SEVERITY_STRINGS.reduce((map, value, index) => { map[value] = index; return map; }, {}), VALID_SEVERITIES = [0, 1, 2, "off", "warn", "error"]; //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ /** * Normalizes the severity value of a rule's configuration to a number * @param {(number|string|[number, ...*]|[string, ...*])} ruleConfig A rule's configuration value, generally * received from the user. A valid config value is either 0, 1, 2, the string "off" (treated the same as 0), * the string "warn" (treated the same as 1), the string "error" (treated the same as 2), or an array * whose first element is one of the above values. Strings are matched case-insensitively. * @returns {(0|1|2)} The numeric severity value if the config value was valid, otherwise 0. */ function getRuleSeverity(ruleConfig) { const severityValue = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig; if (severityValue === 0 || severityValue === 1 || severityValue === 2) { return severityValue; } if (typeof severityValue === "string") { return RULE_SEVERITY[severityValue.toLowerCase()] || 0; } return 0; } /** * Converts old-style severity settings (0, 1, 2) into new-style * severity settings (off, warn, error) for all rules. Assumption is that severity * values have already been validated as correct. * @param {Object} config The config object to normalize. * @returns {void} */ function normalizeToStrings(config) { if (config.rules) { Object.keys(config.rules).forEach(ruleId => { const ruleConfig = config.rules[ruleId]; if (typeof ruleConfig === "number") { config.rules[ruleId] = RULE_SEVERITY_STRINGS[ruleConfig] || RULE_SEVERITY_STRINGS[0]; } else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "number") { ruleConfig[0] = RULE_SEVERITY_STRINGS[ruleConfig[0]] || RULE_SEVERITY_STRINGS[0]; } }); } } /** * Determines if the severity for the given rule configuration represents an error. * @param {int|string|Array} ruleConfig The configuration for an individual rule. * @returns {boolean} True if the rule represents an error, false if not. */ function isErrorSeverity(ruleConfig) { return getRuleSeverity(ruleConfig) === 2; } /** * Checks whether a given config has valid severity or not. * @param {number|string|Array} ruleConfig The configuration for an individual rule. * @returns {boolean} `true` if the configuration has valid severity. */ function isValidSeverity(ruleConfig) { let severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig; if (typeof severity === "string") { severity = severity.toLowerCase(); } return VALID_SEVERITIES.indexOf(severity) !== -1; } /** * Checks whether every rule of a given config has valid severity or not. * @param {Object} config The configuration for rules. * @returns {boolean} `true` if the configuration has valid severity. */ function isEverySeverityValid(config) { return Object.keys(config).every(ruleId => isValidSeverity(config[ruleId])); } /** * Normalizes a value for a global in a config * @param {(boolean|string|null)} configuredValue The value given for a global in configuration or in * a global directive comment * @returns {("readable"|"writeable"|"off")} The value normalized as a string * @throws Error if global value is invalid */ function normalizeConfigGlobal(configuredValue) { switch (configuredValue) { case "off": return "off"; case true: case "true": case "writeable": case "writable": return "writable"; case null: case false: case "false": case "readable": case "readonly": return "readonly"; default: throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`); } } var ConfigOps = { __proto__: null, getRuleSeverity: getRuleSeverity, normalizeToStrings: normalizeToStrings, isErrorSeverity: isErrorSeverity, isValidSeverity: isValidSeverity, isEverySeverityValid: isEverySeverityValid, normalizeConfigGlobal: normalizeConfigGlobal }; /** * @fileoverview Provide the function that emits deprecation warnings. * @author Toru Nagashima <http://github.com/mysticatea> */ //------------------------------------------------------------------------------ // Private //------------------------------------------------------------------------------ // Defitions for deprecation warnings. const deprecationWarningMessages = { ESLINT_LEGACY_ECMAFEATURES: "The 'ecmaFeatures' config file property is deprecated and has no effect.", ESLINT_PERSONAL_CONFIG_LOAD: "'~/.eslintrc.*' config files have been deprecated. " + "Please use a config file per project or the '--config' option.", ESLINT_PERSONAL_CONFIG_SUPPRESS: "'~/.eslintrc.*' config files have been deprecated. " + "Please remove it or add 'root:true' to the config files in your " + "projects in order to avoid loading '~/.eslintrc.*' accidentally." }; const sourceFileErrorCache = new Set(); /** * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted * for each unique file path, but repeated invocations with the same file path have no effect. * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active. * @param {string} source The name of the configuration source to report the warning for. * @param {string} errorCode The warning message to show. * @returns {void} */ function emitDeprecationWarning(source, errorCode) { const cacheKey = JSON.stringify({ source, errorCode }); if (sourceFileErrorCache.has(cacheKey)) { return; } sourceFileErrorCache.add(cacheKey); const rel = path__default["default"].relative(process.cwd(), source); const message = deprecationWarningMessages[errorCode]; process.emitWarning( `${message} (found in "${rel}")`, "DeprecationWarning", errorCode ); } /** * @fileoverview The instance of Ajv validator. * @author Evgeny Poberezkin */ //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- /* * Copied from ajv/lib/refs/json-schema-draft-04.json * The MIT License (MIT) * Copyright (c) 2015-2017 Evgeny Poberezkin */ const metaSchema = { id: "http://json-schema.org/draft-04/schema#", $schema: "http://json-schema.org/draft-04/schema#", description: "Core schema meta-schema", definitions: { schemaArray: { type: "array", minItems: 1, items: { $ref: "#" } }, positiveInteger: { type: "integer", minimum: 0 }, positiveIntegerDefault0: { allOf: [{ $ref: "#/definitions/positiveInteger" }, { default: 0 }] }, simpleTypes: { enum: ["array", "boolean", "integer", "null", "number", "object", "string"] }, stringArray: { type: "array", items: { type: "string" }, minItems: 1, uniqueItems: true } }, type: "object", properties: { id: { type: "string" }, $schema: { type: "string" }, title: { type: "string" }, description: { type: "string" }, default: { }, multipleOf: { type: "number", minimum: 0, exclusiveMinimum: true }, maximum: { type: "number" }, exclusiveMaximum: { type: "boolean", default: false }, minimum: { type: "number" }, exclusiveMinimum: { type: "boolean", default: false }, maxLength