Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Rule Suggester for Linter to Help When Disabling Rules #772

Merged
merged 5 commits into from
Jun 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions src/cm6/rule-alias-suggester.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {Editor, EditorPosition, EditorSuggest, EditorSuggestContext, EditorSuggestTriggerInfo, TFile} from 'obsidian';
import LinterPlugin from '../main';
import {getDisabledRules, rules} from '../rules';
import {DISABLED_RULES_KEY, getYamlSectionValue} from '../utils/yaml';
import {getTextInLanguage} from '../lang/helpers';

const openingYAMLIndicator = /^---\n/gm;
const disableRulesKeyWithColon = `${DISABLED_RULES_KEY}:`;

export type ruleInfo = {
displayName: string,
name: string,
alias: string
}

// based on tag suggester, see https://github.com/jmilldotdev/obsidian-frontmatter-tag-suggest/blob/d80bcfb64d96d7fcb908deb5f4b0c9c8041c267c/main.ts
export class RuleAliasSuggest extends EditorSuggest<ruleInfo> {
ruleInfo: ruleInfo[];

constructor(public plugin: LinterPlugin) {
super(plugin.app);

const allName = getTextInLanguage('all-rules-option');
this.ruleInfo = [{displayName: allName, name: allName.toLowerCase(), alias: 'all'}];
for (const rule of rules) {
const name = rule.getName();
this.ruleInfo.push({displayName: name, name: name.toLowerCase(), alias: rule.alias});
}
}
inline = false;
onTrigger(cursor: EditorPosition, editor: Editor, _: TFile): EditorSuggestTriggerInfo | null {
const lineContents = editor.getLine(cursor.line).toLowerCase();
const onFrontmatterDisabledRulesLine = lineContents.startsWith(disableRulesKeyWithColon) ||
this.disabledRulesIsEndOfStartOfFileToCursor(editor.getRange({line: 0, ch: 0}, cursor));

if (onFrontmatterDisabledRulesLine) {
this.inline = lineContents.startsWith(disableRulesKeyWithColon);
const sub = editor.getLine(cursor.line).substring(0, cursor.ch);
const match = sub.match(/(\S+)$/)?.first().replaceAll('[', '').replaceAll(']', '');
if (match) {
const matchData = {
end: cursor,
start: {
ch: sub.lastIndexOf(match),
line: cursor.line,
},
query: match,
};
return matchData;
}
}

return null;
}

getSuggestions(context: EditorSuggestContext): ruleInfo[] {
const [disabledRules, allIncluded]= getDisabledRules(context.editor.getValue());
if (allIncluded) {
return [];
}

const query = context.query.toLowerCase();
const suggestions = this.ruleInfo.filter((r: ruleInfo) =>
(r.name.contains(query) || r.alias.contains(query)) && !disabledRules.includes(r.alias),
);

return suggestions;
}

renderSuggestion(suggestion: ruleInfo, el: HTMLElement): void {
el.addClass('mod-complex');

const outer = el.createDiv({cls: 'suggestion-content'});
outer.createDiv({cls: 'suggestion-title'}).setText(`${suggestion.displayName}`);
outer.createDiv({cls: 'suggestion-note'}).setText(`${suggestion.alias}`);
}

selectSuggestion(suggestion: ruleInfo): void {
if (this.context) {
let suggestedValue = suggestion.alias;
if (this.inline) {
suggestedValue = `${suggestedValue},`;
} else {
suggestedValue = `${suggestedValue}\n -`;
}

(this.context.editor as Editor).replaceRange(
`${suggestedValue} `,
this.context.start,
this.context.end,
);
}
}

disabledRulesIsEndOfStartOfFileToCursor(range: string): boolean {
if (!range || !range.length) {
return false;
}

if (range.match(openingYAMLIndicator)?.length != 1) {
return false;
}

const disabledRules = getYamlSectionValue(range + '\n', DISABLED_RULES_KEY)?.trimEnd();
if (disabledRules === null) {
return false;
}

return range.trimEnd().endsWith(disabledRules);
}
}
3 changes: 3 additions & 0 deletions src/lang/locale/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ export default {
'characters-removed': 'Zeichen entfernt',
},

// rule-alias-suggester.ts
'all-rules-option': 'Alle',

