Skip to content

Repository files navigation

remark-code-region

CInpm versionLicense: MITnode

Every code block in your docs is extracted from a passing test.

Pull tested code straight from your test suite into your Docusaurus, Astro, or any remark-powered docs. Named regions, automatic test-line stripping, hard build failures on drift. Drop-in replacement for remark-code-import with full file= compatibility.

The problem

Code examples in documentation go stale. You update an API, the tests pass, but the docs still show the old usage. Users copy broken code. Nobody notices until someone files an issue.

The solution

Write your examples as real, executable tests. Mark regions with comments. Reference them from your docs. The plugin injects the tested code at build time.

 ┌──────────────────────────┐
│ tests/test_sdk.py │
│ │
│ # region: create_user │
│ user = sdk.create(...) │ <- Runs in CI
│ assert user.id │
│ # endregion │
└────────────┬─────────────┘
│
build time
│
┌────────────▼─────────────┐
│ docs/quickstart.md │
│ │
│ ```python │
│ user = sdk.create(...) │ <- Injected (asserts stripped)
│ ``` │
└──────────────────────────┘

If the test breaks, CI fails. If the region moves, the build fails. Stale docs become impossible.

Quick start

Code fences without reference or file= are untouched -- migrate one block at a time.

Install:

npm install remark-code-region

Configure (Docusaurus):

// docusaurus.config.jsconstcodeRegion=require('remark-code-region');module.exports={presets: [['classic',{docs: {remarkPlugins: [codeRegion],},}]],};

Add region markers to your test file:

# tests/test_users.pydeftest_create_user():
# region: create_userfrommyappimportclientuser=client.create_user(name="Alice", role="admin")
print(user)
# {"id": "usr_123", "name": "Alice", "role": "admin"}# endregion: create_userassertuser["name"] =="Alice"assertuser["role"] =="admin"

Reference it from your docs:

```python reference="tests/test_users.py#create_user"
```

At build time, the code fence is replaced with:

frommyappimportclientuser=client.create_user(name="Alice", role="admin")
print(user)
# {"id": "usr_123", "name": "Alice", "role": "admin"}

The assert lines are automatically stripped. The region markers are removed. Clean, tested code in your docs.

Two syntaxes

remark-code-region supports two directive syntaxes. Both go through the same pipeline.

reference= (recommended)

Paths resolve relative to rootDir (defaults to process.cwd()). The directive is stripped from meta after processing.

```python reference="tests/test_users.py#create_user"
```

file= (remark-code-import compatible)

Paths resolve relative to the markdown file. Drop-in replacement for remark-code-import -- all existing file= references work unchanged.

```python file=./tests/test_users.py#create_user
```

Use <rootDir> to resolve relative to rootDir instead:

```python file=<rootDir>/tests/test_users.py#create_user
```

