generated from obsidianmd/obsidian-sample-plugin
-
Notifications
You must be signed in to change notification settings - Fork 82
/
rules.ts
179 lines (150 loc) · 5.58 KB
/
rules.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import {
getExactDisabledRuleValue,
getYAMLText,
} from './utils/yaml';
import {
Option,
BooleanOption,
} from './option';
import {YAMLException} from 'js-yaml';
import {LinterError} from './linter-error';
import {getTextInLanguage, LanguageStringKey} from './lang/helpers';
import {ignoreListOfTypes, IgnoreType} from './utils/ignore-types';
import {LinterSettings} from './settings-data';
export type Options = { [optionName: string]: any};
type ApplyFunction = (text: string, options?: Options) => string;
export enum RuleType {
YAML = 'YAML',
HEADING = 'Heading',
FOOTNOTE = 'Footnote',
CONTENT = 'Content',
SPACING = 'Spacing',
PASTE = 'Paste',
}
/** Class representing a rule */
export class Rule {
private ruleHeading: string;
/**
* Create a rule
* @param {LanguageStringKey} nameKey - The name key of the rule
* @param {LanguageStringKey} descriptionKey - The description key of the rule
* @param {string} settingsKey - The settings key of the rule
* @param {string} alias - The alias of the rule which also is the config key for the rule
* @param {RuleType} type - The type of the rule
* @param {ApplyFunction} applyAfterIgnore - The function to apply the rule once everything has been ignored
* @param {Array<Example>} examples - The examples to be displayed in the documentation
* @param {Array<Option>} [options=[]] - The options of the rule to be displayed in the documentation
* @param {boolean} [hasSpecialExecutionOrder=false] - The rule has special execution order
* @param {IgnoreType[]} [ignoreTypes=[]] - The types of elements to ignore for the rule
*/
constructor(
private nameKey: LanguageStringKey,
private descriptionKey: LanguageStringKey,
public settingsKey: string,
public alias: string,
public type: RuleType,
public applyAfterIgnore: ApplyFunction,
public examples: Array<Example>,
public options: Array<Option> = [],
public readonly hasSpecialExecutionOrder: boolean = false,
public readonly ignoreTypes: IgnoreType[] = [],
) {
this.ruleHeading = this.getName().toLowerCase().replaceAll(' ', '-');
options.unshift(new BooleanOption('enabled', this.descriptionKey, '' as LanguageStringKey, false));
for (const option of options) {
option.ruleAlias = alias;
}
}
public getDefaultOptions() {
const options: { [optionName: string]: any } = {};
for (const option of this.options) {
options[option.configKey] = option.defaultValue;
}
return options;
}
public getOptions(settings: LinterSettings) {
return settings.ruleConfigs[this.settingsKey];
}
public getName(): string {
return getTextInLanguage(this.nameKey);
}
public getDescription(): string {
return getTextInLanguage(this.descriptionKey);
}
public getURL(): string {
return 'https://platers.github.io/obsidian-linter/settings/' + this.type.toLowerCase() + '-rules/#' + this.ruleHeading;
}
public enabledOptionName(): string {
return this.options[0].configKey;
}
public apply(text: string, options?: Options): string {
return ignoreListOfTypes(this.ignoreTypes, text, (textAfterIgnore: string) => {
return this.applyAfterIgnore(textAfterIgnore, options);
});
}
}
/** Class representing an example of a rule */
export class Example {
public description: string;
public options: Options;
public before: string;
public after: string;
/**
* Create an example
* @param {string} description - The description of the example
* @param {string} before - The text before the rule is applied
* @param {string} after - The text after the rule is applied
* @param {object} options - The options of the example
*/
constructor(
description: string,
before: string,
after: string,
options: Options = {},
) {
this.description = description;
this.options = options;
this.before = before;
this.after = after;
}
}
export const RuleTypeOrder = Object.values(RuleType);
/**
* Returns a list of ignored rules in the YAML frontmatter of the text.
* @param {string} text The text to parse
* @return {[string[], boolean]} The list of ignored rules and whether the current file should be ignored entirely
*/
export function getDisabledRules(text: string): [string[], boolean] {
const yaml_text = getYAMLText(text);
if (yaml_text === null) {
return [[], false];
}
const disabled_rules = getExactDisabledRuleValue(yaml_text);
if (disabled_rules.includes('all')) {
return [rules.map((rule) => rule.alias), true];
}
return [disabled_rules, false];
}
export const rules: Rule[] = [];
export const rulesDict = {} as Record<string, Rule>;
export const ruleTypeToRules = new Map<RuleType, Rule[]>;
export function registerRule(rule: Rule): void {
rules.push(rule);
rules.sort((a, b) => (RuleTypeOrder.indexOf(a.type) - RuleTypeOrder.indexOf(b.type)) || (a.settingsKey.localeCompare(b.settingsKey)));
rulesDict[rule.alias] = rule;
if (ruleTypeToRules.has(rule.type)) {
ruleTypeToRules.get(rule.type).push(rule);
} else {
ruleTypeToRules.set(rule.type, [rule]);
}
}
export function wrapLintError(error: Error, ruleName: string) {
let errorMessage: string;
if (error instanceof YAMLException) {
errorMessage = error.toString();
errorMessage = getTextInLanguage('logs.wrapper-yaml-error').replace('{ERROR_MESSAGE}', errorMessage.substring(errorMessage.indexOf(':') + 1));
} else {
errorMessage = getTextInLanguage('logs.wrapper-unknown-error').replace('{ERROR_MESSAGE}', error.message);
}
throw new LinterError(`"${ruleName}" encountered an ${errorMessage}`, error);
}