feat: add configurable list indentation rule

This commit is contained in:
xRyul 2026-07-20 13:24:24 +01:00
parent f38e000665
commit b1e76c6516
19 changed files with 446 additions and 23 deletions

View file

@ -14,11 +14,11 @@ If you ever paste something that has many unicodes, Gremlins flag them. To repla
## Features
- Flags invisible or unusual Unicode characters, mixed indentation, and optionally typographic punctuation (curly quotation marks, en dashes, and em dashes). See details below.
- Flags invisible or unusual Unicode characters, mixed indentation, malformed list indentation when enabled, and optionally typographic punctuation (curly quotation marks, en dashes, and em dashes). See details below.
- Shows an icon beside every visible line containing a gremlin (optionally, you can click the icon to fix the character immediatly)
- Works in Source mode and Live Preview.
## The plugin checks three groups of rules.
## The plugin checks four groups of rules.
### 1. Invisible or potentially dangerous Unicode - enabled by default
@ -49,11 +49,19 @@ Bidirectional controls and invisible characters are highlighted because they can
It detects indentation at the beginning of a line containing **both tabs and spaces**.
When fixed, indentation made from tabs followed by stray spaces keeps its tabs and removes the trailing spaces. Other mixed indentation becomes spaces while preserving its visual width, assuming four-column tab stops.
When fixed, indentation made from tabs followed by stray spaces keeps its tabs and removes the trailing spaces. Other mixed indentation becomes spaces while preserving its visual width using Obsidian's current indent visual width.
It does **not** flag indentation made entirely from tabs or entirely from spaces.
### 3. Typographic punctuation - disabled by default
### 3. List indentation - disabled by default
It detects space-indented Markdown list items whose indentation is not a multiple of Obsidian's current **Indent visual width** setting. Unordered, ordered, and task list items are supported; indented prose and code are ignored.
When fixed, indentation is rounded to the nearest level, with ties rounded to the deeper level. For a four-space indent width, one space becomes no indentation, two or three spaces become four, five spaces becomes four, and six or seven spaces become eight.
Fixes preserve the line's indentation style: space-indented list items remain spaces, while tab-led mixed indentation keeps its tabs.
### 4. Typographic punctuation - disabled by default
| Unicode | Character | Optional replacement |
|---|---|---|

View file

