UNPKG

@fimbul/wotan

Version:

Pluggable TypeScript and JavaScript linter

248 lines 9.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultConfigurationProvider = exports.CONFIG_FILENAMES = exports.CONFIG_EXTENSIONS = void 0; const tslib_1 = require("tslib"); const inversify_1 = require("inversify"); const ymir_1 = require("@fimbul/ymir"); const cached_file_system_1 = require("../cached-file-system"); const path = require("path"); const json5 = require("json5"); const yaml = require("js-yaml"); const utils_1 = require("../../utils"); const bind_decorator_1 = require("bind-decorator"); exports.CONFIG_EXTENSIONS = ['.yaml', '.yml', '.json5', '.json', '.js']; exports.CONFIG_FILENAMES = exports.CONFIG_EXTENSIONS.map((ext) => '.wotanrc' + ext); let DefaultConfigurationProvider = class DefaultConfigurationProvider { constructor(fs, resolver, builtinResolver, cache) { this.fs = fs; this.resolver = resolver; this.builtinResolver = builtinResolver; this.cache = cache.create(); } find(fileToLint) { return utils_1.resolveCachedResult(this.cache, path.dirname(fileToLint), this.findConfigForDirectory); } findConfigForDirectory(dir) { for (let name of exports.CONFIG_FILENAMES) { name = path.join(dir, name); if (this.fs.isFile(name)) return name; } const parent = path.dirname(dir); return parent === dir ? undefined : utils_1.resolveCachedResult(this.cache, parent, this.findConfigForDirectory); } resolve(name, basedir) { if (name.startsWith('wotan:')) { const fileName = this.builtinResolver.resolveConfig(name.substr('wotan:'.length)); if (!this.fs.isFile(fileName)) throw new Error(`'${name}' is not a valid builtin configuration, try 'wotan:recommended'.`); return fileName; } return this.resolver.resolve(name, basedir, exports.CONFIG_EXTENSIONS, module.paths.slice(utils_1.OFFSET_TO_NODE_MODULES + 2)); } load(filename, context) { return this.parse(this.read(filename), filename, context); } parse(raw, filename, context) { const dirname = path.dirname(filename); const baseConfigs = utils_1.arrayify(raw.extends).map((base) => context.load(base)); let rulesDirectories; let aliases; for (const base of baseConfigs) { if (base.rulesDirectories !== undefined) { if (rulesDirectories === undefined) { rulesDirectories = new Map(Array.from(base.rulesDirectories, (v) => [v[0], v[1].slice()])); } else { extendRulesDirectories(rulesDirectories, base.rulesDirectories); } } if (base.aliases !== undefined) { if (aliases === undefined) { aliases = new Map(base.aliases); } else { for (const [name, alias] of base.aliases) aliases.set(name, alias); } } } if (raw.rulesDirectories) rulesDirectories = mapRulesDirectories(rulesDirectories, raw.rulesDirectories, dirname); if (raw.aliases) aliases = resolveAliases(raw.aliases, aliases, rulesDirectories); return { aliases, filename, rulesDirectories, extends: baseConfigs, overrides: raw.overrides && raw.overrides.map((o, i) => this.mapOverride(o, i, dirname, aliases, rulesDirectories)), rules: raw.rules ? mapRules(raw.rules, aliases, rulesDirectories) : undefined, processor: this.mapProcessor(raw.processor, dirname), exclude: Array.isArray(raw.exclude) ? raw.exclude : raw.exclude ? [raw.exclude] : undefined, settings: raw.settings ? mapSettings(raw.settings) : undefined, }; } read(filename) { switch (path.extname(filename)) { case '.json': case '.json5': return json5.parse(this.fs.readFile(filename)); case '.yaml': case '.yml': return yaml.load(this.fs.readFile(filename)); default: return this.resolver.require(filename, { cache: false }); } } mapOverride(raw, index, basedir, aliases, rulesDirectoryMap) { const files = utils_1.arrayify(raw.files); if (files.length === 0) throw new Error(`Override ${index} does not specify files.`); return { files, rules: raw.rules ? mapRules(raw.rules, aliases, rulesDirectoryMap) : undefined, settings: raw.settings ? mapSettings(raw.settings) : undefined, processor: this.mapProcessor(raw.processor, basedir), }; } mapProcessor(processor, basedir) { return processor && this.resolver.resolve(processor, basedir, undefined, module.paths.slice(utils_1.OFFSET_TO_NODE_MODULES + 2)); } }; tslib_1.__decorate([ bind_decorator_1.default, tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [String]), tslib_1.__metadata("design:returntype", Object) ], DefaultConfigurationProvider.prototype, "findConfigForDirectory", null); DefaultConfigurationProvider = tslib_1.__decorate([ inversify_1.injectable(), tslib_1.__metadata("design:paramtypes", [cached_file_system_1.CachedFileSystem, ymir_1.Resolver, ymir_1.BuiltinResolver, ymir_1.CacheFactory]) ], DefaultConfigurationProvider); exports.DefaultConfigurationProvider = DefaultConfigurationProvider; function mapRulesDirectories(receiver, raw, dirname) { if (receiver === undefined) receiver = new Map(); for (const key of Object.keys(raw)) { const resolved = path.resolve(dirname, raw[key]); const current = receiver.get(key); if (current === undefined) { receiver.set(key, [resolved]); } else { current.unshift(resolved); } } return receiver; } function extendRulesDirectories(receiver, current) { for (const [key, directories] of current) { const prev = receiver.get(key); if (prev !== undefined) { prev.unshift(...directories); } else { receiver.set(key, directories.slice()); } } } function resolveAliases(raw, receiver, rulesDirectoryMap) { const mapped = new Map(); for (const prefix of Object.keys(raw)) { const obj = raw[prefix]; if (!obj) continue; for (const name of Object.keys(obj)) { let config = obj[name]; const fullName = `${prefix}/${name}`; if (!config) { if (receiver) receiver.delete(fullName); continue; } if (typeof config === 'string') { config = { rule: config }; } else if (!config.rule) { throw new Error(`Alias '${fullName}' does not specify a rule.`); } mapped.set(fullName, config); } } if (receiver === undefined) receiver = new Map(); for (const entry of mapped) { let name = entry[0]; let alias = entry[1]; const result = {}; const names = [name]; do { name = alias.rule; if (names.includes(name)) throw new Error(`Circular Alias: ${names.join(' => ')} => ${name}`); if (!('options' in result) && 'options' in alias) result.options = alias.options; names.push(name); alias = mapped.get(name); } while (alias !== undefined); const parent = receiver.get(name); if (parent) { receiver.set(entry[0], { ...parent, ...result }); } else { receiver.set(entry[0], { ...result, ...resolveNameAndDirectories(name, rulesDirectoryMap) }); } } return receiver; } function resolveNameAndDirectories(rule, rulesDirectoryMap) { const slashIndex = rule.lastIndexOf('/'); if (slashIndex === -1) return { rule, rulesDirectories: undefined }; const rulesDirectories = rulesDirectoryMap && rulesDirectoryMap.get(rule.substr(0, slashIndex)); if (rulesDirectories === undefined) throw new Error(`No rulesDirectories specified for '${rule.substr(0, slashIndex)}'.`); return { rule: rule.substr(slashIndex + 1), rulesDirectories }; // tslint:disable-line:object-shorthand-properties-first } function mapRules(raw, aliases, rulesDirectoryMap) { const result = new Map(); for (const ruleName of Object.keys(raw)) { const alias = aliases && aliases.get(ruleName); result.set(ruleName, { ...(alias || resolveNameAndDirectories(ruleName, rulesDirectoryMap)), ...mapRuleConfig(raw[ruleName]) }); } return result; } function mapRuleConfig(value) { if (typeof value === 'string') return { severity: mapRuleSeverity(value) }; if (!value) return {}; const result = {}; if ('severity' in value) result.severity = mapRuleSeverity(value.severity); if ('options' in value) result.options = value.options; return result; } function mapRuleSeverity(severity) { switch (severity) { case 'off': return 'off'; case 'warn': case 'warning': return 'warning'; case 'hint': case 'suggestion': return 'suggestion'; default: return 'error'; } } function mapSettings(settings) { const result = new Map(); for (const key of Object.keys(settings)) result.set(key, settings[key]); return result; } //# sourceMappingURL=configuration-provider.js.map