Skip to content

Commit 145463a

Browse files
authored
Merge pull request #1192 from pjkaufman/master
Handle Rule Conflicts
2 parents 879ed9b + 6c27734 commit 145463a

File tree

12 files changed

+259
-71
lines changed

12 files changed

+259
-71
lines changed

esbuild.config.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const mockedPlugins = [replace({
2525
// update usage of moment from obsidian to the node implementation of moment we have
2626
'import {moment} from \'obsidian\';': 'import moment from \'moment\';',
2727
// remove the use of obsidian in the options to allow for docs.js to run
28-
'import {Setting} from \'obsidian\';': '',
28+
'import {App, Setting, ToggleComponent} from \'obsidian\';': '',
2929
// remove the use of obsidian in settings helper to allow for docs.js to run
3030
'import {App, MarkdownRenderer} from \'obsidian\';': '',
3131
// remove the use of obsidian in the auto-correct files picker to allow for docs.js to run
@@ -38,6 +38,8 @@ const mockedPlugins = [replace({
3838
'import {App, TFile} from \'obsidian\';': '',
3939
// remove the use of obsidian in parse results modal to allow for docs.js to run
4040
'import {Modal, App} from \'obsidian\';': 'class Modal {}',
41+
// remove the use of app from a couple of settings for docs.js to run
42+
'import {App} from \'obsidian\';': '',
4143
},
4244
delimiters: ['', ''],
4345
})];

src/lang/locale/en.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ export default {
111111

112112
'copy-aria-label': 'Copy',
113113

114+
'disabled-other-rule-notice': 'If you enable <code>{NAME_1}</code>, it will disable <code>{NAME_2}</code>. Would you like to proceed?',
115+
'disabled-conflicting-rule-notice': '{NAME_1}, conflicts with {NAME_2}, so it has been turned off. You can switch which setting is off in the settings tab.',
116+
117+
// confirm-rule-disable-modal.ts
118+
'ok': 'Ok',
119+
114120
// parse-results-modal.ts
115121
'parse-results-heading-text': 'Custom Parse Values',
116122
'file-parse-description-text': 'The following is the list of custom replacements found in {FILE}.',

src/main.ts

Lines changed: 111 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -129,69 +129,13 @@ export default class LinterPlugin extends Plugin {
129129
setLogLevel(this.settings.logLevel);
130130
await this.setOrUpdateMomentInstance();
131131

132-
let updateMade = false;
133-
if (!this.settings.settingsConvertedToConfigKeyValues) {
134-
updateMade = await this.moveConfigValuesToKeyBasedFormat();
135-
}
136-
137-
if ('lintOnFileContentChangeDelay' in this.settings) {
138-
this.settings.ruleConfigs['yaml-timestamp']['update-on-file-contents-updated'] = this.settings['lintOnFileContentChangeDelay'];
139-
140-
delete this.settings['lintOnFileContentChangeDelay'];
141-
updateMade = true;
142-
}
143-
144-
// make sure to load the defaults of any missing rules to make sure they do not cause issues on the settings page
145-
for (const rule of rules) {
146-
if (!this.settings.ruleConfigs[rule.alias]) {
147-
this.settings.ruleConfigs[rule.alias] = rule.getDefaultOptions();
148-
}
149-
150-
// remove this after a reasonable amount of time
151-
if (rule.alias == 'space-between-chinese-japanese-or-korean-and-english-or-numbers') {
152-
const defaults = rule.getDefaultOptions();
153-
if (!('english-symbols-punctuation-before' in this.settings.ruleConfigs[rule.alias])) {
154-
this.settings.ruleConfigs[rule.alias]['english-symbols-punctuation-before'] = defaults['english-symbols-punctuation-before'];
155-
updateMade = true;
156-
}
157-
158-
if (!('english-symbols-punctuation-after' in this.settings.ruleConfigs[rule.alias])) {
159-
this.settings.ruleConfigs[rule.alias]['english-symbols-punctuation-after'] = defaults['english-symbols-punctuation-after'];
160-
updateMade = true;
161-
}
162-
} else if (rule.alias == 'yaml-timestamp') {
163-
const defaults = rule.getDefaultOptions();
164-
if ('force-retention-of-create-value' in this.settings.ruleConfigs[rule.alias]) {
165-
if (!('date-created-source-of-truth' in this.settings.ruleConfigs[rule.alias])) {
166-
if (this.settings.ruleConfigs[rule.alias]['force-retention-of-create-value']) {
167-
this.settings.ruleConfigs[rule.alias]['date-created-source-of-truth'] = 'frontmatter';
168-
} else {
169-
this.settings.ruleConfigs[rule.alias]['date-created-source-of-truth'] = defaults['date-created-source-of-truth'];
170-
}
171-
}
172-
173-
delete this.settings.ruleConfigs[rule.alias]['force-retention-of-create-value'];
174-
updateMade = true;
175-
}
176-
177-
if (!('date-modified-source-of-truth' in this.settings.ruleConfigs[rule.alias])) {
178-
this.settings.ruleConfigs[rule.alias]['date-modified-source-of-truth'] = defaults['date-modified-source-of-truth'];
179-
updateMade = true;
180-
}
181-
}
182-
}
183-
184132
this.updatePasteOverrideStatus();
185133
this.updateHasCustomCommandStatus();
186-
187-
if (updateMade) {
188-
await this.saveSettings();
189-
}
190134
}
191135

192136
async saveSettings() {
193137
if (!this.hasLoadedMisspellingFiles) {
194-
await this.loadAutoCorrectFiles();
138+
await this.loadAutoCorrectFiles(false);
195139
}
196140

197141
await this.saveData(this.settings);
@@ -328,7 +272,8 @@ export default class LinterPlugin extends Plugin {
328272
this.eventRefs.push(eventRef);
329273

330274
this.app.workspace.onLayoutReady(async () => {
331-
await this.loadAutoCorrectFiles();
275+
await this.makeSureSettingsFilledInAndCleanupSettings();
276+
await this.loadAutoCorrectFiles(true);
332277
});
333278

334279
// Source for save setting
@@ -375,13 +320,17 @@ export default class LinterPlugin extends Plugin {
375320
}
376321
}
377322

378-
async loadAutoCorrectFiles() {
323+
async loadAutoCorrectFiles(isOnload: boolean) {
379324
const customAutoCorrectSettings = this.settings.ruleConfigs['auto-correct-common-misspellings'];
380325
if (!customAutoCorrectSettings || !customAutoCorrectSettings.enabled) {
381326
return;
382327
}
383328

384329
await downloadMisspellings(this, async (message: string) => {
330+
if (isOnload) {
331+
message = 'Obsidian Linter:\n' + message;
332+
}
333+
385334
new Notice(message);
386335

387336
this.settings.ruleConfigs['auto-correct-common-misspellings'].enabled = false;
@@ -619,6 +568,109 @@ export default class LinterPlugin extends Plugin {
619568
moment.locale(oldLocale);
620569
}
621570

571+
private async makeSureSettingsFilledInAndCleanupSettings() {
572+
let updateMade = false;
573+
574+
// migrate settings over to the new format if they are using the now deprecated format that uses
575+
// actual settings names for the key in the json
576+
if (!this.settings.settingsConvertedToConfigKeyValues) {
577+
updateMade = await this.moveConfigValuesToKeyBasedFormat();
578+
}
579+
580+
// move a recently moved setting to its new location
581+
if ('lintOnFileContentChangeDelay' in this.settings) {
582+
this.settings.ruleConfigs['yaml-timestamp']['update-on-file-contents-updated'] = this.settings['lintOnFileContentChangeDelay'];
583+
584+
delete this.settings['lintOnFileContentChangeDelay'];
585+
updateMade = true;
586+
}
587+
588+
// check for and fix invalid settings
589+
let noticeText = 'Obsidian Linter:';
590+
let conflictingRulePresent = false;
591+
if (this.settings.ruleConfigs['header-increment'] && this.settings.ruleConfigs['header-increment'].enabled && this.settings.ruleConfigs['header-increment']['start-at-h2'] &&
592+
this.settings.ruleConfigs['file-name-heading'] && this.settings.ruleConfigs['file-name-heading'].enabled
593+
) {
594+
this.settings.ruleConfigs['header-increment']['start-at-h2'] = false;
595+
updateMade = true;
596+
conflictingRulePresent = true;
597+
598+
noticeText += '\n' + getTextInLanguage('disabled-conflicting-rule-notice').replace('{NAME_1}', getTextInLanguage('rules.header-increment.start-at-h2.name')).replace('{NAME_2}', getTextInLanguage('rules.file-name-heading.name'));
599+
}
600+
601+
if (this.settings.ruleConfigs['paragraph-blank-lines'] && this.settings.ruleConfigs['paragraph-blank-lines'].enabled &&
602+
this.settings.ruleConfigs['two-spaces-between-lines-with-content'] && this.settings.ruleConfigs['two-spaces-between-lines-with-content'].enabled
603+
) {
604+
this.settings.ruleConfigs['paragraph-blank-lines'].enabled = false;
605+
updateMade = true;
606+
607+
if (conflictingRulePresent) {
608+
noticeText += '\n';
609+
}
610+
conflictingRulePresent = true;
611+
612+
noticeText += '\n' + getTextInLanguage('disabled-conflicting-rule-notice').replace('{NAME_1}', getTextInLanguage('rules.paragraph-blank-lines.name')).replace('{NAME_2}', getTextInLanguage('rules.two-spaces-between-lines-with-content.name'));
613+
}
614+
615+
if (conflictingRulePresent) {
616+
new Notice(noticeText, userClickTimeout);
617+
}
618+
619+
// make sure to load the defaults of any missing rules to make sure they do not cause issues on the settings page
620+
for (const rule of rules) {
621+
const ruleDefaults = rule.getDefaultOptions();
622+
if (!this.settings.ruleConfigs[rule.alias]) {
623+
this.settings.ruleConfigs[rule.alias] = ruleDefaults;
624+
updateMade = true;
625+
continue;
626+
}
627+
628+
// remove this after a reasonable amount of time
629+
if (rule.alias == 'space-between-chinese-japanese-or-korean-and-english-or-numbers') {
630+
if (!('english-symbols-punctuation-before' in this.settings.ruleConfigs[rule.alias])) {
631+
this.settings.ruleConfigs[rule.alias]['english-symbols-punctuation-before'] = ruleDefaults['english-symbols-punctuation-before'];
632+
updateMade = true;
633+
}
634+
635+
if (!('english-symbols-punctuation-after' in this.settings.ruleConfigs[rule.alias])) {
636+
this.settings.ruleConfigs[rule.alias]['english-symbols-punctuation-after'] = ruleDefaults['english-symbols-punctuation-after'];
637+
updateMade = true;
638+
}
639+
} else if (rule.alias == 'yaml-timestamp') {
640+
const defaults = rule.getDefaultOptions();
641+
if ('force-retention-of-create-value' in this.settings.ruleConfigs[rule.alias]) {
642+
if (!('date-created-source-of-truth' in this.settings.ruleConfigs[rule.alias])) {
643+
if (this.settings.ruleConfigs[rule.alias]['force-retention-of-create-value']) {
644+
this.settings.ruleConfigs[rule.alias]['date-created-source-of-truth'] = 'frontmatter';
645+
} else {
646+
this.settings.ruleConfigs[rule.alias]['date-created-source-of-truth'] = defaults['date-created-source-of-truth'];
647+
}
648+
}
649+
650+
delete this.settings.ruleConfigs[rule.alias]['force-retention-of-create-value'];
651+
updateMade = true;
652+
}
653+
654+
if (!('date-modified-source-of-truth' in this.settings.ruleConfigs[rule.alias])) {
655+
this.settings.ruleConfigs[rule.alias]['date-modified-source-of-truth'] = defaults['date-modified-source-of-truth'];
656+
updateMade = true;
657+
}
658+
}
659+
660+
// make sure new/empty settings on a rule that exists get filled in with their default value as well
661+
for (const key of Object.keys(ruleDefaults)) {
662+
if (!Object.hasOwn(this.settings.ruleConfigs[rule.alias], key)) {
663+
this.settings.ruleConfigs[rule.alias][key] = ruleDefaults[key];
664+
updateMade = true;
665+
}
666+
}
667+
}
668+
669+
if (updateMade) {
670+
await this.saveSettings();
671+
}
672+
}
673+
622674
private createDebouncedFileUpdate(): Debouncer<[TFile, Editor], Promise<void>> {
623675
let delay = 5000;
624676
switch (this.settings.ruleConfigs['yaml-timestamp']['update-on-file-contents-updated'] ?? AfterFileChangeLintTimes.Never) {

src/option.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Setting} from 'obsidian';
1+
import {App, Setting, ToggleComponent} from 'obsidian';
22
import {getTextInLanguage, LanguageStringKey} from './lang/helpers';
33
import LinterPlugin from './main';
44
import {hideEl, unhideEl, setElContent} from './ui/helpers';
@@ -63,21 +63,24 @@ export abstract class Option {
6363

6464
export class BooleanOption extends Option {
6565
public defaultValue: boolean;
66+
private toggleComponent: ToggleComponent;
6667

67-
constructor(configKey: string, nameKey: LanguageStringKey, descriptionKey: LanguageStringKey, defaultValue: any, ruleAlias?: string | null, private onChange?: (value: boolean) => void) {
68+
constructor(configKey: string, nameKey: LanguageStringKey, descriptionKey: LanguageStringKey, defaultValue: any, ruleAlias?: string | null, private onChange?: (value: boolean, app: App) => void) {
6869
super(configKey, nameKey, descriptionKey, defaultValue, ruleAlias);
6970
}
7071

7172
public display(containerEl: HTMLElement, settings: LinterSettings, plugin: LinterPlugin): void {
7273
this.setting = new Setting(containerEl)
7374
.addToggle((toggle) => {
75+
this.toggleComponent = toggle;
76+
7477
toggle.setValue(settings.ruleConfigs[this.ruleAlias][this.configKey]);
7578
toggle.onChange((value) => {
7679
this.setOption(value, settings);
7780
plugin.settings = settings;
7881

7982
if (this.onChange) {
80-
this.onChange(value);
83+
this.onChange(value, plugin.app);
8184
}
8285

8386
void plugin.saveSettings();
@@ -86,6 +89,14 @@ export class BooleanOption extends Option {
8689

8790
this.parseNameAndDescriptionAndRemoveSettingBorder();
8891
}
92+
93+
getValue(): boolean {
94+
return this.toggleComponent.getValue();
95+
}
96+
97+
setValue(value: boolean) {
98+
this.toggleComponent.setValue(value);
99+
}
89100
}
90101

91102
export class TextOption extends Option {

src/rules.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {LinterError} from './linter-error';
1111
import {getTextInLanguage, LanguageStringKey} from './lang/helpers';
1212
import {ignoreListOfTypes, IgnoreType} from './utils/ignore-types';
1313
import {LinterSettings} from './settings-data';
14+
import {App} from 'obsidian';
1415

1516
export type Options = { [optionName: string]: any};
1617

@@ -41,6 +42,7 @@ export class Rule {
4142
* @param {Array<Option>} [options=[]] - The options of the rule to be displayed in the documentation
4243
* @param {boolean} [hasSpecialExecutionOrder=false] - The rule has special execution order
4344
* @param {IgnoreType[]} [ignoreTypes=[]] - The types of elements to ignore for the rule
45+
* @param {function(boolean):boolean} [disableConflictingOptions=null] - The function to disable conflicting rules or options when it is enabled
4446
*/
4547
constructor(
4648
private nameKey: LanguageStringKey,
@@ -53,10 +55,15 @@ export class Rule {
5355
public options: Array<Option> = [],
5456
public readonly hasSpecialExecutionOrder: boolean = false,
5557
public readonly ignoreTypes: IgnoreType[] = [],
58+
disableConflictingOptions: (value: boolean, app: App) => void = null,
5659
) {
5760
this.ruleHeading = this.getName().toLowerCase().replaceAll(' ', '-');
5861

59-
options.unshift(new BooleanOption('enabled', this.descriptionKey, '' as LanguageStringKey, false, alias, (value: boolean) => {
62+
options.unshift(new BooleanOption('enabled', this.descriptionKey, '' as LanguageStringKey, false, alias, (value: boolean, app: App) => {
63+
if (value && disableConflictingOptions) {
64+
disableConflictingOptions(value, app);
65+
}
66+
6067
if (options.length > 1) {
6168
for (let i = 1; i < options.length; i++) {
6269
if (value) {

src/rules/file-name-heading.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import {Options, RuleType} from '../rules';
1+
import {Options, rulesDict, RuleType} from '../rules';
22
import RuleBuilder, {ExampleBuilder, OptionBuilderBase} from './rule-builder';
33
import dedent from 'ts-dedent';
44
import {IgnoreTypes} from '../utils/ignore-types';
55
import {insert} from '../utils/strings';
6+
import {App} from 'obsidian';
7+
import {BooleanOption} from '../option';
8+
import {ConfirmRuleDisableModal} from '../ui/modals/confirm-rule-disable-modal';
69

710
class FileNameHeadingOptions implements Options {
811
@RuleBuilder.noSettingControl()
@@ -17,6 +20,19 @@ export default class FileNameHeading extends RuleBuilder<FileNameHeadingOptions>
1720
descriptionKey: 'rules.file-name-heading.description',
1821
type: RuleType.HEADING,
1922
ruleIgnoreTypes: [IgnoreTypes.code, IgnoreTypes.math, IgnoreTypes.yaml, IgnoreTypes.link, IgnoreTypes.wikiLink, IgnoreTypes.tag],
23+
disableConflictingOptions(value: boolean, app: App): void {
24+
const headerIncrementOptions = rulesDict['header-increment'];
25+
const headerIncrementEnableOption = headerIncrementOptions.options[0] as BooleanOption;
26+
const headerIncrementStartAtH2Option = headerIncrementOptions.options[1] as BooleanOption;
27+
if (value && headerIncrementEnableOption.getValue()) {
28+
new ConfirmRuleDisableModal(app, 'rules.file-name-heading.name', 'rules.header-increment.start-at-h2.name', () => {
29+
headerIncrementStartAtH2Option.setValue(false);
30+
},
31+
() => {
32+
(rulesDict['file-name-heading'].options[0] as BooleanOption).setValue(false);
33+
}).open();
34+
}
35+
},
2036
});
2137
}
2238
get OptionsClass(): new () => FileNameHeadingOptions {

src/rules/header-increment.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import {Options, RuleType} from '../rules';
1+
import {Options, rulesDict, RuleType} from '../rules';
22
import RuleBuilder, {BooleanOptionBuilder, ExampleBuilder, OptionBuilderBase} from './rule-builder';
33
import dedent from 'ts-dedent';
44
import {IgnoreTypes} from '../utils/ignore-types';
55
import {allHeadersRegex} from '../utils/regex';
6+
import {BooleanOption} from '../option';
7+
import {ConfirmRuleDisableModal} from '../ui/modals/confirm-rule-disable-modal';
8+
import {App} from 'obsidian';
69

710
class HeaderIncrementOptions implements Options {
811
startAtH2?: boolean = false;
@@ -169,6 +172,18 @@ export default class HeaderIncrement extends RuleBuilder<HeaderIncrementOptions>
169172
nameKey: 'rules.header-increment.start-at-h2.name',
170173
descriptionKey: 'rules.header-increment.start-at-h2.description',
171174
optionsKey: 'startAtH2',
175+
onChange(value: boolean, app: App): void {
176+
const filenameHeadingEnableOption = rulesDict['file-name-heading'].options[0] as BooleanOption;
177+
178+
if (value && filenameHeadingEnableOption.getValue()) {
179+
new ConfirmRuleDisableModal(app, 'rules.header-increment.start-at-h2.name', 'rules.file-name-heading.name', () => {
180+
filenameHeadingEnableOption.setValue(false);
181+
},
182+
() => {
183+
(rulesDict['header-increment'].options[1] as BooleanOption).setValue(false);
184+
}).open();
185+
}
186+
},
172187
}),
173188
];
174189
}

0 commit comments

Comments
 (0)