@atomiks/mdx-pretty-code
Advanced tools
Comparing version 0.0.2 to 0.0.3
@@ -1,2 +0,1 @@ | ||
import visit from 'unist-util-visit'; | ||
import { JSDOM } from 'jsdom'; | ||
@@ -7,2 +6,365 @@ import rangeParser from 'parse-numeric-range'; | ||
/** | ||
* @typedef {import('unist').Node} Node | ||
* @typedef {import('unist').Parent} Parent | ||
* | ||
* @typedef {string} Type | ||
* @typedef {Object<string, unknown>} Props | ||
* | ||
* @typedef {null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>} Test | ||
*/ | ||
const convert = | ||
/** | ||
* @type {( | ||
* (<T extends Node>(test: T['type']|Partial<T>|TestFunctionPredicate<T>) => AssertPredicate<T>) & | ||
* ((test?: Test) => AssertAnything) | ||
* )} | ||
*/ | ||
( | ||
/** | ||
* Generate an assertion from a check. | ||
* @param {Test} [test] | ||
* When nullish, checks if `node` is a `Node`. | ||
* When `string`, works like passing `function (node) {return node.type === test}`. | ||
* When `function` checks if function passed the node is true. | ||
* When `object`, checks that all keys in test are in node, and that they have (strictly) equal values. | ||
* When `array`, checks any one of the subtests pass. | ||
* @returns {AssertAnything} | ||
*/ | ||
function (test) { | ||
if (test === undefined || test === null) { | ||
return ok | ||
} | ||
if (typeof test === 'string') { | ||
return typeFactory(test) | ||
} | ||
if (typeof test === 'object') { | ||
return Array.isArray(test) ? anyFactory(test) : propsFactory(test) | ||
} | ||
if (typeof test === 'function') { | ||
return castFactory(test) | ||
} | ||
throw new Error('Expected function, string, or object as test') | ||
} | ||
); | ||
/** | ||
* @param {Array.<Type|Props|TestFunctionAnything>} tests | ||
* @returns {AssertAnything} | ||
*/ | ||
function anyFactory(tests) { | ||
/** @type {Array.<AssertAnything>} */ | ||
const checks = []; | ||
let index = -1; | ||
while (++index < tests.length) { | ||
checks[index] = convert(tests[index]); | ||
} | ||
return castFactory(any) | ||
/** | ||
* @this {unknown} | ||
* @param {unknown[]} parameters | ||
* @returns {boolean} | ||
*/ | ||
function any(...parameters) { | ||
let index = -1; | ||
while (++index < checks.length) { | ||
if (checks[index].call(this, ...parameters)) return true | ||
} | ||
return false | ||
} | ||
} | ||
/** | ||
* Utility to assert each property in `test` is represented in `node`, and each | ||
* values are strictly equal. | ||
* | ||
* @param {Props} check | ||
* @returns {AssertAnything} | ||
*/ | ||
function propsFactory(check) { | ||
return castFactory(all) | ||
/** | ||
* @param {Node} node | ||
* @returns {boolean} | ||
*/ | ||
function all(node) { | ||
/** @type {string} */ | ||
let key; | ||
for (key in check) { | ||
// @ts-expect-error: hush, it sure works as an index. | ||
if (node[key] !== check[key]) return false | ||
} | ||
return true | ||
} | ||
} | ||
/** | ||
* Utility to convert a string into a function which checks a given node’s type | ||
* for said string. | ||
* | ||
* @param {Type} check | ||
* @returns {AssertAnything} | ||
*/ | ||
function typeFactory(check) { | ||
return castFactory(type) | ||
/** | ||
* @param {Node} node | ||
*/ | ||
function type(node) { | ||
return node && node.type === check | ||
} | ||
} | ||
/** | ||
* Utility to convert a string into a function which checks a given node’s type | ||
* for said string. | ||
* @param {TestFunctionAnything} check | ||
* @returns {AssertAnything} | ||
*/ | ||
function castFactory(check) { | ||
return assertion | ||
/** | ||
* @this {unknown} | ||
* @param {Array.<unknown>} parameters | ||
* @returns {boolean} | ||
*/ | ||
function assertion(...parameters) { | ||
// @ts-expect-error: spreading is fine. | ||
return Boolean(check.call(this, ...parameters)) | ||
} | ||
} | ||
// Utility to return true. | ||
function ok() { | ||
return true | ||
} | ||
/** | ||
* @param {string} d | ||
* @returns {string} | ||
*/ | ||
function color(d) { | ||
return '\u001B[33m' + d + '\u001B[39m' | ||
} | ||
/** | ||
* @typedef {import('unist').Node} Node | ||
* @typedef {import('unist').Parent} Parent | ||
* @typedef {import('unist-util-is').Test} Test | ||
* @typedef {import('./complex-types').Action} Action | ||
* @typedef {import('./complex-types').Index} Index | ||
* @typedef {import('./complex-types').ActionTuple} ActionTuple | ||
* @typedef {import('./complex-types').VisitorResult} VisitorResult | ||
* @typedef {import('./complex-types').Visitor} Visitor | ||
*/ | ||
/** | ||
* Continue traversing as normal | ||
*/ | ||
const CONTINUE = true; | ||
/** | ||
* Do not traverse this node’s children | ||
*/ | ||
const SKIP = 'skip'; | ||
/** | ||
* Stop traversing immediately | ||
*/ | ||
const EXIT = false; | ||
/** | ||
* Visit children of tree which pass a test | ||
* | ||
* @param tree Abstract syntax tree to walk | ||
* @param test Test node, optional | ||
* @param visitor Function to run for each node | ||
* @param reverse Visit the tree in reverse order, defaults to false | ||
*/ | ||
const visitParents = | ||
/** | ||
* @type {( | ||
* (<Tree extends Node, Check extends Test>(tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor<Tree, Check>, reverse?: boolean) => void) & | ||
* (<Tree extends Node>(tree: Tree, visitor: import('./complex-types').BuildVisitor<Tree>, reverse?: boolean) => void) | ||
* )} | ||
*/ | ||
( | ||
/** | ||
* @param {Node} tree | ||
* @param {Test} test | ||
* @param {import('./complex-types').Visitor<Node>} visitor | ||
* @param {boolean} [reverse] | ||
*/ | ||
function (tree, test, visitor, reverse) { | ||
if (typeof test === 'function' && typeof visitor !== 'function') { | ||
reverse = visitor; | ||
// @ts-expect-error no visitor given, so `visitor` is test. | ||
visitor = test; | ||
test = null; | ||
} | ||
const is = convert(test); | ||
const step = reverse ? -1 : 1; | ||
factory(tree, null, [])(); | ||
/** | ||
* @param {Node} node | ||
* @param {number?} index | ||
* @param {Array.<Parent>} parents | ||
*/ | ||
function factory(node, index, parents) { | ||
/** @type {Object.<string, unknown>} */ | ||
// @ts-expect-error: hush | ||
const value = typeof node === 'object' && node !== null ? node : {}; | ||
/** @type {string|undefined} */ | ||
let name; | ||
if (typeof value.type === 'string') { | ||
name = | ||
typeof value.tagName === 'string' | ||
? value.tagName | ||
: typeof value.name === 'string' | ||
? value.name | ||
: undefined; | ||
Object.defineProperty(visit, 'name', { | ||
value: | ||
'node (' + | ||
color(value.type + (name ? '<' + name + '>' : '')) + | ||
')' | ||
}); | ||
} | ||
return visit | ||
function visit() { | ||
/** @type {ActionTuple} */ | ||
let result = []; | ||
/** @type {ActionTuple} */ | ||
let subresult; | ||
/** @type {number} */ | ||
let offset; | ||
/** @type {Array.<Parent>} */ | ||
let grandparents; | ||
if (!test || is(node, index, parents[parents.length - 1] || null)) { | ||
result = toResult(visitor(node, parents)); | ||
if (result[0] === EXIT) { | ||
return result | ||
} | ||
} | ||
// @ts-expect-error looks like a parent. | ||
if (node.children && result[0] !== SKIP) { | ||
// @ts-expect-error looks like a parent. | ||
offset = (reverse ? node.children.length : -1) + step; | ||
// @ts-expect-error looks like a parent. | ||
grandparents = parents.concat(node); | ||
// @ts-expect-error looks like a parent. | ||
while (offset > -1 && offset < node.children.length) { | ||
// @ts-expect-error looks like a parent. | ||
subresult = factory(node.children[offset], offset, grandparents)(); | ||
if (subresult[0] === EXIT) { | ||
return subresult | ||
} | ||
offset = | ||
typeof subresult[1] === 'number' ? subresult[1] : offset + step; | ||
} | ||
} | ||
return result | ||
} | ||
} | ||
} | ||
); | ||
/** | ||
* @param {VisitorResult} value | ||
* @returns {ActionTuple} | ||
*/ | ||
function toResult(value) { | ||
if (Array.isArray(value)) { | ||
return value | ||
} | ||
if (typeof value === 'number') { | ||
return [CONTINUE, value] | ||
} | ||
return [value] | ||
} | ||
/** | ||
* @typedef {import('unist').Node} Node | ||
* @typedef {import('unist').Parent} Parent | ||
* @typedef {import('unist-util-is').Test} Test | ||
* @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult | ||
* @typedef {import('./complex-types').Visitor} Visitor | ||
*/ | ||
/** | ||
* Visit children of tree which pass a test | ||
* | ||
* @param tree Abstract syntax tree to walk | ||
* @param test Test, optional | ||
* @param visitor Function to run for each node | ||
* @param reverse Fisit the tree in reverse, defaults to false | ||
*/ | ||
const visit = | ||
/** | ||
* @type {( | ||
* (<Tree extends Node, Check extends Test>(tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor<Tree, Check>, reverse?: boolean) => void) & | ||
* (<Tree extends Node>(tree: Tree, visitor: import('./complex-types').BuildVisitor<Tree>, reverse?: boolean) => void) | ||
* )} | ||
*/ | ||
( | ||
/** | ||
* @param {Node} tree | ||
* @param {Test} test | ||
* @param {import('./complex-types').Visitor} visitor | ||
* @param {boolean} [reverse] | ||
*/ | ||
function (tree, test, visitor, reverse) { | ||
if (typeof test === 'function' && typeof visitor !== 'function') { | ||
reverse = visitor; | ||
visitor = test; | ||
test = null; | ||
} | ||
visitParents(tree, test, overload, reverse); | ||
/** | ||
* @param {Node} node | ||
* @param {Array.<Parent>} parents | ||
*/ | ||
function overload(node, parents) { | ||
const parent = parents[parents.length - 1]; | ||
return visitor( | ||
node, | ||
parent ? parent.children.indexOf(node) : null, | ||
parent | ||
) | ||
} | ||
} | ||
); | ||
let highlighter = null; | ||
function createRemarkPlugin(options = {}) { | ||
@@ -25,3 +387,6 @@ return () => async (tree) => { | ||
const highlighter = await shiki.getHighlighter(shikiOptions); | ||
if (!highlighter) { | ||
highlighter = await shiki.getHighlighter(shikiOptions); | ||
} | ||
const loadedLanguages = highlighter.getLoadedLanguages(); | ||
@@ -126,3 +491,2 @@ | ||
) | ||
.flat() | ||
.join(''); | ||
@@ -140,2 +504,6 @@ | ||
dom.window.document | ||
.querySelector('code') | ||
.setAttribute('data-language', lang); | ||
node.value = sanitizeHtml( | ||
@@ -142,0 +510,0 @@ dom.window.document.body.innerHTML, |
{ | ||
"name": "@atomiks/mdx-pretty-code", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"description": "A Remark plugin to make the code in your MDX docs simply beautiful. Powered by [Shiki](https://github.com/shikijs/shiki).", | ||
"main": "./dist/mdx-pretty-code.cjs", | ||
"module": "./dist/mdx-pretty-code.js", | ||
"type": "module", | ||
@@ -26,6 +27,6 @@ "exports": { | ||
"peerDependencies": { | ||
"shiki": "*", | ||
"unist-util-visit": "*" | ||
"shiki": "*" | ||
}, | ||
"devDependencies": { | ||
"@rollup/plugin-node-resolve": "^13.1.1", | ||
"prettier": "^2.5.1", | ||
@@ -35,3 +36,3 @@ "rollup": "^2.60.2", | ||
"typescript": "^4.5.2", | ||
"unist-util-visit": "^4.1.0" | ||
"unist-util-visit": "^4.0.0" | ||
}, | ||
@@ -38,0 +39,0 @@ "dependencies": { |
# MDX Pretty Code | ||
<p align="center"> | ||
<img src="./preview.jpg" height="761"> | ||
<img src="https://github.com/atomiks/mdx-pretty-code/raw/master//preview.jpg"> | ||
</p> | ||
@@ -13,17 +13,11 @@ | ||
- ✅ Context-adjustable inline code highlighting | ||
- ✅ Line numbers | ||
- ✅ No runtime or bundle size cost | ||
## Installation | ||
ESM contexts: | ||
```shell | ||
npm install @atomiks/mdx-pretty-code shiki unist-util-visit | ||
npm install @atomiks/mdx-pretty-code shiki | ||
``` | ||
CJS contexts: | ||
```shell | ||
npm install @atomiks/mdx-pretty-code shiki unist-util-visit@2 | ||
``` | ||
## Usage | ||
@@ -185,2 +179,29 @@ | ||
## Line numbers | ||
CSS counters can be used to add line numbers. | ||
```css | ||
code { | ||
counter-reset: line; | ||
} | ||
code > .line::before { | ||
counter-increment: line; | ||
content: counter(line); | ||
/* Other styling */ | ||
display: inline-block; | ||
width: 1rem; | ||
margin-right: 2rem; | ||
text-align: right; | ||
color: gray; | ||
} | ||
``` | ||
## Language meta | ||
The `code` tag has a `data-language` attribute, so you can add the language | ||
information to the code block. | ||
## Sanitizing | ||
@@ -187,0 +208,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34825
4
898
214
6
1