Both syntaxes support named regions (#region_name), line ranges (#L3-L6), and query flags (?noStrip).

Multi-region concatenation

Pull multiple regions from the same file into one code block:

```python reference="tests/test_users.py#imports,create_user"
```

Regions are joined with a blank line separator (configurable via regionSeparator). Mix regions and line ranges:

```python reference="tests/test_users.py#imports,L25-L30,create_user"
```

Whitespace around commas is trimmed. If any region is missing, the build fails.

Region markers

Use comment-style markers that match your language:

# region: namecodehere# endregion: name
// region: namecodehere// endregion: name

Supported out of the box (no configuration needed):

Comment styleLanguages
# region: / # endregion:Python, bash, Ruby, YAML, TOML
// region: / // endregion:JavaScript, TypeScript, Rust, Go, Java, C, C++, Swift, Kotlin

Adding more languages

For languages that use different comment syntax, add marker presets via the regionMarkers option. Built-in presets are available for CSS, HTML, and SQL:

constcodeRegion=require('remark-code-region');const{PRESET_MARKERS}=codeRegion;remarkPlugins: [[codeRegion,{regionMarkers: [// Keep the defaults (# and // comments)
...codeRegion.DEFAULT_REGION_MARKERS,// Add CSS: /* region: name */ ... /* endregion: name */PRESET_MARKERS.css,// Add HTML: <!-- region: name --> ... <!-- endregion: name -->PRESET_MARKERS.html,// Add SQL/Lua: -- region: name ... -- endregion: namePRESET_MARKERS.sql,],}]],

Custom markers

For any other comment style, define your own {start, end} regex pair. Group 1 must capture the region name:

regionMarkers: [
...codeRegion.DEFAULT_REGION_MARKERS,{// Erlang/Elixir: %% region: namestart: /^[\t]*%%\s*region:\s*(\S+)\s*$/,end: /^[\t]*%%\s*endregion:\s*(\S+)\s*$/,},],

Automatic test-line stripping

These patterns are removed from injected code by default:

PresetPatternLanguage
pythonassert ...Python
rustassert_eq!(), assert_ne!()Rust
javaassertEquals(), assertTrue(), etc.Java/JUnit
jsexpect()JS (Jest/Vitest)
cppASSERT_*, EXPECT_*C++ (gtest)
goif err != nil { t.FatalGo
markersLines ending with // test-only or # test-onlyAny

To keep assertions visible in a specific block, append ?noStrip:

```python reference="tests/test_users.py#create_user?noStrip"
```

Customizing strip patterns

Use PRESET_STRIP to compose exactly what you need:

constcodeRegion=require('remark-code-region');const{PRESET_STRIP}=codeRegion;remarkPlugins: [[codeRegion,{strip: [...PRESET_STRIP.python, ...PRESET_STRIP.js, ...PRESET_STRIP.markers],}]],

Available presets: python, rust, java, js, cpp, go, markers.

Or disable stripping entirely:

remarkPlugins: [[codeRegion,{strip: false}]],

Assertion transmutation

Instead of stripping assertions, transform them into readable output comments that show expected values:

# Test file:user=client.create_user(name="Alice", role="admin")
assertuser["name"] =="Alice"assertuser["role"] =="admin"assertuser["id"]
# In your docs (with transmutation):user=client.create_user(name="Alice", role="admin")
# user["name"] => "Alice"# user["role"] => "admin"# user["id"] => (truthy)

Enable transmutation with composable per-language presets:

constcodeRegion=require('remark-code-region');const{PRESET_TRANSMUTE}=codeRegion;remarkPlugins: [[codeRegion,{transmute: [...PRESET_TRANSMUTE.python, ...PRESET_TRANSMUTE.js],}]],

Available presets: python, js, rust, java, go, cpp.

Each language transforms assertions into subject => value comments using the correct comment prefix (# for Python, // for JS, etc.):

LanguageBeforeAfter
Pythonassert user["name"] == "Alice"# user["name"] => "Alice"
JSexpect(sum(2, 3)).toBe(5);// sum(2, 3) => 5
Rustassert_eq!(rows.len(), 1);// rows.len() => 1
JavaassertEquals("Alice", name);// name => "Alice"
Goassert.Equal(t, 3, len(rows))// len(rows) => 3
C++ASSERT_EQ(x, 1);// x => 1

Inequality and negation are kept natural: assert x != 0 becomes # x != 0.

Transmutation is opt-in and runs before strip. Transmuted lines become comments and skip stripping. Unmatched lines fall through to strip as usual. Use ?noTransmute to disable per block.

Diff between two regions

Show a highlighted diff between two tested code states -- for migration guides, changelogs, and "what changed in v2" docs.

```python diff reference="tests/test_api.py#v1_handler" diff-reference="#v2_handler"
```

The primary reference= is the "before", diff-reference= is the "after". Same-file shorthand (#v2_handler) inherits the file from the primary. Cross-file diffs work too.

Three output modes (set globally via diffFormat option or per-block with ?format=):

unified (default) -- standard diff format, lang set to "diff":

 def handler(request):
- return Response(request.body)+ validate(request)+ return Response(request.body, status=200)

inline-annotations -- Shiki [!code ++] / [!code --] markers, original language preserved:

returnResponse(request.body) // [!code--]
validate(request) // [!code++]
returnResponse(request.body, status=200) // [!code++]

side-by-side -- emits two sibling code nodes with data-diff-role="before" / "after" for custom component rendering.

Both sides go through the full cleaning pipeline (strip, transmute, dedent) independently before diffing.

remarkPlugins: [[codeRegion,{diffFormat: 'inline-annotations'}]],

Per-block override:

```python reference="file.py#v1?format=inline-annotations" diff-reference="#v2"
```

The diff-file= syntax works alongside file= for remark-code-import compatible paths.

Step-by-step tutorial diffs

For tutorials where each step builds on the previous, use diff-step to show the current step's complete code with highlights on what changed:

First, create the basic app:
```python reference="tests/tutorial.py#step1"
```
Now add configuration:
```python reference="tests/tutorial.py#step2" diff-step="step1"
```
Finally, add authentication:
```python reference="tests/tutorial.py#step3" diff-step="step2"
```

Step 1 renders as plain code. Step 2 renders with // [!code ++] on lines added since step 1. Step 3 shows what changed since step 2. Each block is a standalone code fence with its own prose context.

The reader sees complete, copyable code at every step -- with green/red highlights showing exactly what's new. Every step is a passing test.

diff-step defaults to inline-annotations (Shiki highlighting). Override with ?format=unified for +/- diff output.

diff-referencediff-step
Use caseMigration guide (before/after)Tutorial (step-by-step)
OutputDiff onlyFull current code with change annotations
Default formatunified (+/- prefixes)inline-annotations (Shiki highlights)

Tab groups

Group consecutive code fences into tabs using the tab attribute. Each fence is processed independently (reference extraction, strip, transmute, diff — everything works), then consecutive tab fences are wrapped in a tabGroup AST node.

Multi-language tabs:

```python tab="Python" reference="tests/test_sdk.py#create_user"
``````javascript tab="Node.js" reference="tests/test_sdk.js#create_user"
```

Same-language version comparison:

```python tab="SDK v1" reference="tests/v1.py#create_user"
``````python tab="SDK v2" reference="tests/v2.py#create_user"
```

Inline code tabs (no reference needed):

```bash tab="npm"
npm install myapp
``````bash tab="yarn"
yarn add myapp
```

Bare tab (no value) derives the label from the language tag: ```python tab → label "Python".

Sync key

Use tab-group="id" to synchronize tab selection across multiple tab groups on a page (like Docusaurus groupId or Starlight syncKey). Clicking "Python" in one group switches all groups with the same sync key.

```python tab="Python" tab-group="lang" reference="tests/install.py#pip"

To split adjacent tab groups without visible content between them, use an HTML comment (<!-- -->), which creates an invisible node that breaks the consecutive grouping.

AST output

The plugin emits tabGroup wrapper nodes with data.hName = 'div' and data.hProperties.class = 'code-tabs' (customizable via tabGroupClass option). Each child code node gets data.tabLabel and data.hProperties['data-tab'].

Without a companion plugin, this renders as a <div class="code-tabs"> wrapping stacked <pre><code> blocks. Add a companion plugin to transform these into native framework tabs:

Docusaurus:

constcodeRegion=require('remark-code-region');constcodeRegionTabs=require('remark-code-region/docusaurus');remarkPlugins: [codeRegion,codeRegionTabs],

Astro Starlight:

importcodeRegionfrom'remark-code-region';importcodeRegionTabsfrom'remark-code-region/starlight';remarkPlugins: [codeRegion,codeRegionTabs],

For other remark pipelines, the tabGroup AST nodes are well-structured for custom rehype plugins to consume directly.

Shiki annotations

Add ?highlight= or ?focus= to any reference to inject Shiki transformer annotations into the extracted code:

```python reference="tests/test_sdk.py#create_user?highlight=1,3-5"
``````python reference="tests/test_sdk.py#create_user?focus=2,4-6"
```

Line numbers are 1-based and refer to the final output (after strip/transmute/dedent). Ranges (3-5) and mixed specs (1,3-5,8) are supported.

Add the Shiki companion plugin to inject the annotations:

importcodeRegionfrom'remark-code-region';importcodeRegionShikifrom'remark-code-region/shiki';remarkPlugins: [codeRegion,codeRegionShiki],

Requires @shikijs/transformers (transformerNotationHighlight, transformerNotationFocus) configured in your Shiki setup.

Soft diff-step highlighting

By default, diff-step emits // [!code ++] / // [!code --] for green/red diff highlighting. The Shiki companion can convert these to softer // [!code highlight] annotations — emphasizing new lines without the diff color scheme:

remarkPlugins: [codeRegion,[codeRegionShiki,{diffStepStyle: 'highlight'}]],

Lines marked // [!code --] are removed (since diff-step shows the current code state), and // [!code ++] becomes // [!code highlight].

Auto-dedent

Extracted regions are automatically dedented. Common leading whitespace is removed so that code nested inside test functions or classes renders flush-left in your docs.

deftest_create_user():
# region: create_userfrommyappimportclientuser=client.create_user(name="Alice")
# endregion: create_user

...renders as:

frommyappimportclientuser=client.create_user(name="Alice")

Dedent is part of the configurable clean pipeline. To disable it, use clean: ['collapse', 'trim'] (omitting 'dedent').

Clean pipeline

The clean option controls the whitespace processing pipeline. Same API shape as strip: undefined uses defaults, false disables all, string[] for a custom list.

Available steps (run in array order):

StepWhat it does
'dedent'Remove common leading whitespace
'collapse'Collapse 3+ consecutive blank lines to 2
'trim'Trim leading and trailing whitespace
'trimEnd'Trim trailing whitespace only
constcodeRegion=require('remark-code-region');const{PRESET_CLEAN}=codeRegion;// Default: ['dedent', 'collapse', 'trim']remarkPlugins: [[codeRegion,{clean: [...PRESET_CLEAN.default]}]],// remark-code-import compat: only strip trailing whitespaceremarkPlugins: [[codeRegion,{clean: [...PRESET_CLEAN.compat]}]],// Custom: dedent and trim, but keep blank line runs intactremarkPlugins: [[codeRegion,{clean: ['dedent','trim']}]],// Disable all cleaningremarkPlugins: [[codeRegion,{clean: false}]],

Options

OptionTypeDefaultDescription
rootDirstringprocess.cwd()Base directory for resolving reference= paths.
allowOutsideRootbooleanfalseAllow references to files outside rootDir. Disabled by default as a security boundary.
regionMarkers{start, end}[]DEFAULT_REGION_MARKERSRegion marker pairs. Each start/end is a RegExp where group 1 captures the region name.
stripRegExp[] | falseundefined (defaults)Patterns to strip from injected code. undefined uses built-in defaults. false disables. RegExp[] replaces defaults.
transmuteTransmuteRule[] | falseundefined (disabled)Transform assertions into output comments. undefined/false disables. TransmuteRule[] enables with those rules.
cleanstring[] | false['dedent', 'collapse', 'trim']Whitespace cleaning steps. false disables. string[] for custom list.
regionSeparatorstring'\n\n'String inserted between regions in multi-region concatenation.
preserveFileMetabooleanfalseKeep file= in meta after processing (matches remark-code-import behavior).
diffFormatstring'unified'Output format for diff blocks: 'unified', 'inline-annotations', or 'side-by-side'.
tabGroupClassstring'code-tabs'CSS class for tab group wrapper <div>.

Exports (for composing custom configurations):

ExportWhat it is
DEFAULT_REGION_MARKERSDefault region marker pairs (# and // comments)
PRESET_MARKERSAdditional markers: .css, .html, .sql
PRESET_STRIPStrip patterns by language: .python, .rust, .java, .js, .cpp, .go, .markers
DEFAULT_STRIP_PATTERNSAll built-in strip patterns (union of all PRESET_STRIP groups)
PRESET_TRANSMUTETransmute rules by language: .python, .js, .rust, .java, .go, .cpp
DEFAULT_TRANSMUTE_RULESAll transmute rules combined
PRESET_CLEANClean step presets: .default, .compat
DEFAULT_CLEANDefault clean steps (['dedent', 'collapse', 'trim'])
COMMENT_PREFIXLanguage tag to comment prefix map (70+ languages)
constcodeRegion=require('remark-code-region');remarkPlugins: [[codeRegion,{rootDir: __dirname,regionMarkers: [
...codeRegion.DEFAULT_REGION_MARKERS,codeRegion.PRESET_MARKERS.css,],strip: [...codeRegion.PRESET_STRIP.python, ...codeRegion.PRESET_STRIP.js],transmute: [...codeRegion.PRESET_TRANSMUTE.python],clean: ['dedent','trim'],}]],

Hard failure on drift

If a referenced file is missing or a region doesn't exist, the build fails:

Error: remark-code-region: region 'create_user' not found in tests/test_users.py

If a region is opened but never closed, the build fails:

Error: remark-code-region: region 'create_user' in tests/test_users.py was opened but never closed

This is intentional. Silent stale docs are worse than a build error.

MDX compatibility

Works inside MDX components (<Tabs>, admonitions) -- the plugin runs at the remark AST level, before MDX processing. Any code fence with a reference or file= attribute is resolved, regardless of where it sits in the markdown tree.

Framework support

Docusaurus

constcodeRegion=require('remark-code-region');constcodeRegionTabs=require('remark-code-region/docusaurus');// optional, for tab groupsmodule.exports={presets: [['classic',{docs: {remarkPlugins: [codeRegion,codeRegionTabs]},}]],};

Astro Starlight

importcodeRegionfrom'remark-code-region';importcodeRegionTabsfrom'remark-code-region/starlight';// optional, for tab groupsexportdefaultdefineConfig({markdown: {remarkPlugins: [codeRegion,codeRegionTabs]},});

Astro (non-Starlight)

importcodeRegionfrom'remark-code-region';exportdefaultdefineConfig({markdown: {remarkPlugins: [codeRegion]},});

Next.js (MDX)

importcodeRegionfrom'remark-code-region';constwithMDX=createMDX({options: {remarkPlugins: [codeRegion]},});

Raw remark/unified

import{remark}from'remark';importcodeRegionfrom'remark-code-region';constresult=awaitremark().use(codeRegion,{rootDir: '/path/to/repo'}).process(markdown);

Note: VitePress uses markdown-it (not remark) and is not compatible.

Migrating from remark-code-import

remark-code-region is a drop-in replacement. All file= syntax works unchanged:

- const remarkCodeImport = require('remark-code-import');+ const remarkCodeImport = require('remark-code-region');

remark-code-import dropped require() support in v1.0.0. If your Docusaurus config uses require(), you're stuck on an old version. remark-code-region supports both require() and import.

Featureremark-code-importremark-code-region
Include from filefile=./path.js#L3-L6reference="path.py#region_name" or file=
Named regionsNoYes -- stable across edits
Line rangesYesYes (via file= syntax)
Multi-regionNoYes (#imports,create_user)
Strip test linesNoAuto-strips asserts, expects, test-only markers
Transmute assertionsNoTransforms assertions into => value comments
Diff two regionsNodiff-reference= with unified, inline-annotation, or side-by-side output
Tutorial step diffsNodiff-step= shows current code with change highlights from previous step
Auto-dedentOpt-inOn by default (configurable)
Composable clean pipelineNoPRESET_CLEAN.default / .compat / custom
Security boundaryallowImportingFromOutsideallowOutsideRoot (default: false)
CJS require() supportDropped in v1.0Yes
Fail on missing fileYesYes

For exact byte-identical output during migration:

constcodeRegion=require('remark-code-region');const{PRESET_CLEAN}=codeRegion;remarkPlugins: [[codeRegion,{clean: [...PRESET_CLEAN.compat],preserveFileMeta: true,strip: false,}]],

License

MIT

About

Every code block in your docs is extracted from a passing test

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages