textlint
Version:
The pluggable linting tool for text and markdown.
351 lines • 16.2 kB
JavaScript
// LICENSE : MIT
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Config = void 0;
const crypto_1 = __importDefault(require("crypto"));
const config_loader_1 = require("./config/config-loader");
const preset_loader_1 = require("./config/preset-loader");
const plugin_loader_1 = require("./config/plugin-loader");
const textlint_module_resolver_1 = require("./engine/textlint-module-resolver");
const separate_by_config_option_1 = require("./config/separate-by-config-option");
const utils_1 = require("@textlint/utils");
const logger_1 = require("../util/logger");
// @ts-expect-error no types. it will be removed
const md5_1 = __importDefault(require("md5"));
const fs_1 = __importDefault(require("fs"));
const assert_1 = __importDefault(require("assert"));
// @ts-expect-error no types. it will be removed
const unique_concat_1 = __importDefault(require("unique-concat"));
const path_1 = __importDefault(require("path"));
const read_pkg_up_1 = __importDefault(require("read-pkg-up"));
function applyNormalizerToList(normalizer, names) {
return names.map((name) => {
return normalizer(name);
});
}
function applyNormalizerToConfig(normalizer, config) {
return Object.keys(config).reduce((results, key) => {
const shortPluginName = normalizer(key);
results[shortPluginName] = config[key];
return results;
}, {});
}
/**
* @type {TextlintConfig}
*/
const defaultOptions = Object.freeze({
// rule package names
rules: [],
// disabled rule package names
// always should start with empty
disabledRules: [],
// rules config object
rulesConfig: {},
// filter rule package names
filterRules: [],
disabledFilterRules: [],
// rules config object
filterRulesConfig: {},
// preset package names
// e.g.) ["preset-foo"]
presets: [],
// plugin package names
plugins: [],
// plugin config
pluginsConfig: {},
// base directory for loading {rule, config, plugin} modules
rulesBaseDirectory: undefined,
// ".textlint" file path
configFile: undefined,
// rule directories
rulePaths: [],
// formatter file name
// e.g.) stylish.js => set "stylish"
// NOTE: default formatter is defined in Engine,
// because There is difference between TextLintEngine and TextFixEngine.
formatterName: undefined,
// --quiet
quiet: false,
// --no-color
color: true,
// --no-textlintrc
textlintrc: true,
// --cache : enable or disable
cache: false,
// --cache-location: cache file path
cacheLocation: path_1.default.resolve(process.cwd(), ".textlintcache"),
// --ignore-path: ".textlintignore" file path
ignoreFile: path_1.default.resolve(process.cwd(), ".textlintignore")
});
// Priority: CLI > Code options > config file
class Config {
/**
* @return {string} rc config filename
* it's name use as `.<name>rc`
*/
static get CONFIG_FILE_NAME() {
return "textlint";
}
/**
* Create config object form command line options
* See options.js
* @param {object} cliOptions the options is command line option object. @see options.js
* @returns {Config}
*/
static initWithCLIOptions(cliOptions) {
const options = {};
options.rules = cliOptions.rule ? cliOptions.rule : defaultOptions.rules;
// TODO: CLI --filter <rule>?
options.filterRules = defaultOptions.filterRules;
options.disabledFilterRules = defaultOptions.disabledFilterRules;
// TODO: CLI --disable <rule>?
options.disabledRules = defaultOptions.disabledRules;
options.presets = cliOptions.preset ? cliOptions.preset : defaultOptions.presets;
options.plugins = cliOptions.plugin ? cliOptions.plugin : defaultOptions.plugins;
options.configFile = cliOptions.config ? cliOptions.config : defaultOptions.configFile;
options.rulePaths = cliOptions.rulesdir ? cliOptions.rulesdir : defaultOptions.rulePaths;
options.formatterName = cliOptions.format ? cliOptions.format : defaultOptions.formatterName;
options.quiet = cliOptions.quiet !== undefined ? cliOptions.quiet : defaultOptions.quiet;
options.color = cliOptions.color !== undefined ? cliOptions.color : defaultOptions.color;
// --no-textlintrc: disable textlint
options.textlintrc = cliOptions.textlintrc !== undefined ? cliOptions.textlintrc : defaultOptions.textlintrc;
// --cache
options.cache = cliOptions.cache !== undefined ? cliOptions.cache : defaultOptions.cache;
// --cache-location="path/to/file"
options.cacheLocation =
cliOptions.cacheLocation !== undefined
? path_1.default.resolve(process.cwd(), cliOptions.cacheLocation)
: defaultOptions.cacheLocation;
// --rules-base-directory "other/node_modules"
options.rulesBaseDirectory = cliOptions.rulesBaseDirectory || defaultOptions.rulesBaseDirectory;
// --ignore-path="path/to/file"
options.ignoreFile =
cliOptions.ignorePath !== undefined
? path_1.default.resolve(process.cwd(), cliOptions.ignorePath)
: defaultOptions.ignoreFile;
return this.initWithAutoLoading(options);
}
/* eslint-disable complexity */
/**
* load config and merge options.
* These config is user defined options.
* These config is prefer than preset packages's config that is defined by package author.
* @param options
*/
static initWithAutoLoading(options = {}) {
// Base directory
const rulesBaseDirectory = options.rulesBaseDirectory
? options.rulesBaseDirectory
: defaultOptions.rulesBaseDirectory;
// Create resolver
const moduleResolver = new textlint_module_resolver_1.TextLintModuleResolver({
rulesBaseDirectory
});
// => ConfigFile
// configFile is optional
// => load .textlintrc
const loadedResult = typeof options.textlintrc === "undefined" || options.textlintrc
? (0, config_loader_1.loadConfig)({
configFileName: this.CONFIG_FILE_NAME,
configFilePath: options.configFile,
moduleResolver,
cwd: options.cwd
})
: {
config: {},
filePath: undefined
};
const configFileRaw = loadedResult.config;
const configFilePath = loadedResult.filePath;
// => Load options from .textlintrc
const configRuleNamesObject = (0, separate_by_config_option_1.separateEnabledOrDisabled)(configFileRaw.rules);
const configFilterRuleNamesObject = (0, separate_by_config_option_1.separateEnabledOrDisabled)(configFileRaw.filters);
const configPresetNames = configRuleNamesObject.presetNames;
const configFilePlugins = (0, plugin_loader_1.getPluginNames)(configFileRaw);
const configFilePluginConfig = (0, plugin_loader_1.getPluginConfig)(configFileRaw);
// Notes: vs. loadRulesConfigFromPresets
// loadRulesConfigFromPresets load rules config from **preset package**. (It is not user defined config. It is defined by package author)
// In other hands, this line load rules config from .textlintrc. (It is user defined config)
const configFileRulesConfig = (0, preset_loader_1.createFlatRulesConfigFromRawRulesConfig)(configFileRaw.rules);
const configFileFilterRulesConfig = (0, preset_loader_1.createFlatRulesConfigFromRawRulesConfig)(configFileRaw.filters);
// => User specified Options
const optionRules = options.rules || [];
const optionFilterRules = options.filterRules || [];
const optionDisabledRules = options.disabledRules || [];
const optionDisabledFilterRules = options.disabledFilterRules || [];
const optionRulesConfig = options.rulesConfig || {};
const optionFilterRulesConfig = options.filterRulesConfig || {};
const optionPlugins = options.plugins || [];
const optionPresets = options.presets || [];
const optionPluginsConfig = options.pluginsConfig || {};
// => Merge options and configFileOptions
// Priority options > configFile
const rules = (0, unique_concat_1.default)(optionRules, configRuleNamesObject.enabledRuleNames);
const disabledRules = (0, unique_concat_1.default)(optionDisabledRules, configRuleNamesObject.disabledRuleNames);
const filterRules = (0, unique_concat_1.default)(optionFilterRules, configFilterRuleNamesObject.enabledRuleNames);
const disabledFilterRules = (0, unique_concat_1.default)(optionDisabledFilterRules, configFilterRuleNamesObject.disabledRuleNames);
const rulesConfig = Object.assign({}, configFileRulesConfig, optionRulesConfig);
const filterRulesConfig = Object.assign({}, configFileFilterRulesConfig, optionFilterRulesConfig);
const plugins = (0, unique_concat_1.default)(optionPlugins, configFilePlugins);
const pluginsConfig = Object.assign({}, configFilePluginConfig, optionPluginsConfig);
const presets = (0, unique_concat_1.default)(optionPresets, configPresetNames);
const mergedOptions = Object.assign({}, options, {
rules,
disabledRules,
rulesConfig,
filterRules,
disabledFilterRules,
filterRulesConfig,
plugins,
pluginsConfig,
presets,
configFile: configFilePath
});
return new this(mergedOptions);
}
/**
* Return hash string of the config and textlint version
* @returns {string}
*/
get hash() {
try {
const version = read_pkg_up_1.default.sync({ cwd: __dirname }).pkg.version;
const toString = JSON.stringify(this.toJSON());
return (0, md5_1.default)(`${version}-${toString}`);
}
catch (error) {
// Fallback for some env
// https://github.com/textlint/textlint/issues/597
logger_1.Logger.warn("Use random value as hash because calculating hash value throw error", error);
return crypto_1.default.randomBytes(20).toString("hex");
}
}
/**
* initialize with options.
* @param {TextlintConfig} options the option object is defined as TextlintConfig.
* @returns {Config}
* @constructor
*/
constructor(options = {}) {
/**
* @type {string|undefined} absolute path to .textlintrc file.
* - If using .textlintrc, return path to .textlintrc
* - If using npm config module, return path to main file of the module
* - If not using config file, return undefined
*/
this.configFile = options.configFile;
if (this.configFile) {
assert_1.default.ok(path_1.default.isAbsolute(this.configFile), `configFile should be absolute path: ${this.configFile}`);
}
this.rulesBaseDirectory = options.rulesBaseDirectory
? options.rulesBaseDirectory
: defaultOptions.rulesBaseDirectory;
// rule names that are defined in ,textlintrc
const moduleResolver = new textlint_module_resolver_1.TextLintModuleResolver({
rulesBaseDirectory: this.rulesBaseDirectory
});
/**
* @type {string[]} rule key list
* These rule is set `false` to options
*/
this.disabledRules = applyNormalizerToList(utils_1.normalizeTextlintRuleKey, options.disabledRules ? options.disabledRules : defaultOptions.disabledRules);
/**
* @type {string[]} rule key list
* rules does not includes disabledRules
*/
this.rules = applyNormalizerToList(utils_1.normalizeTextlintRuleKey, options.rules ? options.rules : defaultOptions.rules).filter((ruleName) => {
return !this.disabledRules.includes(ruleName);
});
/**
* @type {string[]} rule key list
* These rule is set `false` to options
*/
this.disabledFilterRules = applyNormalizerToList(utils_1.normalizeTextlintFilterRuleKey, options.disabledFilterRules ? options.disabledFilterRules : defaultOptions.disabledFilterRules);
/**
* @type {string[]} filter rule key list
*/
this.filterRules = applyNormalizerToList(utils_1.normalizeTextlintFilterRuleKey, options.filterRules ? options.filterRules : defaultOptions.filterRules).filter((ruleName) => {
return !this.disabledFilterRules.includes(ruleName);
});
/**
* @type {string[]} preset key list
*/
this.presets = applyNormalizerToList(utils_1.normalizeTextlintRulePresetKey, options.presets ? options.presets : defaultOptions.presets);
// => load plugins
// this.rules has not contain plugin rules
// =====================
this.plugins = applyNormalizerToList(utils_1.normalizeTextlintPluginKey, options.plugins ? options.plugins : defaultOptions.plugins);
this.pluginsConfig = applyNormalizerToConfig(utils_1.normalizeTextlintPluginKey, options.pluginsConfig ? options.pluginsConfig : defaultOptions.pluginsConfig);
// rulesConfig
// load preset package's config and merge it to user defined rules config
// user config > default preset config
const presetRulesConfig = (0, preset_loader_1.loadRulesConfigFromPresets)(this.presets, moduleResolver);
this.rulesConfig = applyNormalizerToConfig(utils_1.normalizeTextlintRuleKey, Object.assign({}, presetRulesConfig, options.rulesConfig));
// filterRulesConfig
this.filterRulesConfig = applyNormalizerToConfig(utils_1.normalizeTextlintFilterRuleKey, options.filterRulesConfig || defaultOptions.filterRulesConfig);
/**
* @type {string[]}
*/
this.rulePaths = options.rulePaths ? options.rulePaths : defaultOptions.rulePaths;
/**
* @type {string}
*/
this.formatterName = options.formatterName ? options.formatterName : defaultOptions.formatterName;
/**
* @type {boolean}
*/
this.quiet = options.quiet !== undefined ? options.quiet : defaultOptions.quiet;
/**
* @type {boolean}
*/
this.color = options.color !== undefined ? options.color : defaultOptions.color;
/**
* @type {boolean}
*/
this.cache = options.cache !== undefined ? options.cache : defaultOptions.cache;
/**
* @type {string}
*/
this.cacheLocation = options.cacheLocation !== undefined ? options.cacheLocation : defaultOptions.cacheLocation;
this._assertCacheLocation(this.cacheLocation);
/**
* @type {string}
*/
this.ignoreFile = options.ignoreFile !== undefined ? options.ignoreFile : defaultOptions.ignoreFile;
}
_assertCacheLocation(locationPath) {
let fileStats;
try {
fileStats = fs_1.default.lstatSync(locationPath);
}
catch (ex) {
fileStats = null;
}
if (!fileStats) {
return;
}
// TODO: --cache-location does not support directory
// We should defined what is default name.
assert_1.default.ok(!fileStats.isDirectory(), "--cache-location doesn't support directory");
}
/* eslint-enable complexity */
toJSON() {
const r = Object.create(null);
Object.keys(this).forEach((key) => {
if (!this.hasOwnProperty(key)) {
return;
}
const value = this[key];
if (value == null) {
return;
}
r[key] = typeof value.toJSON !== "undefined" ? value.toJSON() : value;
});
return r;
}
}
exports.Config = Config;
//# sourceMappingURL=config.js.map