textlint
Version:
The pluggable linting tool for text and markdown.
205 lines (202 loc) • 8.3 kB
JavaScript
// LICENSE : MIT
;
import { EventEmitter } from "events";
import { moduleInterop } from "@textlint/module-interop";
import debug0 from "debug";
import { isPluginRuleKey } from "../config/config-util";
import { loadFromDir } from "../../engine/rule-loader";
import { Logger } from "../../util/logger";
import { TextLintModuleResolver } from "./textlint-module-resolver";
import { TextLintModuleMapper } from "./textlint-module-mapper";
import { normalizeTextlintFilterRuleKey, normalizeTextlintPluginKey, normalizeTextlintRuleKey, normalizeTextlintRulePresetKey } from "@textlint/utils";
import fs from "fs";
const debug = debug0("textlint:module-loader");
const isFile = (filePath) => {
try {
return fs.statSync(filePath).isFile();
}
catch (error) {
return false;
}
};
export class TextLintModuleLoader extends EventEmitter {
static get Event() {
return {
rule: "rule",
filterRule: "filterRule",
plugin: "plugin",
error: "error"
};
}
constructor(config) {
super();
this.moduleResolver = new TextLintModuleResolver(config);
}
/**
* set up lint rules using {@lint Config} object.
* The {@lint Config} object was created with initialized {@link TextLintEngine} (as-known Constructor).
* @param {Config} config the config is parsed object
*/
loadFromConfig(config) {
debug("config %O", config);
// --ruledir
if (config.rulePaths && this.listenerCount(TextLintModuleLoader.Event.rule) > 0) {
// load in additional rules
config.rulePaths.forEach((rulesDir) => {
debug("Loading rules from %o", rulesDir);
const rules = loadFromDir(rulesDir);
Object.keys(rules).forEach((ruleName) => {
const entry = [ruleName, rules[ruleName]];
this.emit(TextLintModuleLoader.Event.rule, entry);
});
});
}
// --rule
if (config.rules && this.listenerCount(TextLintModuleLoader.Event.rule) > 0) {
// load in additional rules
config.rules.forEach((ruleName) => {
this.loadRule(ruleName);
});
}
// TODO: --filter
if (config.filterRules && this.listenerCount(TextLintModuleLoader.Event.filterRule) > 0) {
// load in additional filterRules
config.filterRules.forEach((ruleName) => {
this.loadFilterRule(ruleName);
});
}
// --preset
if (config.presets && this.listenerCount(TextLintModuleLoader.Event.rule) > 0) {
config.presets.forEach((presetName) => {
this.loadPreset(presetName);
});
}
// --plugin
if (config.plugins && this.listenerCount(TextLintModuleLoader.Event.plugin) > 0) {
// load in additional rules from plugin
config.plugins.forEach((pluginName) => {
this.loadPlugin(pluginName);
});
}
}
/**
* load rule from plugin name.
* plugin module has `rules` object and define rule with plugin prefix.
* @param {string} pluginName
*/
loadPlugin(pluginName) {
const pkgPath = this.moduleResolver.resolvePluginPackageName(pluginName);
debug("Loading rules from plugin: %s", pkgPath);
const plugin = moduleInterop(require(pkgPath));
const pluginNameWithoutPrefix = normalizeTextlintPluginKey(pluginName);
// Notes: plugins not support "rules" and "rulesConfig"
// https://github.com/textlint/textlint/issues/291
if (plugin.hasOwnProperty("rules")) {
throw new Error(`textlint plugins not support "rules" and "rulesConfig".
But ${pluginName} has these filed.
For more details, See https://github.com/textlint/textlint/issues/291`);
}
// register plugin.Processor
if (!plugin.hasOwnProperty("Processor")) {
throw new Error(`textlint plugin should have "Processor".
For more details. See https://github.com/textlint/textlint/blob/master/docs/plugin.md`);
}
const pluginEntry = [pluginNameWithoutPrefix, plugin];
this.emit(TextLintModuleLoader.Event.plugin, pluginEntry);
}
loadPreset(presetName) {
/*
Caution: Rules of preset are defined as following.
{
"rules": {
"preset-gizmo": {
"ruleA": false
}
}
It mean that "ruleA" is defined as "preset-gizmo/ruleA"
*/
const presetRuleNameWithoutPrefix = normalizeTextlintRulePresetKey(presetName);
// ignore plugin's rule
if (isPluginRuleKey(presetRuleNameWithoutPrefix)) {
Logger.warn(`${presetRuleNameWithoutPrefix} is Plugin's rule. This is unknown case, please report issue.`);
return;
}
const pkgPath = this.moduleResolver.resolvePresetPackageName(presetName);
debug("Loading rules from preset: %s", pkgPath);
const preset = moduleInterop(require(pkgPath));
const entities = TextLintModuleMapper.createEntities(preset.rules, presetRuleNameWithoutPrefix);
entities.forEach((entry) => {
this.emit(TextLintModuleLoader.Event.rule, entry);
});
}
/**
* load rule file with `ruleName` and define rule.
* if rule is not found, then throw ReferenceError.
* if already rule is loaded, do not anything.
* @param {string} ruleName
*/
loadRule(ruleName) {
/*
Task
- check already define
- resolve package name
- load package
- emit rule
*/
// ruleName is filePath
if (isFile(ruleName)) {
const ruleCreator = moduleInterop(require(ruleName));
const ruleEntry = [ruleName, ruleCreator];
this.emit(TextLintModuleLoader.Event.rule, ruleEntry);
return;
}
// ignore already defined rule
// ignore rules from rulePaths because avoid ReferenceError is that try to require.
const definedRuleName = normalizeTextlintRuleKey(ruleName);
// ignore plugin's rule
if (isPluginRuleKey(definedRuleName)) {
Logger.warn(`${definedRuleName} is Plugin's rule. This is unknown case, please report issue.`);
return;
}
const pkgPath = this.moduleResolver.resolveRulePackageName(ruleName);
debug("Loading rules from %s", pkgPath);
const ruleCreator = moduleInterop(require(pkgPath));
const ruleEntry = [definedRuleName, ruleCreator];
this.emit(TextLintModuleLoader.Event.rule, ruleEntry);
}
/**
* load filter rule file with `ruleName` and define rule.
* if rule is not found, then throw ReferenceError.
* if already rule is loaded, do not anything.
* @param {string} ruleName
*/
loadFilterRule(ruleName) {
/*
Task
- check already define
- resolve package name
- load package
- emit rule
*/
// ignore already defined rule
// ignore rules from rulePaths because avoid ReferenceError is that try to require.
if (isFile(ruleName)) {
const ruleCreator = moduleInterop(require(ruleName));
const ruleEntry = [ruleName, ruleCreator];
this.emit(TextLintModuleLoader.Event.filterRule, ruleEntry);
return;
}
const definedRuleName = normalizeTextlintFilterRuleKey(ruleName);
// ignore plugin's rule
if (isPluginRuleKey(definedRuleName)) {
Logger.warn(`${definedRuleName} is Plugin's rule. This is unknown case, please report issue.`);
return;
}
const pkgPath = this.moduleResolver.resolveFilterRulePackageName(ruleName);
debug("Loading filter rules from %s", pkgPath);
const ruleCreator = moduleInterop(require(pkgPath));
const ruleEntry = [definedRuleName, ruleCreator];
this.emit(TextLintModuleLoader.Event.filterRule, ruleEntry);
}
}
//# sourceMappingURL=textlint-module-loader.js.map