Repository navigation
mdrefine 0.2.0-rc.3
Pre-release
Pre-release
Immutable
release. Only release title and notes can be modified.
Upgrading from a prerelease
From 0.2.0-rc.2:
format --writerefuses a read-only file on every platform, where Linux and macOS used to replace it: the file gets anengine/unwritable-fileerror and stays unchanged. Make the file writable to format it.- One document that cannot be read or written, or that a rule was refused or failed on, no longer keeps
format --writefrom writing the others. Every document that formatted is written, that one with the edits that were kept; each problem is reported for its own file, and the exit code is still2. Before, such a document kept every file from being written, and a file that failed while the batch was written stopped the run. Pass--excludeto leave a file alone. - In the JSON report,
writtenlists the files that were written instead of beingtrueorfalse. A script that tested it as a boolean must test its length, because an empty list counts as true in JavaScript. - A document that starts with a byte order mark gets its findings and edits at the right positions; before, it could be refused or get findings in the wrong places. In the library,
parsenow returnsdocument.sourcewithout the mark, which the tree's positions already skipped. mdtools-disable-next-lineand line numbers also count a carriage return alone as a line ending; before, such a directive could apply to the wrong line.- A file locked by another program on Windows (
EBUSY) is handled like one that may not be read, instead of stopping every command. - A document that is not UTF-8 text gets an
engine/unreadable-fileerror, and stdin that is not stops the command with exit code2. Before, such text was decoded with U+FFFD in place of what was not UTF-8, andformat --writewrote those characters into the file. Save such files as UTF-8. format --writewrites each file into the existing file instead of replacing it with a new one, so the file keeps its permissions, owner and group, attributes, creation time, and hard links. Before, a replaced file lost what Node.js cannot copy: the other names of a hard link kept the old content, and on Windows the file's own permissions were dropped, which could let more users read it. A crash or power failure during the write can now leave a file incomplete; see what writing keeps.- When a rule's edits would change a document's meaning,
formatkeeps the other rules' edits, so--diffand the library's output show them, and reports that rule withengine/unsafe-formatat the line where the meaning would first differ. Before, the whole document was left unchanged. Edits of two rules that overlap are applied in turn instead of refusing the document. - A mistake on the command line, such as an unknown option or command or a missing option value, exits with code
2instead of1, the code for findings, and so doesmdtoolswithout a command, which prints the help on stderr. - The text output and the diff name files from the current directory instead of the workspace root. The JSON report keeps paths from the root and adds
root. config explainnames the configuration fileconfiginstead offile, adds the document'spath, and refuses a document outside the workspace.configno longer accepts--exclude,--json,--stdin-filepath, and--max-warnings, which did nothing there.- A rule that throws or returns invalid findings is skipped for that document with an
engine/rule-errorerror, inlintas well, and the exit code is2. Before,lintstopped at the first such rule, andformatreported it asengine/unsafe-format. A plugin rule with an invalid definition, such asphase: "Inline", stops the command with exit code2where it used to be ignored, and a rule or preset named like a built-in property of every object, such astoString, is reported as unknown. recommendedwarns about six more problems, listed under Added. A check that runslint --max-warnings 0fails on them until the documents are fixed or the rules are turned off.recommendedformats more, soformat --checkandlintreport documents that it used to accept untilformat --writeruns once. It removes trailing spaces, except hard line breaks, and repeated blank lines; writes code fences with backticks and thematic breaks as---; turns underlined headings into#headings and removes closing#sequences; and aligns tables. To keep a form, turn its rule off or set its option, such as"style/heading": ["warn", { "closing": "keep" }]..gitignorefiles decide as in Git: a deeper file can include again what a higher one excludes, and patterns match letter case exactly unless the repository'score.ignorecaseis on, as Git sets it on Windows and macOS. Files that were left out before can be linted now, and the reverse.- Git's other ignore rules apply too:
.gitignorefiles above the workspace root,.git/info/exclude, and the global excludes file, while a file that Git tracks is never ignored. With--rooton a folder of a repository, or with a global excludes file, files that were linted before can be left out now, and a tracked file that a pattern matches is linted. .markdownfiles are documents, and a file named on the command line is read as Markdown whatever its name. A hook that passes every changed file should pass only the Markdown ones.- An input that does not exist and contains
*,?,[, or{is a pattern, and is an error only when it matches no document. .npm-cacheis an ordinary directory, linted like any other unless.gitignoreorignoreleaves it out.style/wrapwraps more: across line breaks in links, emphasis, and code spans, inside the text of links and emphasis, in the bodies of GitHub, Forgejo, and Gitea alerts, and before words such as--configor**strong**. A check that runsformat --checkfails on documents formatted by an earlier version untilformat --writeruns once.- In the library,
resolveConfigtakes one options object,{ path, config, plugins, defaultDialect }, aslintandformatdo, so their options serve as they are. A call with the four separate arguments of the prereleases throws an error that says so. presets,builtInRules,dialects,dialectAliases, andconfigSchemaare frozen, and a change to them throws aTypeError; change a copy instead.- In the library's types,
WikiLink,ObsidianComment, andObsidianHighlightreplaceObsidianLiteral,Rule.schemais aJsonSchemainstead of Ajv's schema type, and the lists and records inConfigare read-only: replace a list or therulesof a configuration instead of changing it in place. formatthrows an error that the workspace throws, such as a loader'sEIO, aslintdoes, instead of reporting it asengine/unsafe-format.- A plugin named
engineis rejected. recommendedformats lists:*and+bullets become-,1)becomes1., numbers are made consistent, and each item gets one space after its marker and no indentation before it.format --checkfails on lists written otherwise until they are formatted or the list rules are turned off.style/wrapno longer joins the lines of a paragraph that has a line starting with:::,{%, or{{, but lines that an earlier version joined stay joined: split them again where a site generator reads them by line.- A document whose front matter would end inside a code block, an HTML block, or display math can get
engine/unsafe-formaterrors, and exit code2, fromformat, where the rules used to change it.
From 0.2.0-rc.1 or earlier, also:
- When nothing names a dialect, documents are read as GitHub Markdown, or as Obsidian notes inside a vault, where they used to be read as CommonMark. Name
"dialect": "commonmark"to keep the old reading. The library'slint,format,resolveConfig, andcreateWorkspaceassumegithubas well. $…$is math only under the conditions of each dialect's renderer; before, any text between two$signs was. Text that was left alone because of that is now wrapped and restyled like other prose.- A directory that may not be read is skipped with a warning, and a selected document that may not be read gets an
engine/unreadable-fileerror, where both used to stop the command with exit code2and no report. An.obsidian/app.jsonthat cannot be read or is not valid JSON gives a warning, and the vault is not wrapped. - In a vault below the workspace root, Obsidian links resolve from the vault's root and reach only its files. A workspace root inside a vault no longer counts as the whole vault, so links that the rest of the vault could affect are not rewritten.
- A plugin package is resolved as an
importfrom the configuration file, with the conditions given to Node.js, so a package with both animportand arequireentry loads throughimport. - In the library, a source loader given to
createWorkspacecan be called more than once for the same file and must return the same text each time, and one that throwsEACCESorEPERMmakes the file unreadable instead of stopping the run. - The
lintsummary counts errors and warnings, and messages about paths, configuration files, and plugins are worded differently. Exit codes and the JSON report keep their meaning.
From every prerelease:
- The command line, the configuration, the JSON report, and the library now follow the versioning policy. npm's
latesttag names the newest stable release; release candidates no longer move it.
Added
style/list-marker,style/ordered-list, andstyle/list-indent, inrecommended, write bullets as-and numbers as1.with consistent numbering and one space after each marker, and move each item's lines with its text. Their options cover markdownlint's MD004, MD007, MD029, and MD030 (#275).--quietreports errors only, andruleslists the rules of the configuration's plugins too, also as JSON with--json(#260).- The CLI finds
mdtools.config.jsandmdtools.config.cjs,extendsaccepts a single preset name, and a rule's setting can be its severity alone in a list, as in["warn"]. The package ships the configuration's JSON Schema asconfig.schema.json, which the documentation site serves as well, for editors to complete and check configuration files (#261). - The library loads with
require(), exportsmdrefine/package.json, and has a doc comment for every export; the package listskeywords(#261). - Nine problem rules:
links/undefined-reference,links/unused-definition,links/empty,structure/heading-increment,content/image-alt, andcontent/table-columnsinrecommended, and the optionalcontent/code-language,structure/single-title, andstructure/duplicate-heading(#237). style/block-spacingseparates headings and lists from the blocks around them with a blank line (#190).- Four style rules in
recommended:style/code-fence,style/thematic-break,style/blank-lines, andstyle/trailing-space, with markdownlint's choices as options.style/headingalso removes closing#sequences and can write underlined headings (optionsclosingandstyle),style/tablecan write compact tables (optionstyle), and both joinrecommended(#267). lintandformatacceptrangesto work on parts of a document, andformatreturns theirscope(#188, #195).mdrefine/nodeexportsdiscoverWorkspace(), the CLI's discovery of files, with the exampleexamples/library/lint-workspace.mjs(#189).- Glob patterns as inputs, such as
mdtools lint "docs/**/*.md", expanded by the CLI itself on every system (#239).
Changed
- A mistake on the command line exits with code
2, as in ESLint and Prettier (#260). - The text output names files from the current directory, and the JSON report gives the workspace
root(#260). format --checknames the files that would change (#260).- The help describes every command and lists only the options that each one reads, an unknown dialect gets the list of dialects,
lint --fixpoints toformat --write,config explainreportsconfigandpathand refuses documents outside the workspace, and theobsidian/strict-line-breaksmessage speaks to both command-line and library users (#260). - Configuration and rule option errors name the setting as it is written, in plain words, and list the allowed values, as in
"extends[1]" must be a preset name, not 5; a JSON syntax error gives its line and column (#261). resolveConfigtakes an options object, the exported tables are frozen and typed read-only, and the types of Obsidian nodes and rule schemas are their own (#261).format --writerefuses read-only files on every platform, and reports a file that fails withengine/unwritable-file(#177).- A file locked by another program on Windows counts as one that may not be read, in the CLI and in
createWorkspaceloaders (#177). - Documents and stdin that are not UTF-8 text are refused instead of being damaged, and
createWorkspacetreats a loader error with the codeERR_ENCODING_INVALID_ENCODED_DATAas an unreadable target (#234). format --writewrites into the existing file instead of replacing it, so that the file keeps its permissions, attributes, owner, creation time, and hard links, and writes the original content back when a write fails part way (#234, #255).format --writewrites every document that formatted, and reports a document that could not be read, formatted as configured, or written for its own file; the JSON report'swrittenlists the files written (#255).- The documentation is restructured, with new pages for the rules, dialects, integrations, the library API, versions, and this changelog, and the release notes come from this file (#238).
- When one rule's edits would change a document's meaning,
formatleaves out only that rule's edits and names the rule and the line, and it applies overlapping edits in turn (#241). - Plugin rule option schemas are compiled once instead of for every document (#241).
- Many repeated headings, and many emphases on one line, no longer take time that grows with the square of their number on Forgejo and Gitea, and
ignoreandoverridespatterns are compiled once instead of for every path (#273). .markdownfiles are documents and link targets, and a file named on the command line is read as Markdown whatever its name (#239).- CI tests Node.js 22.12.0, the lowest supported version, where it tested the newest Node.js 22, and also tests Node.js 26 (#257).
- The documentation site describes the newest stable release instead of the unreleased state of
main(#257). style/wrapreflows a paragraph with a line break inside a link's text, an image description, emphasis, strong text, strikethrough, or a code span, and can break lines inside the text of links and emphasis (#262).style/wrapwraps the body of a GitHub, Forgejo, or Gitea alert below the line with its marker, as it does for Obsidian callouts (#262).style/wrapreflows a paragraph whose code span goes on in a line that starts with spaces or tabs, and joins the span without them, asstyle/inline-codedoes (#288).style/wrapleaves a paragraph as written when one of its lines starts with:::,{%, or{{, such as a Docusaurus admonition or a Liquid tag, keeps a word that starts with them from starting a line, and keeps a template tag that one line holds whole. Site generators read these by line (#298).- The plugin example's style rule writes one space after a heading's
#marks, becausestyle/thematic-breaknow does what it did (#267).
Fixed
style/wrap, the other rules that leave the line of an alert's or callout's marker alone, and the safety check recognize a marker written with an escape or a character reference, such as\[!NOTE]or[!TIP], which GitHub renders as an alert. Before,style/wrapjoined the body onto such a marker, and GitHub showed an ordinary quote (#298).- A document whose front matter would end at a
---or+++inside a code block, an HTML block, or display math that a line of the front matter opens is read with that block, and formatting keeps its meaning both with and without the front matter. Before, rules could change the block's content, such as a___in code into---, which GitHub shows (#298). style/wrapwraps a paragraph that starts with a linked image, such as a badge, unless it opens a block quote. Before, it was left as written, as if it started with a callout's marker (#298).- A plugin rule's option schema of
falserefuses options, and an asynchronous schema ($async) is refused when the rule set is built instead of crashing Node.js after the run (#297). - A heading whose code span is written across lines gets the anchor that GitHub gives it, so a correct link to it is no longer reported missing, and the library's
textContentreads such a code span as renderers show it (#293). --dialectworks with an.mjsconfiguration that exports a frozen object (#260).--stdin-filepathaccepts a document inside a root that is reached through a link or a Windows junction (#260).- The configuration and command-line pages say that configuration patterns match letter case on every system, while
.gitignorefiles and input patterns ignore it on Windows and macOS (#261). formatstops on an error of the workspace, aslintdoes, instead of reporting it asengine/unsafe-format, and a plugin namedengine, whose findings would pass for the engine's own, is rejected (#261).parsereads a document in the dialect that an alias such ascodebergstands for, so Forgejo's rules apply to it, and the type declarations no longer need@types/mdastfrom another package, which a strict installation such as pnpm's does not provide (#261).- Documents and JSON configuration files that start with a byte order mark are read correctly (#174).
style/headingkeeps link reference definitions directly above an underlined heading, which it used to replace with the heading, so that the document was refused (#267).style/final-newlineadds no line ending, andstyle/block-spacingno blank line, where it would become part of an unclosed HTML or Obsidian comment, andstyle/final-newlinenone where it would add a line to a code block without its closing fence; such documents used to be refused (#267).style/wrapleaves paragraphs as written instead of refusing the document when their first word is a bare block marker (#196), when an Obsidian paragraph holds==,[[, or]]outside its highlights and links, or when the first line is lazy (#200).style/wraplets words that only start like a block marker, such as--config,#tag, or-5, begin a line, and keeps a table delimiter row from beginning one (#262).style/wrapkeeps a paragraph's first line from becoming a link reference definition, a thematic break, or display math instead of refusing the document (#262).style/wrapcounts a tab in a list item's prefix up to the next tab stop instead of as no width (#262).- A carriage return alone ends a line for suppression directives, diagnostic positions, callout titles, and
style/wrap(#196, #200). - A rule that throws or returns invalid findings is reported as
engine/rule-errorfor its document, and invalid plugin rules and names such astoStringare rejected with a clear message (#241). .gitignorefiles are applied as Git applies them, with negations in deeper files and letter case per platform (#239).- Git's ignore rules apply from the repository's top level, also above the workspace root, together with
.git/info/excludeand the global excludes file. Inside a nested repository only its own rules apply, and none applies to a file that Git tracks (#273). - Git's ignore rules match letter case as each repository's
core.ignorecasesays, and each repository, also a nested one, uses the global excludes file that its own settings name (#287). - A file or directory named on the command line that is ignored gets a warning that says why, instead of being skipped without a word (#239).
- A link into
dist,coverage,node_modules, or another directory that is not processed, or through a symbolic link or junction, is checked on disk instead of being reported missing; one that leads outside the workspace root cannot be checked (#259). - An Obsidian block identifier counts only at the end of a block and holds letters, digits, and hyphens, in every rule that reads one (#259).
- A vault's
.obsidian/app.jsonthat starts with a byte order mark is read (#259). links/notationdecodes percent escapes when it makes a wikilink (#259).- A link to a file that is not Markdown accepts any fragment, such as an image's
#gh-dark-mode-onlyor a PDF's#page=2(#259). - An Obsidian wikilink never resolves to a folder (#259).
- Obsidian links to a heading below another,
[[Note#Setup#Mobile]], are resolved (#259). - An Obsidian heading link matches the heading's source text, with its backticks, as well as its visible text (#259).
style/emphasisandstyle/strongleave the text of a collapsed or shortcut reference, such as[*Title*][], as written, because that text is also the label that matches the definition, and likewise text in brackets that the new markers would make match one. Before, all of the rule's edits in the document were refused (#274).style/emphasis,style/strong,style/inline-code, andstyle/block-spacingno longer propose edits that change the parse in rare places, where their edits were refused: crossed or shared markers, as in*a _b* c_or*****a***, markers next to dollar math or across the line of a callout's or alert's marker, code spans whose joined line would form an Obsidian highlight or wikilink, change that line, or pair with earlier backticks, a table below one that ends in a list item or block quote, and a list in a quote that ends in an empty footnote definition (#274).style/inline-codeleaves out the spaces and tabs that start a code span's next line when it joins the span, as CommonMark and GitHub do; before,npm runcontinued by an indentedbuildbecamenpm run build, which changed what GitHub shows. The safety check compares code spans the same way (#274).style/inline-codeand the safety check read a code span as GitHub shows it where a line break is at its edge, and on a lazy continuation line, one without its block quote's>or its list item's indentation, whose spaces and tabs GitHub keeps in the span. Before, the rule could change what GitHub shows, such asnpm run buildintonpm run build, and the safety check did not notice. Outside the GitHub dialect, a span on such an indented lazy line stays split, because commonmark.js leaves those spaces out.style/wrapnow also reflows a paragraph whose code span has a line break at its edge (#291).style/wrapkeeps a dollar from the edge of a line where it could begin or end math there: in Obsidian a word that ends in$stays with the word after it, and on GitHub and in Obsidian a word that starts with$stays with the word before it. Before, such a paragraph was refused in Obsidian (#274).style/wrapleaves a paragraph as written when its second line is a table delimiter row, in every dialect with tables. Directly below a table that ends in a list item or block quote, the parser reads another table as such a paragraph, and joining its lines removed a table that GitHub shows (#274).style/wrapkeeps a run of=or-that starts the body of an alert or callout together with the next word. Alone on a line, it would underline the marker's line as a heading, and the paragraph was refused (#274).- The type declaration of
defaultDialectdescribes vaults anywhere in a workspace (#184), and the CLI guide explains stdin in PowerShell (#182).
🤖 Generated with Claude Opus 5.5 via Claude Code