@ -1,7 +1,7 @@
{
"id": "gremlins",
"name": "Gremlins",
"version": "0.2.4",
"version": "0.3.0",
"minAppVersion": "1.5.0",
"description": "Reveal invisible Unicode characters and suspicious indentation in notes.",
"author": "xRyul",

47
package-lock.json generated
View file

@ -1,14 +1,15 @@
{
"name": "obsidian-gremlins",
"version": "0.2.4",
"version": "0.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-gremlins",
"version": "0.2.4",
"version": "0.3.0",
"license": "MIT",
"devDependencies": {
"@codemirror/language": "6.12.4",
"@codemirror/state": "6.5.0",
"@codemirror/view": "6.38.6",
"@eslint/js": "9.39.4",
@ -27,6 +28,21 @@
"node": ">=18.18.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.12.4",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz",
"integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
"@lezer/common": "^1.5.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/state": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
@ -771,6 +787,33 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@lezer/common": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz",
"integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@lezer/highlight": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.3.0"
}
},
"node_modules/@lezer/lr": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz",
"integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@marijn/find-cluster-break": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.3.tgz",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-gremlins",
"version": "0.2.4",
"version": "0.3.0",
"description": "Reveal invisible Unicode characters and suspicious indentation in Obsidian notes.",
"main": "main.js",
"type": "module",
@ -28,6 +28,7 @@
"url": "https://github.com/xRyul/gremlins/issues"
},
"devDependencies": {
"@codemirror/language": "6.12.4",
"@codemirror/state": "6.5.0",
"@codemirror/view": "6.38.6",
"@eslint/js": "9.39.4",

View file

@ -2,9 +2,13 @@ import { GREMLIN_DEFINITIONS_BY_CODE_POINT } from './characters.ts';
import type { GremlinsSettings } from './settings-model.ts';
import type { GremlinDefinition, GremlinMatch } from './types.ts';
const DEFAULT_INDENT_SIZE = 4;
const MARKDOWN_LIST_ITEM = /^( +)(?=(?:[-+*]|\d+[.)])(?:[\t ]|$))/;
export function detectGremlins(
text: string,
settings: GremlinsSettings,
indentSize = DEFAULT_INDENT_SIZE,
): GremlinMatch[] {
const matches: GremlinMatch[] = [];
const lines = text.split('\n');
@ -12,7 +16,9 @@ export function detectGremlins(
for (let line = 0; line < lines.length; line += 1) {
const lineText = lines[line] ?? '';
matches.push(...detectLineGremlins(lineText, lineFrom, line, settings));
matches.push(
...detectLineGremlins(lineText, lineFrom, line, settings, indentSize),
);
lineFrom += lineText.length + 1;
}
@ -24,8 +30,11 @@ export function detectLineGremlins(
lineFrom: number,
line: number,
settings: GremlinsSettings,
indentSize = DEFAULT_INDENT_SIZE,
isListItem = true,
): GremlinMatch[] {
const matches: GremlinMatch[] = [];
const effectiveIndentSize = normalizeIndentSize(indentSize);
if (settings.showMixedIndentation) {
const indentation = /^[\t ]+/.exec(text)?.[0];
@ -44,6 +53,26 @@ export function detectLineGremlins(
}
}
if (settings.showListIndentation && isListItem) {
const indentation = MARKDOWN_LIST_ITEM.exec(text)?.[1];
if (
indentation &&
indentation.length % effectiveIndentSize !== 0
) {
matches.push({
codePoint: null,
count: indentation.length,
from: lineFrom,
kind: 'list-indentation',
line,
name: 'list indentation',
severity: 'warning',
to: lineFrom + indentation.length,
zeroWidth: false,
});
}
}
for (let index = 0; index < text.length; ) {
const codePoint = text.codePointAt(index);
if (codePoint === undefined) {
@ -89,3 +118,9 @@ function isDefinitionEnabled(
? settings.showTypographicCharacters
: settings.showDangerousCharacters;
}
function normalizeIndentSize(indentSize: number) {
return Number.isInteger(indentSize) && indentSize > 0
? indentSize
: DEFAULT_INDENT_SIZE;
}

View file

@ -22,6 +22,10 @@ import {
isGremlinFixable,
} from './fix.ts';
import { findGremlinAtPosition } from './match-position.ts';
import {
isMarkdownListItem,
markdownSyntaxTreeChanged,
} from './markdown-context.ts';
import { GREMLIN_ICON_ID } from './gremlin-icon.ts';
import {
formatGremlinTooltip,
@ -94,7 +98,12 @@ export function createGremlinsEditorExtension(
}
update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged) {
if (
update.docChanged ||
update.viewportChanged ||
update.startState.tabSize !== update.state.tabSize ||
markdownSyntaxTreeChanged(update.startState, update.state)
) {
this.refresh(update.view);
}
}
@ -189,6 +198,8 @@ function buildVisibleGremlins(
line.from,
line.number - 1,
settings,
view.state.tabSize,
isMarkdownListItem(view.state, line.text, line.from),
);
matches.push(...lineMatches);
@ -262,6 +273,7 @@ function fixGremlinsOnLine(
lineMatches,
line.text,
line.from,
view.state.tabSize,
);
if (changes.length === 0) {
return true;
@ -315,6 +327,7 @@ function decorationClasses(match: GremlinMatch) {
`gremlins-severity-${match.severity}`,
match.zeroWidth ? 'gremlins-zero-width' : 'gremlins-visible-width',
match.kind === 'mixed-indentation' ? 'gremlins-mixed-indentation' : '',
match.kind === 'list-indentation' ? 'gremlins-list-indentation' : '',
]
.filter(Boolean)
.join(' ');

View file

@ -1,7 +1,7 @@
import { GREMLIN_DEFINITIONS_BY_CODE_POINT } from './characters.ts';
import type { GremlinMatch } from './types.ts';
const MARKDOWN_TAB_SIZE = 4;
const DEFAULT_INDENT_SIZE = 4;
export interface GremlinFixChange {
from: number;
@ -11,7 +11,7 @@ export interface GremlinFixChange {
export function isGremlinFixable(match: GremlinMatch) {
return (
match.kind === 'mixed-indentation' ||
match.kind !== 'character' ||
GREMLIN_DEFINITIONS_BY_CODE_POINT.has(match.codePoint)
);
}
@ -20,8 +20,10 @@ export function buildGremlinFixChanges(
matches: readonly GremlinMatch[],
lineText: string,
lineFrom: number,
indentSize = DEFAULT_INDENT_SIZE,
): GremlinFixChange[] {
const changes: GremlinFixChange[] = [];
const effectiveIndentSize = normalizeIndentSize(indentSize);
for (const match of matches) {
if (match.kind === 'character') {
@ -44,7 +46,10 @@ export function buildGremlinFixChanges(
);
changes.push({
from: match.from,
insert: normalizeMixedIndentation(indentation),
insert:
match.kind === 'mixed-indentation'
? normalizeMixedIndentation(indentation, effectiveIndentSize)
: normalizeListIndentation(indentation, effectiveIndentSize),
to: match.to,
});
}
@ -52,23 +57,41 @@ export function buildGremlinFixChanges(
return changes;
}
function normalizeMixedIndentation(indentation: string) {
function normalizeMixedIndentation(
indentation: string,
indentSize: number,
) {
if (/^\t+ +$/.test(indentation)) {
return indentation.replace(/ +$/, '');
}
return ' '.repeat(indentationWidth(indentation));
return ' '.repeat(indentationWidth(indentation, indentSize));
}
function indentationWidth(indentation: string) {
function normalizeListIndentation(
indentation: string,
indentSize: number,
) {
const width = indentationWidth(indentation, indentSize);
const normalizedWidth = Math.round(width / indentSize) * indentSize;
return ' '.repeat(normalizedWidth);
}
function indentationWidth(indentation: string, indentSize: number) {
let width = 0;
for (const character of indentation) {
width =
character === '\t'
? width + MARKDOWN_TAB_SIZE - (width % MARKDOWN_TAB_SIZE)
? width + indentSize - (width % indentSize)
: width + 1;
}
return width;
}
function normalizeIndentSize(indentSize: number) {
return Number.isInteger(indentSize) && indentSize > 0
? indentSize
: DEFAULT_INDENT_SIZE;
}

View file

@ -1,4 +1,4 @@
import type { Extension } from '@codemirror/state';
import type { EditorState, Extension } from '@codemirror/state';
import {
addIcon,
Notice,
@ -12,6 +12,7 @@ import { createGremlinsEditorExtension } from './editor-extension.ts';
import { buildGremlinFixChanges } from './fix.ts';
import { findGremlinAtPosition } from './match-position.ts';
import { GREMLIN_ICON_ID, GREMLIN_ICON_SVG } from './gremlin-icon.ts';
import { isMarkdownListItem } from './markdown-context.ts';
import { formatGremlinTooltip } from './presentation.ts';
import {
DEFAULT_SETTINGS,
@ -19,6 +20,8 @@ import {
} from './settings-model.ts';
import { GremlinsSettingTab } from './settings-tab.ts';
const DEFAULT_INDENT_SIZE = 4;
export default class GremlinsPlugin extends Plugin {
settings: GremlinsSettings = DEFAULT_SETTINGS;
private readonly editorExtensions: Extension[] = [];
@ -80,13 +83,29 @@ export default class GremlinsPlugin extends Plugin {
const cursor = editor.getCursor();
const lineText = editor.getLine(cursor.line);
const editorState = getEditorState(editor);
const indentSize = getEditorIndentSize(editorState);
const isListItem = editorState
? isMarkdownListItem(
editorState,
lineText,
editor.posToOffset({ ch: 0, line: cursor.line }),
)
: false;
const matches = detectLineGremlins(
lineText,
0,
cursor.line,
this.settings,
indentSize,
isListItem,
);
const changes = buildGremlinFixChanges(
matches,
lineText,
0,
indentSize,
);
const changes = buildGremlinFixChanges(matches, lineText, 0);
if (changes.length === 0) {
return false;
}
@ -110,11 +129,23 @@ export default class GremlinsPlugin extends Plugin {
private inspectGremlinAtCursor(editor: Editor, checking: boolean) {
const cursor = editor.getCursor();
const lineText = editor.getLine(cursor.line);
const editorState = getEditorState(editor);
const indentSize = getEditorIndentSize(editorState);
const isListItem = editorState
? isMarkdownListItem(
editorState,
lineText,
editor.posToOffset({ ch: 0, line: cursor.line }),
)
: false;
const matches = detectLineGremlins(
editor.getLine(cursor.line),
lineText,
0,
cursor.line,
this.settings,
indentSize,
isListItem,
);
const match =
findGremlinAtPosition(matches, cursor.ch, 1) ??
@ -129,3 +160,14 @@ export default class GremlinsPlugin extends Plugin {
return true;
}
}
function getEditorState(editor: Editor) {
return (editor as Editor & { cm?: { state?: EditorState } }).cm?.state ?? null;
}
function getEditorIndentSize(state: EditorState | null) {
const tabSize = state?.tabSize;
return tabSize && Number.isInteger(tabSize)
? tabSize
: DEFAULT_INDENT_SIZE;
}

34
src/markdown-context.ts Normal file
View file

@ -0,0 +1,34 @@
import { syntaxTree } from '@codemirror/language';
import type { EditorState } from '@codemirror/state';
type MarkdownSyntaxNode = ReturnType<typeof syntaxTree>['topNode'];
const LIST_MARKER_NODE = /(?:^|_)formatting-list(?:_|$)/;
export function markdownSyntaxTreeChanged(
startState: EditorState,
state: EditorState,
) {
return syntaxTree(startState) !== syntaxTree(state);
}
export function isMarkdownListItem(
state: EditorState,
lineText: string,
lineFrom: number,
) {
const indentationLength = /^[\t ]*/.exec(lineText)?.[0].length ?? 0;
let node: MarkdownSyntaxNode | null = syntaxTree(state).resolveInner(
lineFrom + indentationLength,
1,
);
while (node) {
if (LIST_MARKER_NODE.test(node.name)) {
return true;
}
node = node.parent;
}
return false;
}

View file

@ -17,6 +17,10 @@ export function formatGremlinTooltip(match: GremlinMatch) {
return `Mixed indentation · Leading indentation contains both tabs and spaces · ${severity}`;
}
if (match.kind === 'list-indentation') {
return `List indentation · ${match.count} leading ${pluralizeSpaces(match.count)} do not match the configured indent width · ${severity}`;
}
const count = match.count > 1 ? `${match.count} ` : '';
const name = `${match.name}${match.count > 1 ? 's' : ''}`;
return `${count}${name} · Unicode ${formatCodePoint(match.codePoint)} · ${severity}`;
@ -32,6 +36,10 @@ export function highestSeverity(
);
}
function pluralizeSpaces(count: number) {
return count === 1 ? 'space' : 'spaces';
}
function capitalize(value: string) {
return value.charAt(0).toUpperCase() + value.slice(1);
}

View file

@ -2,6 +2,7 @@ export interface GremlinsSettings {
enableClickToFix: boolean;
showDangerousCharacters: boolean;
showGutterIcons: boolean;
showListIndentation: boolean;
showMixedIndentation: boolean;
showTypographicCharacters: boolean;
}
@ -10,6 +11,7 @@ export const DEFAULT_SETTINGS: GremlinsSettings = {
enableClickToFix: false,
showDangerousCharacters: true,
showGutterIcons: true,
showListIndentation: false,
showMixedIndentation: true,
showTypographicCharacters: false,
};

View file

@ -52,6 +52,19 @@ export class GremlinsSettingTab extends PluginSettingTab {
),
);
new Setting(containerEl)
.setName('List indentation')
.setDesc(
"Highlight space-indented Markdown list items whose indentation is not a multiple of Obsidian's indent visual width.",
)
.addToggle((toggle) =>
toggle
.setValue(this.controller.settings.showListIndentation)
.onChange((value) =>
this.updateSettings({ showListIndentation: value }),
),
);
new Setting(containerEl)
.setName('Typographic punctuation')
.setDesc(

View file

@ -35,6 +35,19 @@ export interface MixedIndentationGremlinMatch {
zeroWidth: false;
}
export interface ListIndentationGremlinMatch {
codePoint: null;
count: number;
from: number;
kind: 'list-indentation';
line: number;
name: 'list indentation';
severity: 'warning';
to: number;
zeroWidth: false;
}
export type GremlinMatch =
| CharacterGremlinMatch
| ListIndentationGremlinMatch
| MixedIndentationGremlinMatch;

View file

@ -38,7 +38,8 @@
color: var(--color-blue);
}
.gremlins-mixed-indentation {
.gremlins-mixed-indentation,
.gremlins-list-indentation {
background-image: repeating-linear-gradient(
135deg,
transparent 0,

View file

@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import { detectGremlins } from '../src/detect.ts';
import { detectGremlins, detectLineGremlins } from '../src/detect.ts';
import { DEFAULT_SETTINGS } from '../src/settings-model.ts';
describe('detectGremlins', () => {
@ -110,4 +110,98 @@ describe('detectGremlins', () => {
assert.deepEqual(matches, []);
});
it('flags list items indented by one, two, or three spaces when enabled', () => {
const matches = detectGremlins(
' - one\n * two\n 1. three\n - four\n\t- tab',
{
...DEFAULT_SETTINGS,
showListIndentation: true,
},
4,
);
assert.deepEqual(
matches.map(({ count, kind, line }) => ({ count, kind, line })),
[
{ count: 1, kind: 'list-indentation', line: 0 },
{ count: 2, kind: 'list-indentation', line: 1 },
{ count: 3, kind: 'list-indentation', line: 2 },
],
);
});
it('flags invalid indentation at deeper list levels', () => {
const matches = detectGremlins(
' - five\n - six\n - seven\n - eight',
{
...DEFAULT_SETTINGS,
showListIndentation: true,
},
4,
);
assert.deepEqual(
matches.map(({ count, line }) => ({ count, line })),
[
{ count: 5, line: 0 },
{ count: 6, line: 1 },
{ count: 7, line: 2 },
],
);
});
it('uses the current Obsidian indent width for list indentation', () => {
const matches = detectGremlins(
' - one\n - two\n - three\n - four',
{
...DEFAULT_SETTINGS,
showListIndentation: true,
},
2,
);
assert.deepEqual(
matches.map(({ count, line }) => ({ count, line })),
[
{ count: 1, line: 0 },
{ count: 3, line: 2 },
],
);
});
it('does not treat indented non-list content as malformed list indentation', () => {
const matches = detectGremlins(
' prose\n ```ts\n > quote\n code()',
{
...DEFAULT_SETTINGS,
showListIndentation: true,
},
4,
);
assert.deepEqual(matches, []);
});
it('respects the Markdown parser when a list-looking line is not a list', () => {
const matches = detectLineGremlins(
' - literal code',
0,
0,
{
...DEFAULT_SETTINGS,
showListIndentation: true,
},
4,
false,
);
assert.deepEqual(matches, []);
});
it('keeps list-indentation warnings disabled by default', () => {
const matches = detectGremlins(' - nested', DEFAULT_SETTINGS, 4);
assert.deepEqual(matches, []);
});
});

View file

@ -27,6 +27,20 @@ function characterMatch(
};
}
function listIndentationMatch(count: number): GremlinMatch {
return {
codePoint: null,
count,
from: 20,
kind: 'list-indentation',
line: 2,
name: 'list indentation',
severity: 'warning',
to: 20 + count,
zeroWidth: false,
};
}
describe('isGremlinFixable', () => {
it('provides an automatic fix for every defined gremlin character', () => {
for (const definition of GREMLIN_DEFINITIONS) {
@ -37,6 +51,10 @@ describe('isGremlinFixable', () => {
);
}
});
it('provides an automatic fix for malformed list indentation', () => {
assert.equal(isGremlinFixable(listIndentationMatch(2)), true);
});
});
describe('buildGremlinFixChanges', () => {
@ -162,6 +180,59 @@ describe('buildGremlinFixChanges', () => {
]);
});
it('rounds list indentation to the nearest four-space level', () => {
const cases = [
{ indentation: ' ', normalized: '' },
{ indentation: ' ', normalized: ' ' },
{ indentation: ' ', normalized: ' ' },
{ indentation: ' ', normalized: ' ' },
{ indentation: ' ', normalized: ' ' },
{ indentation: ' ', normalized: ' ' },
];
for (const { indentation, normalized } of cases) {
const match = listIndentationMatch(indentation.length);
assert.deepEqual(
buildGremlinFixChanges(
[match],
`${indentation}- Item`,
20,
4,
),
[{ from: 20, insert: normalized, to: 20 + indentation.length }],
);
}
});
it('uses the current indent width while preserving space indentation', () => {
const match = listIndentationMatch(3);
assert.deepEqual(
buildGremlinFixChanges([match], ' - Item', 20, 2),
[{ from: 20, insert: ' ', to: 23 }],
);
});
it('uses the current indent width when measuring mixed indentation', () => {
const match: GremlinMatch = {
codePoint: null,
count: 2,
from: 20,
kind: 'mixed-indentation',
line: 2,
name: 'mixed indentation',
severity: 'warning',
to: 22,
zeroWidth: false,
};
assert.deepEqual(
buildGremlinFixChanges([match], ' \tItem', 20, 2),
[{ from: 20, insert: ' ', to: 22 }],
);
});
it('removes zero-width, joining, and bidi controls', () => {
const line = `a\u200b\u200c\u202eb`;

View file

@ -51,6 +51,23 @@ describe('presentation helpers', () => {
);
});
it('describes malformed list indentation without a Unicode code point', () => {
assert.equal(
formatGremlinTooltip({
codePoint: null,
count: 2,
from: 0,
kind: 'list-indentation',
line: 0,
name: 'list indentation',
severity: 'warning',
to: 2,
zeroWidth: false,
}),
'List indentation · 2 leading spaces do not match the configured indent width · Warning',
);
});
it('selects the highest severity for a gutter marker', () => {
assert.equal(highestSeverity(['info', 'error', 'warning']), 'error');
assert.equal(highestSeverity(['info', 'warning']), 'warning');

View file

@ -7,4 +7,8 @@ describe('gremlins settings', () => {
it('keeps click-to-fix disabled by default', () => {
assert.equal(DEFAULT_SETTINGS.enableClickToFix, false);
});
it('keeps list-indentation warnings disabled by default', () => {
assert.equal(DEFAULT_SETTINGS.showListIndentation, false);
});
});

View file

@ -4,5 +4,6 @@
"0.2.1": "1.5.0",
"0.2.2": "1.5.0",
"0.2.3": "1.5.0",
"0.2.4": "1.5.0"
"0.2.4": "1.5.0",
"0.3.0": "1.5.0"
}