1 | /**
|
2 | * @fileoverview Defines a storage for rules.
|
3 | * @author Nicholas C. Zakas
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Requirements
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | const lodash = require("lodash");
|
13 | const ruleReplacements = require("../conf/replacements").rules;
|
14 | const builtInRules = require("./built-in-rules-index");
|
15 |
|
16 | //------------------------------------------------------------------------------
|
17 | // Helpers
|
18 | //------------------------------------------------------------------------------
|
19 |
|
20 | /**
|
21 | * Creates a stub rule that gets used when a rule with a given ID is not found.
|
22 | * @param {string} ruleId The ID of the missing rule
|
23 | * @returns {{create: function(RuleContext): Object}} A rule that reports an error at the first location
|
24 | * in the program. The report has the message `Definition for rule '${ruleId}' was not found` if the rule is unknown,
|
25 | * or `Rule '${ruleId}' was removed and replaced by: ${replacements.join(", ")}` if the rule is known to have been
|
26 | * replaced.
|
27 | */
|
28 | const createMissingRule = lodash.memoize(ruleId => {
|
29 | const message = Object.prototype.hasOwnProperty.call(ruleReplacements, ruleId)
|
30 | ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements[ruleId].join(", ")}`
|
31 | : `Definition for rule '${ruleId}' was not found`;
|
32 |
|
33 | return {
|
34 | create: context => ({
|
35 | Program() {
|
36 | context.report({
|
37 | loc: { line: 1, column: 0 },
|
38 | message
|
39 | });
|
40 | }
|
41 | })
|
42 | };
|
43 | });
|
44 |
|
45 | /**
|
46 | * Normalizes a rule module to the new-style API
|
47 | * @param {(Function|{create: Function})} rule A rule object, which can either be a function
|
48 | * ("old-style") or an object with a `create` method ("new-style")
|
49 | * @returns {{create: Function}} A new-style rule.
|
50 | */
|
51 | function normalizeRule(rule) {
|
52 | return typeof rule === "function" ? Object.assign({ create: rule }, rule) : rule;
|
53 | }
|
54 |
|
55 | //------------------------------------------------------------------------------
|
56 | // Public Interface
|
57 | //------------------------------------------------------------------------------
|
58 |
|
59 | class Rules {
|
60 | constructor() {
|
61 | this._rules = Object.create(null);
|
62 | Object.keys(builtInRules).forEach(ruleId => {
|
63 | this.define(ruleId, builtInRules[ruleId]);
|
64 | });
|
65 | }
|
66 |
|
67 | /**
|
68 | * Registers a rule module for rule id in storage.
|
69 | * @param {string} ruleId Rule id (file name).
|
70 | * @param {Function} ruleModule Rule handler.
|
71 | * @returns {void}
|
72 | */
|
73 | define(ruleId, ruleModule) {
|
74 | this._rules[ruleId] = normalizeRule(ruleModule);
|
75 | }
|
76 |
|
77 | /**
|
78 | * Access rule handler by id (file name).
|
79 | * @param {string} ruleId Rule id (file name).
|
80 | * @returns {{create: Function, schema: JsonSchema[]}}
|
81 | * A rule. This is normalized to always have the new-style shape with a `create` method.
|
82 | */
|
83 | get(ruleId) {
|
84 | if (!Object.prototype.hasOwnProperty.call(this._rules, ruleId)) {
|
85 | return createMissingRule(ruleId);
|
86 | }
|
87 | if (typeof this._rules[ruleId] === "string") {
|
88 | return normalizeRule(require(this._rules[ruleId]));
|
89 | }
|
90 | return this._rules[ruleId];
|
91 |
|
92 | }
|
93 |
|
94 | /**
|
95 | * Get an object with all currently loaded rules
|
96 | * @returns {Map} All loaded rules
|
97 | */
|
98 | getAllLoadedRules() {
|
99 | const allRules = new Map();
|
100 |
|
101 | Object.keys(this._rules).forEach(name => {
|
102 | const rule = this.get(name);
|
103 |
|
104 | allRules.set(name, rule);
|
105 | });
|
106 | return allRules;
|
107 | }
|
108 | }
|
109 |
|
110 | module.exports = Rules;
|