// settings.ts
'linter-title': 'Linter',
'empty-search-results-text': 'Keine Einstellungen stimmen mit der Suche überein',
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ export default {
'characters-removed': 'characters removed',
},

// rule-alias-suggester.ts
'all-rules-option': 'All',

// settings.ts
'linter-title': 'Linter',
'empty-search-results-text': 'No settings match search',
Expand Down
1 change: 1 addition & 0 deletions src/lang/locale/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default {
'characters-added': 'Caracteres añadidos',
'characters-removed': 'Caracteres eliminados',
},
'all-rules-option': 'Todo',
'linter-title': 'Linter',
'empty-search-results-text': 'No hay configuración que coincida con la búsqueda',
'warning-text': 'Advertencia',
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/zh-cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ export default {
'characters-removed': '字符已移除',
},

// rule-alias-suggester.ts
'all-rules-option': '全部',

// settings.ts
'linter-title': 'Linter',
'empty-search-results-text': '没有匹配的设置项',
Expand Down
3 changes: 3 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {SettingTab} from './ui/settings';
import {NormalArrayFormats} from './utils/yaml';
import {urlRegex} from './utils/regex';
import {getTextInLanguage, LanguageStringKey, setLanguage} from './lang/helpers';
import {RuleAliasSuggest} from './cm6/rule-alias-suggester';

