Skip to content

Commit

Permalink
Refactor code-style
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Sep 4, 2023
1 parent edc5650 commit cad037f
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 57 deletions.
126 changes: 79 additions & 47 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,63 @@
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Root} Root
*
* @typedef {import('lowlight').Root} LowlightRoot
* @typedef {import('lowlight/lib/core.js').HighlightSyntax} HighlightSyntax
* @typedef {import('hast').Root} Root
* @typedef {import('hast').Element} Element
* @typedef {Root|Root['children'][number]} Node
* To do: expose from `lowlight` root.
*
* @typedef {import('vfile').VFile} VFile
*/

/**
* @typedef Options
* Configuration (optional).
* @property {string} [prefix='hljs-']
* Prefix to use before classes.
* @property {boolean} [detect=false]
* Whether to detect the programming language on code without a language
* class.
* @property {Array<string>} [subset]
* Scope of languages to check when auto-detecting (default: all languages).
* @property {boolean} [ignoreMissing=false]
* Swallow errors for missing languages.
* By default, unregistered syntaxes throw an error when they are used.
* Pass `true` to swallow those errors and thus ignore code with unknown code
* languages.
* @property {Array<string>} [plainText=[]]
* List of plain-text languages.
* Pass any languages you would like to be kept as plain-text instead of
* getting highlighted.
* @property {Record<string, string|Array<string>>} [aliases={}]
* Register more aliases.
* Passed to `lowlight.registerAlias`.
* @property {Record<string, HighlightSyntax>} [languages={}]
* Register more languages.
* Each key/value pair passed as arguments to `lowlight.registerLanguage`.
* @property {Record<string, Array<string> | string> | null | undefined} [aliases={}]
* Register more aliases (optional); passed to `lowlight.registerAlias`.
* @property {boolean | null | undefined} [detect=false]
* Detect the programming language on code without a language class (default:
* `false`).
* @property {boolean | null | undefined} [ignoreMissing=false]
* Swallow errors for missing languages (default: `false`); unregistered
* syntaxes normally throw an error when used; pass `ignoreMissing: true` to
* swallow those errors and ignore code with unknown code languages.
* @property {Record<string, HighlightSyntax> | null | undefined} [languages={}]
* Register more languages (optional); each key/value pair passed as arguments
* to `lowlight.registerLanguage`.
* @property {Array<string> | null | undefined} [plainText=[]]
* List of plain-text languages (optional); pass any languages you would like
* to be kept as plain-text instead of getting highlighted.
* @property {string | null | undefined} [prefix='hljs-']
* Prefix to use before classes (default: `'hljs-'`).
* @property {Array<string> | null | undefined} [subset]
* Scope of languages to check when auto-detecting (optional); when not
* passed, all registered languages are checked.
*/

import {lowlight} from 'lowlight'
import {toText} from 'hast-util-to-text'
import {lowlight} from 'lowlight'
import {visit} from 'unist-util-visit'

const own = {}.hasOwnProperty
/** @type {Options} */
const emptyOptions = {}

