@fimbul/wotan
Version:
Pluggable TypeScript and JavaScript linter
248 lines • 9.86 kB
JavaScript
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
;