Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

markedit-html-validate

A MarkEdit extension that finds raw HTML in a Markdown file which silently breaks rendering, and tells you exactly where the rendered output stops.

The problem it catches

Generated Markdown — security reports, API docs, anything written by a tool — tends to contain angle-bracket placeholders in prose:

Iframe embedding confirmed: <iframe src={iframeSrc}> loads the builder origin directly.

That is not text. Per the CommonMark grammar it is a valid HTML tag, and iframe is a raw-text element: the HTML parser stops recognising markup inside it and keeps going to the end of the file looking for </iframe>. Everything after that line is absorbed as inert fallback content and renders as nothing at all — no error, no warning, no visible gap. In the document that prompted this extension, one such line on line 677 silently discarded the remaining 3,845 lines, 85% of the report.

The editor is no help here, because the source is perfectly valid Markdown. Only the rendered output is destroyed.

What it reports

Rule Severity Effect on the rendered document
unclosed-raw-text-element critical <iframe>, <script>, <style>, <textarea>, <title>, <noscript>, <template>, <xmp> and friends swallow the rest of the file.
unclosed-comment critical A <!-- with no --> absorbs everything after it.
unclosed-code-fence critical The remainder of the file becomes one code block.
unclosed-element error <div>, <details>, <a> … stay open, so the rest of the document is re-parented inside them. Visible, but mis-nested.
stray-closing-tag error A closing tag with no opener. The element you meant to close stays open.
mismatched-nesting error Overlapping rather than nested tags; the renderer drops one.
text-parsed-as-html warning <ownerId>, <victim ref> and similar are read as unknown HTML tags. The placeholder vanishes from the output, leaving a gap where a value should be.

Severities are not guesses. tools/deriveTables.mjs measures them: it renders Intro <tag> tail… for every candidate element, parses the result with a spec-compliant HTML parser, and reports where a marker further down the document actually lands. src/htmlSpec.ts records that outcome.

What it deliberately ignores

False positives would make the tool useless on exactly the documents it is meant for, so tag detection follows CommonMark's raw-HTML grammar exactly, via the same Lezer parser the editor uses:

  • Fenced code blocks and inline code spans are never scanned.
  • {"name":"<a global admin's email>"} is left alone — an apostrophe cannot appear in an attribute name, so this is literal text and renders verbatim.
  • Void elements (<br>, <hr>, <img>) and self-closing tags need no closing tag.
  • Elements whose end tag HTML makes optional (<p>, <li>, <td> …) are not reported.
  • A custom element that is closed somewhere (<my-widget>x</my-widget>) is deliberate; one that never closes (<victim-hid>) is a placeholder.

Install

npm install
npm run install-script   # builds, then copies into MarkEdit's scripts directory

Then restart MarkEdit — user scripts are injected when a document window is created, so a running instance will not pick up the change.

The script is installed to:

~/Library/Containers/app.cyan.markedit/Data/Documents/scripts/markedit-html-validate.js

To uninstall, delete that file and restart.

Use

There are two ways to run a check. Opening a document checks it automatically, and Extensions ▸ HTML Validate ▸ Validate Document checks whatever is in front of you right now — use that after fixing something.

Command Shortcut
Validate Document ⇧⌘H Check now, and always show the summary
Next Problem ⌃⌘H Cycle through findings, worst first
Show Problem List Jump-to menu of every finding
Clear Highlights Remove the underlines

Unlike the automatic check, Validate Document always reports — including a plain "no problems found" — and ignores the maxAutoCheckBytes size limit.

On opening a document

The document is checked immediately. If anything critical is found you get an alert naming the line, what it does, and how much of the document is lost:

This document will not render fully

Line 677: <iframe src={iframeSrc}> is never closed, and iframe content is not
parsed as HTML.

Every line after this point is absorbed into the element and renders as nothing
at all. That is 3,845 of 4,522 lines — about 85% of the document.

Also found: 11 placeholders read as HTML tags.

Errors and warnings do not interrupt you — they are underlined in the editor (red wavy for critical, amber for errors, dotted yellow for warnings).

While you type, underlines refresh 400 ms after you stop. No modal ever appears from typing — only from opening a document, or from asking.

Settings

Add to MarkEdit's settings.json, under your own htmlValidate key:

{
  "htmlValidate": {
    "alertOnOpen": true,
    "validateWhileTyping": true,
    "maxAutoCheckBytes": 4000000
  }
}
  • alertOnOpen — show the modal when a document you open will not render fully. Underlines still appear when off.
  • validateWhileTyping — keep underlines current as you edit.
  • maxAutoCheckBytes — skip the automatic checks above this size. The manual command always runs regardless.

Checking files outside the editor

The same validator runs as a command-line check, so generated Markdown can be caught before it reaches an editor at all. It exits non-zero on any critical or error finding:

node tools/check.mjs report.md
npm run check -- docs/*.md

Development

npm run typecheck      # tsc --noEmit
npm test               # validator unit tests + bundle integration tests
npm run build          # single-file CJS bundle into dist/
npm run derive-tables  # re-measure the element behaviour tables
npm run reload         # quit and relaunch MarkEdit

npm test covers two layers. The unit tests check the validator's findings against independent ground truth: every claim like "this content is invisible" is verified by rendering the Markdown and inspecting the resulting DOM (tools/renderedVisibility.mjs), rather than by trusting the validator's own reasoning. The integration tests load the built bundle exactly as MarkEdit does — as CommonJS with an injected require handing back real CodeMirror modules — and drive the menu actions against a stub host, so decoration ranges are replayed through a real StateField that would throw if they were malformed.

Publishing to the MarkEdit registry

Community extensions are listed at markedit-app.github.io/extensions, fed by the MarkEdit-app/extensions repository. An entry is a single JSON file; registry/markedit-html-validate.json here is ready to submit once the placeholders are filled in.

Review is about provenance and integrity — the source is identifiable, the URL is reachable over HTTPS, the hash matches, and the extension does what it claims. Taste and completeness are not judged.

  1. Push this project to a public GitHub repository, and set author and homepage in registry/markedit-html-validate.json accordingly. homepage is just the repository URL — every extension in the registry points at a GitHub repo, and this README is what a visitor lands on.

  2. Cut a v1.0.0 release and attach dist/markedit-html-validate.js as an asset. A Release asset is preferred over a raw file URL, because downloads are then counted.

  3. Re-hash the exact bytes you uploaded and update sha256:

    npm run sha256
  4. Fork MarkEdit-app/extensions, copy the JSON to extensions/markedit-html-validate.json, and open a pull request. CI checks the schema, that id matches the filename, and that the hash matches the fetched bytes.

Leave addedDate and date out — the publish workflow adds them after merge. Later versions are appended to versions, newest first; only the newest five are kept.

Layout

Path
main.ts Entry point: menu, lifecycle, settings
src/validator.ts The analysis. Pure, no MarkEdit dependency
src/htmlSpec.ts Measured element behaviour tables
src/decorations.ts CodeMirror underlines and reveal-range
src/report.ts Alert and problem-list wording
tools/check.mjs Command-line front end
tools/deriveTables.mjs Regenerates the tables in htmlSpec.ts
tools/renderedVisibility.mjs Independent ground truth for the tests

@lezer/markdown, @codemirror/* and markedit-api are external in the bundle: MarkEdit provides them at runtime, so the extension shares the editor's own module instances. The validator parses the document itself rather than reading the editor's live syntax tree, because CodeMirror parses lazily and would leave a large document's tail unexamined.

Licence

MIT. See LICENSE.

About

MarkEdit extension that finds raw HTML silently breaking Markdown rendering, and reports where the rendered output stops.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages