@textlint/kernel
Version:
textlint kernel is core logic by pure JavaScript.
176 lines • 6.83 kB
JavaScript
// LICENSE : MIT
;
import { TextlintRuleErrorImpl } from "../context/TextlintRuleErrorImpl";
import { EventEmitter, PromiseEventEmitter } from "./promise-event-emitter";
import { resolveLocation, resolveFixCommandLocation } from "../core/source-location";
import timing from "../util/timing";
import { invariant } from "../util/invariant";
import MessageType from "../shared/type/MessageType";
import { normalizeTextlintKeyPath } from "@textlint/utils";
import { TextlintRuleContextImpl } from "../context/TextlintRuleContextImpl";
import _debug from "debug";
import { Controller as TraverseController } from "@textlint/ast-traverse";
const traverseController = new TraverseController();
const debug = _debug("textlint:core-task");
class RuleTypeEmitter extends PromiseEventEmitter {
}
/**
* CoreTask receive AST and prepare, traverse AST, emit nodeType event!
* You can observe task and receive "message" event that is TextLintMessage.
*/
export default class TextLintCoreTask extends EventEmitter {
static get events() {
return {
// receive start event
start: "start",
// receive message from each rules
message: "message",
// receive complete event
complete: "complete",
// receive error event
error: "error"
};
}
constructor() {
super();
this.ruleTypeEmitter = new RuleTypeEmitter();
}
createShouldIgnore() {
const shouldIgnore = (args) => {
const { ruleId, range, optional } = args;
invariant(typeof range[0] !== "undefined" && typeof range[1] !== "undefined" && range[0] >= 0 && range[1] >= 0, "ignoreRange should have actual range: " + range);
// FIXME: should have index, loc
// should be compatible with LintReportedMessage?
const message = {
type: MessageType.ignore,
ruleId: ruleId,
range: range,
// ignoring target ruleId - default: filter all messages
// This ruleId should be normalized, because the user can report any value
ignoringRuleId: optional.ruleId ? normalizeTextlintKeyPath(optional.ruleId) : "*"
};
this.emit(TextLintCoreTask.events.message, message);
};
return shouldIgnore;
}
createReporter(sourceCode) {
/**
* push new RuleError to results
* @param {ReportMessage} reportArgs
*/
const reportFunction = (reportArgs) => {
const { ruleId, node, severity, ruleError } = reportArgs;
const { loc, range } = resolveLocation({
source: sourceCode,
ruleId,
node,
ruleError
});
const { fix } = resolveFixCommandLocation({
node,
ruleError
});
debug("%s report %s", ruleId, ruleError);
// add TextLintMessage
const message = {
type: MessageType.lint,
ruleId: ruleId,
message: ruleError.message,
index: range[0],
line: loc.start.line,
column: loc.start.column,
range,
loc,
severity: severity, // it's for compatible ESLint formatter
fix: fix !== undefined ? fix : undefined
};
if (!(ruleError instanceof TextlintRuleErrorImpl)) {
// FIXME: RuleReportedObject should be removed
// `error` is a any data.
const data = ruleError;
message.data = data;
}
this.emit(TextLintCoreTask.events.message, message);
};
return reportFunction;
}
/**
* start process and emitting events.
* You can listen message by `task.on("message", message => {})`
* @param {SourceCode} sourceCode
*/
startTraverser(sourceCode) {
this.emit(TextLintCoreTask.events.start);
const promiseQueue = [];
const ruleTypeEmitter = this.ruleTypeEmitter;
traverseController.traverse(sourceCode.ast, {
enter(node, parent) {
const type = node.type;
Object.defineProperty(node, "parent", { value: parent });
if (ruleTypeEmitter.listenerCount(type) > 0) {
const promise = ruleTypeEmitter.emit(type, node);
promiseQueue.push(promise);
}
},
leave(node) {
const type = `${node.type}:exit`;
if (ruleTypeEmitter.listenerCount(type) > 0) {
const promise = ruleTypeEmitter.emit(type, node);
promiseQueue.push(promise);
}
}
});
Promise.all(promiseQueue)
.then(() => {
this.emit(TextLintCoreTask.events.complete);
})
.catch((error) => {
this.emit(TextLintCoreTask.events.error, error);
});
}
/**
* try to get rule object
*/
tryToGetRuleObject(ruleCreator, ruleContext, ruleOptions) {
try {
return ruleCreator(ruleContext, ruleOptions);
}
catch (error) {
if (error instanceof Error) {
error.message = `Error while loading rule '${ruleContext.id}': ${error.message}`;
}
throw error;
}
}
/**
* try to get filter rule object
*/
tryToGetFilterRuleObject(ruleCreator, ruleContext, ruleOptions) {
try {
return ruleCreator(ruleContext, ruleOptions);
}
catch (error) {
if (error instanceof Error) {
error.message = `Error while loading filter rule '${ruleContext.id}': ${error.message}`;
}
throw error;
}
}
/**
* add all the node types as listeners of the rule
* @param {Function} ruleCreator
* @param {Readonly<RuleContext>|Readonly<FilterRuleContext>} ruleContext
* @param {Object|boolean|undefined} ruleOptions
* @returns {Object}
*/
tryToAddListenRule(ruleCreator, ruleContext, ruleOptions) {
const ruleObject = ruleContext instanceof TextlintRuleContextImpl
? this.tryToGetRuleObject(ruleCreator, ruleContext, ruleOptions)
: this.tryToGetFilterRuleObject(ruleCreator, ruleContext, ruleOptions);
const types = Object.keys(ruleObject);
types.forEach((nodeType) => {
this.ruleTypeEmitter.on(nodeType, timing.enabled ? timing.time(ruleContext.id, ruleObject[nodeType]) : ruleObject[nodeType]);
});
}
}
//# sourceMappingURL=textlint-core-task.js.map