Fix MD018 false positives on valid ATX headings #210

Closed
opened 2026-06-01 22:15:56 +00:00 by steve · 0 comments
Owner

The custom Lapis MD018 replacement currently reports false positives for valid
ATX headings with more than one hash, such as ## Start Here. The regression is
in the shared @lapis-notes/language-service helper, so both browser-worker and
Electron markdown lint paths can surface the incorrect diagnostic.

Problem

The custom rule introduced to exempt lowercase Lapis tags from MD018 uses a
pattern that backtracks on multi-hash headings. As a result, valid headings like
## Start Here can be interpreted as a single # heading followed by text that
starts with #, which incorrectly triggers MD018.

Goal

Restore correct MD018 behavior so valid spaced ATX headings are not reported,
while malformed headings and lowercase tag exemptions continue to behave as
documented.

Scope

  • Fix the shared MD018 replacement logic in
    packages/language-service/src/markdownlint/options.ts
  • Add regression coverage for valid spaced ATX headings in
    packages/language-service/src/__tests__/markdownlint.test.ts
  • Update the markdown-lint spec page to mention that valid multi-level ATX
    headings remain exempt from the replacement rule

Non-goals

  • Changing stock markdownlint rules outside the in-app MD018 replacement
  • Expanding markdownlint configuration support beyond current disabled-rule
    handling
  • Reworking lint tooltip or code-action UX

Acceptance Criteria

  • ## Start Here does not report MD018 through the shared markdownlint helper
  • Other valid spaced ATX headings such as # Intro and ### Deep Dive do
    not report MD018
  • Lowercase Lapis tag exemptions such as #task and #task Buy milk
    continue to avoid MD018
  • Malformed headings such as #Heading, ##Section, and ###foo still
    report MD018
  • Existing MD018 quick-fix behavior remains intact for real violations
  • Focused language-service tests pass after the fix

Implementation Notes

  • Prefer explicit parsing over a backtracking regex so the hash run and the next
    character are interpreted unambiguously
  • Keep the replacement rule aliases and vault-level MD018 disable behavior
    unchanged so browser and Electron providers stay aligned
  • Add at least one regression case that would have failed before the fix

Suggested Files or Specs To Inspect

  • packages/language-service/src/markdownlint/options.ts
  • packages/language-service/src/__tests__/markdownlint.test.ts
  • packages/language-service/src/markdownlint/runtime.ts
  • spec/src/20-packages/plugins/markdown-lint/index.md

Validation Commands

  • pnpm --filter @lapis-notes/language-service test
  • pnpm --filter @lapis-notes/language-service check:all

Follow-up Tasks

Implementation Summary

Fixed the custom MD018 matcher so valid spaced ATX headings no longer false-positive, added regression coverage, and documented the corrected behavior.

The custom Lapis MD018 replacement currently reports false positives for valid ATX headings with more than one hash, such as `## Start Here`. The regression is in the shared `@lapis-notes/language-service` helper, so both browser-worker and Electron markdown lint paths can surface the incorrect diagnostic. ## Problem The custom rule introduced to exempt lowercase Lapis tags from MD018 uses a pattern that backtracks on multi-hash headings. As a result, valid headings like `## Start Here` can be interpreted as a single `#` heading followed by text that starts with `#`, which incorrectly triggers MD018. ## Goal Restore correct MD018 behavior so valid spaced ATX headings are not reported, while malformed headings and lowercase tag exemptions continue to behave as documented. ## Scope - Fix the shared MD018 replacement logic in `packages/language-service/src/markdownlint/options.ts` - Add regression coverage for valid spaced ATX headings in `packages/language-service/src/__tests__/markdownlint.test.ts` - Update the markdown-lint spec page to mention that valid multi-level ATX headings remain exempt from the replacement rule ## Non-goals - Changing stock markdownlint rules outside the in-app MD018 replacement - Expanding markdownlint configuration support beyond current disabled-rule handling - Reworking lint tooltip or code-action UX ## Acceptance Criteria - [ ] `## Start Here` does not report MD018 through the shared markdownlint helper - [ ] Other valid spaced ATX headings such as `# Intro` and `### Deep Dive` do not report MD018 - [ ] Lowercase Lapis tag exemptions such as `#task` and `#task Buy milk` continue to avoid MD018 - [ ] Malformed headings such as `#Heading`, `##Section`, and `###foo` still report MD018 - [ ] Existing MD018 quick-fix behavior remains intact for real violations - [ ] Focused language-service tests pass after the fix ## Implementation Notes - Prefer explicit parsing over a backtracking regex so the hash run and the next character are interpreted unambiguously - Keep the replacement rule aliases and vault-level `MD018` disable behavior unchanged so browser and Electron providers stay aligned - Add at least one regression case that would have failed before the fix ## Suggested Files or Specs To Inspect - `packages/language-service/src/markdownlint/options.ts` - `packages/language-service/src/__tests__/markdownlint.test.ts` - `packages/language-service/src/markdownlint/runtime.ts` - `spec/src/20-packages/plugins/markdown-lint/index.md` ## Validation Commands - `pnpm --filter @lapis-notes/language-service test` - `pnpm --filter @lapis-notes/language-service check:all` ## Related Issues - Follow-up to #161 — [Exempt Lapis tag lines from markdownlint MD018](../closed/161-exempt-lapis-tag-lines-from-markdownlint-md018.md) ## Follow-up Tasks ## Implementation Summary Fixed the custom MD018 matcher so valid spaced ATX headings no longer false-positive, added regression coverage, and documented the corrected behavior. <!-- backlog:task_id=TASK-MD018-VALID-HEADINGS source_spec=spec/src/20-packages/plugins/markdown-lint/index.md related_issue=161 -->
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lapis-notes/lapis#210
No description provided.