/**
* Plugin to highlight the syntax of code with lowlight (`highlight.js`).
* Highlight programming code with lowlight (`highlight.js`).
*
* @type {import('unified').Plugin<[Options?] | Array<void>, Root>}
* @param {Readonly<Options> | null | undefined} [options]
* Configuration (optional).
* @returns
* Transform.
*/
export default function rehypeHighlight(options = {}) {
const {aliases, languages, prefix, plainText, ignoreMissing, subset, detect} =
options
export default function rehypeHighlight(options) {
const settings = options || emptyOptions
const aliases = settings.aliases
const detect = settings.detect || false
const ignoreMissing = settings.ignoreMissing || false
const languages = settings.languages
const plainText = settings.plainText
const prefix = settings.prefix
const subset = settings.subset
let name = 'hljs'

if (aliases) {
Expand All @@ -56,7 +69,7 @@ export default function rehypeHighlight(options = {}) {
let key

for (key in languages) {
if (own.call(languages, key)) {
if (Object.hasOwn(languages, key)) {
lowlight.registerLanguage(key, languages[key])
}
}
Expand All @@ -67,17 +80,23 @@ export default function rehypeHighlight(options = {}) {
name = pos > -1 ? prefix.slice(0, pos) : prefix
}

return (tree, file) => {
// eslint-disable-next-line complexity
visit(tree, 'element', (node, _, givenParent) => {
const parent = /** @type {Node?} */ (givenParent)

/**
* Transform.
*
* @param {Root} tree
* Tree.
* @param {VFile} file
* File.
* @returns {undefined}
* Nothing.
*/
return function (tree, file) {
visit(tree, 'element', function (node, _, parent) {
if (
!parent ||
!('tagName' in parent) ||
parent.tagName !== 'pre' ||
node.tagName !== 'code' ||
!node.properties
!parent ||
parent.type !== 'element' ||
parent.tagName !== 'pre'
) {
return
}
Expand Down Expand Up @@ -111,8 +130,18 @@ export default function rehypeHighlight(options = {}) {
lowlight.highlightAuto(toText(parent), {prefix, subset})
} catch (error) {
const exception = /** @type {Error} */ (error)
if (!ignoreMissing || !/Unknown language/.test(exception.message)) {
file.fail(exception, node, 'rehype-highlight:missing-language')

if (
lang &&
(!ignoreMissing || !/Unknown language/.test(exception.message))
) {
file.fail('Cannot highlight as `' + lang + '`, it’s not registered', {
ancestors: [parent, node],
cause: exception,
place: node.position,
ruleId: 'missing-language',
source: 'rehype-highlight'
})
}

return
Expand All @@ -122,7 +151,7 @@ export default function rehypeHighlight(options = {}) {
node.properties.className.push('language-' + result.data.language)
}

if (Array.isArray(result.children) && result.children.length > 0) {
if (result.children.length > 0) {
node.children = result.children
}
})
Expand All @@ -133,10 +162,13 @@ export default function rehypeHighlight(options = {}) {
* Get the programming language of `node`.
*
* @param {Element} node
* @returns {false|string|undefined}
* Node.
* @returns {false | string | undefined}
* Language or `undefined`, or `false` when an explikcit `no-highlight` class
* is used.
*/
function language(node) {
const className = node.properties && node.properties.className
const className = node.properties.className
let index = -1

if (!Array.isArray(className)) {
Expand Down
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
"@types/hast": "^3.0.0",
"hast-util-to-text": "^4.0.0",
"lowlight": "^2.0.0",
"unified": "^11.0.0",
"unist-util-visit": "^5.0.0"
"unist-util-visit": "^5.0.0",
"vfile": "^6.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
Expand Down Expand Up @@ -79,6 +79,9 @@
"strict": true
},
"xo": {
"prettier": true
"prettier": true,
"rules": {
"complexity": "off"
}
}
}
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ languages.

###### `options.aliases`

Register more aliases (`Record<string, string|Array<string>>`, default: `{}`).
Register more aliases (`Record<string, Array<string> | string>`, default: `{}`).
Passed to [`lowlight.registerAlias`][register-alias].

###### `options.languages`
Expand Down
16 changes: 11 additions & 5 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import {rehype} from 'rehype'
import rehypeHighlight from './index.js'

test('rehypeHighlight', async function (t) {
await t.test('should expose the public api', async function () {
assert.deepEqual(Object.keys(await import('./index.js')).sort(), [
'default'
])
})

await t.test('should work on empty code', async function () {
const file = await rehype()
.data('settings', {fragment: true})
Expand Down Expand Up @@ -124,7 +130,7 @@ test('rehypeHighlight', async function (t) {
await t.test('should highlight (prefix without dash)', async function () {
const file = await rehype()
.data('settings', {fragment: true})
.use(rehypeHighlight, {prefix: 'foo', detect: true})
.use(rehypeHighlight, {detect: true, prefix: 'foo'})
.process(
[
'<h1>Hello World!</h1>',
Expand All @@ -146,7 +152,7 @@ test('rehypeHighlight', async function (t) {
await t.test('should highlight (prefix with dash)', async function () {
const file = await rehype()
.data('settings', {fragment: true})
.use(rehypeHighlight, {prefix: 'foo-', detect: true})
.use(rehypeHighlight, {detect: true, prefix: 'foo-'})
.process(
[
'<h1>Hello World!</h1>',
Expand Down Expand Up @@ -302,7 +308,7 @@ test('rehypeHighlight', async function (t) {
} catch (error) {
assert.match(
String(error),
/Unknown language: `foobar` is not registered/
/Cannot highlight as `foobar`, it’s not registered/
)
}
})
Expand Down Expand Up @@ -362,7 +368,7 @@ test('rehypeHighlight', async function (t) {
// For some reason this isn’t detected as c++.
const file = await rehype()
.data('settings', {fragment: true})
.use(rehypeHighlight, {subset: ['cpp'], detect: true})
.use(rehypeHighlight, {detect: true, subset: ['cpp']})
.process(`<pre><code>def add(a, b):\n return a + b</code></pre>`)

assert.equal(
Expand Down Expand Up @@ -506,8 +512,8 @@ test('rehypeHighlight', async function (t) {
*/
function testLang() {
return {
contains: [],
aliases: ['test'],
contains: [],
keywords: {keyword: 'test'}
}
}
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"declaration": true,
"emitDeclarationOnly": true,
"exactOptionalPropertyTypes": true,
"lib": ["es2020"],
"lib": ["es2022"],
"module": "node16",
"strict": true,
"target": "es2020"
Expand Down

0 comments on commit cad037f

Please sign in to comment.