eslint-plugin-eslint-config
Version:
ESLint rules for ESLint config files
354 lines (280 loc) • 12.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.tryGetConfigInfo = exports.getConfigInfo = exports.ESLintErrorType = void 0;
var _eslint = _interopRequireDefault(require("eslint"));
var _requireRelative = _interopRequireDefault(require("require-relative"));
var _vm = require("vm");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
const pathToBlankFile = require.resolve('../blank.js');
const getsertCache = (cache, key, fn, _name) => {
const cached = cache.get(key);
if (cached) {
// console.debug(`${_name}: hit`);
return cached;
} // console.debug(`${_name}: miss`);
const fresh = fn();
cache.set(key, fresh);
return fresh;
};
const relativeRequire = name => (0, _requireRelative.default)(name, process.cwd());
relativeRequire.resolve = name => _requireRelative.default.resolve(name, process.cwd());
const compileConfigCodeCache = new Map();
const compileConfigCode = fileCode => {
return getsertCache(compileConfigCodeCache, fileCode, () => {
var _runInNewContext;
return (_runInNewContext = (0, _vm.runInNewContext)(fileCode, {
module: {
exports: {}
},
require: relativeRequire
})) !== null && _runInNewContext !== void 0 ? _runInNewContext : {};
}, 'compileConfigCode');
};
const createCliEngineCache = new Map();
const createCLIEngine = config => {
const extraConfig = {
parserOptions: _objectSpread(_objectSpread({}, config.parserOptions), {}, {
project: require.resolve('../../tsconfig.fake.json'),
projectFolderIgnoreList: []
})
};
if (config.ignorePatterns) {
const patterns = Array.isArray(config.ignorePatterns) ? config.ignorePatterns : [config.ignorePatterns];
extraConfig.ignorePatterns = patterns.filter(pattern => typeof pattern !== 'string');
}
return getsertCache(createCliEngineCache, JSON.stringify(config), () => new _eslint.default.CLIEngine({
useEslintrc: false,
ignorePath: pathToBlankFile,
ignorePattern: ['!node_modules/*'],
cache: false,
envs: ['node'],
baseConfig: _objectSpread(_objectSpread({}, config), extraConfig)
}), 'createCLIEngine');
};
const mergeConfigInfo = (a, b) => ({
deprecatedRules: [...a.deprecatedRules, ...b.deprecatedRules],
unknownRules: [...a.unknownRules, ...b.unknownRules],
errors: [...a.errors, ...b.errors]
});
const ensureArray = (v = []) => Array.isArray(v) ? v : [v];
/**
* Merges two ESLint configs, favoring the second config over the first.
*
* Merging is done at the top level only, and based on explicit named properties,
* so any additional properties on either config will be lost.
*/
const mergeConfigs = (a, b) => {
var _b$parser, _b$processor;
return {
parser: (_b$parser = b.parser) !== null && _b$parser !== void 0 ? _b$parser : a.parser,
parserOptions: _objectSpread(_objectSpread({}, a.parserOptions), b.parserOptions),
processor: (_b$processor = b.processor) !== null && _b$processor !== void 0 ? _b$processor : a.processor,
env: _objectSpread(_objectSpread({}, a.env), b.env),
globals: _objectSpread(_objectSpread({}, a.globals), b.globals),
plugins: [...ensureArray(a.plugins), ...(Array.isArray(b.plugins) ? b.plugins : [])],
extends: [...ensureArray(a.extends), ...ensureArray(b.extends)],
settings: _objectSpread(_objectSpread({}, a.settings), b.settings),
overrides: [...ensureArray(a.overrides), ...ensureArray(b.overrides)],
rules: _objectSpread(_objectSpread({}, a.rules), b.rules)
};
};
const extractRelevantConfigs = config => {
var _config$overrides;
return [_objectSpread(_objectSpread({}, config), {}, {
overrides: []
}), ...((_config$overrides = config.overrides) !== null && _config$overrides !== void 0 ? _config$overrides : []).map(override => extractRelevantConfigs(mergeConfigs(_objectSpread(_objectSpread({}, config), {}, {
overrides: [],
rules: {}
}), override))).reduce((prev, curr) => prev.concat(curr), [])];
};
let ESLintErrorType;
exports.ESLintErrorType = ESLintErrorType;
(function (ESLintErrorType) {
ESLintErrorType["FailedToLoadModule"] = "FailedToLoadModule";
ESLintErrorType["InvalidRuleConfig"] = "InvalidRuleConfig";
ESLintErrorType["ProcessorNotFound"] = "ProcessorNotFound";
ESLintErrorType["FailedToExtend"] = "FailedToExtend";
ESLintErrorType["InvalidConfig"] = "InvalidConfig";
})(ESLintErrorType || (exports.ESLintErrorType = ESLintErrorType = {}));
const tryParseAsFailedToLoadModuleError = error => {
var _$exec;
// noinspection RegExpRedundantEscape
const [, kind, name, path] = (_$exec = /Failed to load ([\0-\u{10FFFF}]+) '([\0-\u{10FFFF}]*)' declared in 'BaseConfig((?:#overrides\[[0-9]*?\])*)': Cannot find module '/iu.exec(error.message.trim())) !== null && _$exec !== void 0 ? _$exec : [];
return kind //
? {
type: ESLintErrorType.FailedToLoadModule,
kind,
name,
path
} : null;
};
const tryParseAsInvalidRuleConfigError = error => {
var _$exec2;
// noinspection RegExpRedundantEscape
const [, path, ruleId, reason] = (_$exec2 = /BaseConfig((?:#overrides\[[0-9]*?\])*):[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+Configuration for rule "([\0-\u{10FFFF}]+)" is invalid:([\0-\u{10FFFF}]+)/iu.exec(error.message.trim())) !== null && _$exec2 !== void 0 ? _$exec2 : [];
return ruleId //
? {
type: ESLintErrorType.InvalidRuleConfig,
reason,
ruleId,
path
} : null;
};
const tryParseAsFailedToExtendError = error => {
var _$exec3;
// noinspection RegExpRedundantEscape
const [, name, path] = (_$exec3 = /Failed to load config "([\0-\u{10FFFF}]+)" to extend from\.\nReferenced from: BaseConfig((?:#overrides\[[0-9]*?\])*)/iu.exec(error.message.trim())) !== null && _$exec3 !== void 0 ? _$exec3 : [];
return name //
? {
type: ESLintErrorType.FailedToExtend,
name,
path
} : null;
};
const tryToParseAsProcessorNotFoundError = error => {
var _$exec4;
const [, name] = (_$exec4 = /ESLint configuration of processor in '(?:[\0-\u{10FFFF}]+)' is invalid: '([\0-\u{10FFFF}]+)' was not found\./iu.exec(error.message.trim())) !== null && _$exec4 !== void 0 ? _$exec4 : [];
return name //
? {
type: ESLintErrorType.ProcessorNotFound,
name
} : null;
};
const tryParseAsInvalidConfigError = error => {
var _$exec5;
const [, reason] = (_$exec5 = /ESLint configuration (?:[\0-\u{10FFFF}]+) is invalid:([\0-\u{10FFFF}]+)/iu.exec(error.message.trim())) !== null && _$exec5 !== void 0 ? _$exec5 : [];
return reason //
? {
type: ESLintErrorType.InvalidConfig,
reason
} : null;
};
const errorParsers = [tryParseAsFailedToLoadModuleError, tryParseAsInvalidRuleConfigError, tryToParseAsProcessorNotFoundError, tryParseAsFailedToExtendError, tryParseAsInvalidConfigError];
const parseESLintError = error => {
for (const errorParser of errorParsers) {
const parsedError = errorParser(error);
if (parsedError) {
return parsedError;
}
}
error.message = `Unable to parse error from ESLint: ${error.message}`;
throw error;
};
const followErrorPathToConfig = (config, error) => {
if (!error.path || !config.overrides) {
return config;
}
const levels = error.path.substr(1).split('#').map(p => parseInt(p.substring(p.indexOf('[') + 1, p.length - 1)));
/* istanbul ignore next */
return levels.reduce((conf, i) => {
var _conf$overrides$i, _conf$overrides;
return (_conf$overrides$i = (_conf$overrides = conf.overrides) === null || _conf$overrides === void 0 ? void 0 : _conf$overrides[i]) !== null && _conf$overrides$i !== void 0 ? _conf$overrides$i : {};
}, config);
};
const tryRemoveErrorPointFromConfig = (config, error) => {
var _configToDeleteFrom$p;
const configToDeleteFrom = followErrorPathToConfig(config, error);
if (error.type === ESLintErrorType.FailedToExtend) {
if (typeof configToDeleteFrom.extends === 'string') {
delete configToDeleteFrom.extends;
return true;
}
configToDeleteFrom.extends = ensureArray(configToDeleteFrom.extends).filter(extend => extend !== error.name);
return true;
}
if (error.type === ESLintErrorType.FailedToLoadModule) {
switch (error.kind) {
case 'plugin':
configToDeleteFrom.plugins = (_configToDeleteFrom$p = configToDeleteFrom.plugins) === null || _configToDeleteFrom$p === void 0 ? void 0 : _configToDeleteFrom$p.filter(plugin => plugin !== error.name);
return true;
case 'parser':
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete configToDeleteFrom[error.kind];
return true;
// no default
}
}
if (error.type === ESLintErrorType.InvalidRuleConfig) {
const {
ruleId
} = error;
/* istanbul ignore if */
if (!(configToDeleteFrom.rules && ruleId in configToDeleteFrom.rules)) {
throw new Error('Cannot delete InvalidRuleConfig error - please report');
} // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete configToDeleteFrom.rules[ruleId];
return true;
}
return false;
};
const collectConfigInfoFromESLint = config => {
const theConfig = _objectSpread({
rules: {}
}, config);
const errors = [];
let counter = 0;
do {
/* istanbul ignore if */
if ((counter += 1) > 20) {
throw new Error('inf. loop detected - please report');
}
try {
const results = createCLIEngine(theConfig).executeOnText('', pathToBlankFile);
return {
deprecatedRules: results.usedDeprecatedRules,
unknownRules: results.results[0].messages.filter(({
message
}) => /Definition for rule .+ was not found\./iu.test(message)).map(({
ruleId
}) => ruleId).filter(ruleId => !!ruleId),
errors
};
} catch (error) {
const eslintError = parseESLintError(error);
errors.push(eslintError);
if (tryRemoveErrorPointFromConfig(theConfig, eslintError)) {
continue;
}
return {
deprecatedRules: [],
unknownRules: [],
errors
};
} // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition,no-constant-condition
} while (true);
};
const collectConfigInfoCache = new Map();
/**
* Collects information about the given `config` using ESLint.
*
* Info about any `overrides` the `config` might have will be collected and
* merged into the returned info object.
*/
const collectConfigInfo = config => {
return getsertCache(collectConfigInfoCache, JSON.stringify(config), () => collectConfigInfoFromESLint(config), 'collectConfigInfo');
};
const getConfigInfoCache = new Map();
const getConfigInfo = configText => {
return getsertCache(getConfigInfoCache, configText, () => {
const config = compileConfigCode(configText);
const topLevelInfo = collectConfigInfo(config);
return extractRelevantConfigs(config).map(collectConfigInfo).map(info => _objectSpread(_objectSpread({}, info), {}, {
errors: info.errors.filter(error => ![ESLintErrorType.InvalidRuleConfig, ESLintErrorType.InvalidConfig].includes(error.type))
})) //
.concat(topLevelInfo).reduce(mergeConfigInfo);
}, 'getConfigInfo');
};
exports.getConfigInfo = getConfigInfo;
const tryGetConfigInfo = configText => {
try {
return getConfigInfo(configText);
} catch (_unused) {
return null;
}
};
exports.tryGetConfigInfo = tryGetConfigInfo;