// https://github.com/liamcain/obsidian-calendar-ui/blob/03ceecbf6d88ef260dadf223ee5e483d98d24ffc/src/localization.ts#L20-L43
const langToMomentLocale = {
Expand Down Expand Up @@ -89,6 +90,8 @@ export default class LinterPlugin extends Plugin {

this.registerEventsAndSaveCallback();

this.registerEditorSuggest(new RuleAliasSuggest(this));

this.settingsTab = new SettingTab(this.app, this);
this.addSettingTab(this.settingsTab);
}
Expand Down
30 changes: 5 additions & 25 deletions src/rules.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {
getYamlSectionValue,
loadYAML,
getExactDisabledRuleValue,
getYAMLText,
QuoteCharacter,
} from './utils/yaml';
import {
Option,
BooleanOption,
} from './option';
import {yamlRegex} from './utils/regex';
import {YAMLException} from 'js-yaml';
import {LinterError} from './linter-error';
import {
Expand Down Expand Up @@ -166,31 +165,12 @@ export const RuleTypeOrder = Object.values(RuleType);
* @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.match(yamlRegex);
if (!yaml) {
const yaml_text = getYAMLText(text);
if (yaml_text === null) {
return [[], false];
}

const yaml_text = yaml[1];
const disabledRulesValue = getYamlSectionValue(yaml_text, 'disabled rules');
if (disabledRulesValue == null) {
return [[], false];
}

let disabledRulesKeyAndValue = disabledRulesValue.includes('\n') ? 'disabled rules:\n' : 'disabled rules: ';
disabledRulesKeyAndValue += disabledRulesValue;

const parsed_yaml = loadYAML(disabledRulesKeyAndValue);
let disabled_rules = (parsed_yaml as { 'disabled rules': string[] | string })[
'disabled rules'
];
if (!disabled_rules) {
return [[], false];
}

if (typeof disabled_rules === 'string') {
disabled_rules = [disabled_rules];
}
const disabled_rules = getExactDisabledRuleValue(yaml_text);

if (disabled_rules.includes('all')) {
return [rules.map((rule) => rule.alias), true];
Expand Down
15 changes: 9 additions & 6 deletions src/rules/remove-yaml-keys.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {Options, RuleType} from '../rules';
import RuleBuilder, {ExampleBuilder, OptionBuilderBase, TextAreaOptionBuilder} from './rule-builder';
import dedent from 'ts-dedent';
import {yamlRegex} from '../utils/regex';
import {removeYamlSection} from '../utils/yaml';
import {getYAMLText, removeYamlSection} from '../utils/yaml';

class RemoveYamlKeysOptions implements Options {
yamlKeysToRemove: string[] = [];
Expand All @@ -22,12 +21,16 @@ export default class RemoveYamlKeys extends RuleBuilder<RemoveYamlKeysOptions> {
}
apply(text: string, options: RemoveYamlKeysOptions): string {
const yamlKeysToRemove: string[] = options.yamlKeysToRemove;
const yaml = text.match(yamlRegex);
if (!yaml || yamlKeysToRemove.length === 0) {
if (yamlKeysToRemove.length === 0) {
return text;
}

let yamlText = yaml[1];
const yaml = getYAMLText(text);
if (yaml === null) {
return text;
}

let yamlText = yaml;
for (const key of yamlKeysToRemove) {
let actualKey = key.trim();
if (actualKey.endsWith(':')) {
Expand All @@ -36,7 +39,7 @@ export default class RemoveYamlKeys extends RuleBuilder<RemoveYamlKeysOptions> {

yamlText = removeYamlSection(yamlText, actualKey);
}
return text.replace(yaml[1], yamlText);
return text.replace(yaml, yamlText);
}
get exampleBuilders(): ExampleBuilder<RemoveYamlKeysOptions>[] {
return [
Expand Down
9 changes: 3 additions & 6 deletions src/rules/yaml-key-sort.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {Options, RuleType} from '../rules';
import RuleBuilder, {BooleanOptionBuilder, DropdownOptionBuilder, ExampleBuilder, OptionBuilderBase, TextAreaOptionBuilder} from './rule-builder';
import dedent from 'ts-dedent';
import {yamlRegex} from '../utils/regex';
import {getYamlSectionValue, loadYAML, removeYamlSection, setYamlSection} from '../utils/yaml';
import {getYAMLText, getYamlSectionValue, loadYAML, removeYamlSection, setYamlSection} from '../utils/yaml';

type YamlSortOrderForOtherKeys = 'None' | 'Ascending Alphabetical' | 'Descending Alphabetical';

Expand Down Expand Up @@ -36,14 +35,12 @@ export default class YamlKeySort extends RuleBuilder<YamlKeySortOptions> {
return YamlKeySortOptions;
}
apply(text: string, options: YamlKeySortOptions): string {
const yaml = text.match(yamlRegex);
if (!yaml) {
const oldYaml = getYAMLText(text);
if (oldYaml === null) {
return text;
}

const oldYaml = yaml[1];
let yamlText = oldYaml;

const priorityAtStartOfYaml: boolean = options.priorityKeysAtStartOfYaml;

const yamlKeys: string[] = options.yamlKeyPrioritySortOrder;
Expand Down
34 changes: 34 additions & 0 deletions src/utils/yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const OBSIDIAN_ALIAS_KEY_SINGULAR = 'alias';
export const OBSIDIAN_ALIAS_KEY_PLURAL = 'aliases';
export const OBSIDIAN_ALIASES_KEYS = [OBSIDIAN_ALIAS_KEY_SINGULAR, OBSIDIAN_ALIAS_KEY_PLURAL];
export const LINTER_ALIASES_HELPER_KEY = 'linter-yaml-title-alias';
export const DISABLED_RULES_KEY = 'disabled rules';

/**
* Adds an empty YAML block to the text if it doesn't already have one.
Expand All @@ -24,6 +25,15 @@ export function initYAML(text: string): string {
return text;
}

export function getYAMLText(text: string): string | null {
const yaml = text.match(yamlRegex);
if (!yaml) {
return null;
}

return yaml[1];
}

export function formatYAML(text: string, func: (text: string) => string): string {
if (!text.match(yamlRegex)) {
return text;
Expand Down Expand Up @@ -407,3 +417,27 @@ function basicEscapeString(value: string, defaultEscapeCharacter: QuoteCharacter
// the line must have a colon with a space
return `${defaultEscapeCharacter}${value}${defaultEscapeCharacter}`;
}

export function getExactDisabledRuleValue(yaml_text: string): string[] {
const disabledRulesValue = getYamlSectionValue(yaml_text, DISABLED_RULES_KEY);
if (disabledRulesValue == null) {
return [];
}

let disabledRulesKeyAndValue = disabledRulesValue.includes('\n') ? `${DISABLED_RULES_KEY}:\n` : `${DISABLED_RULES_KEY}: `;
disabledRulesKeyAndValue += disabledRulesValue;

const parsed_yaml = loadYAML(disabledRulesKeyAndValue);
let disabled_rules = (parsed_yaml as { 'disabled rules': string[] | string })[
'disabled rules'
];
if (!disabled_rules) {
return [];
}

if (typeof disabled_rules === 'string') {
disabled_rules = [disabled_rules];
}

return disabled_rules;
}