UNPKG

@textlint/kernel

Version:
176 lines 6.83 kB
// LICENSE : MIT "use strict"; 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