Skip to content

Commit

Permalink
Move core to lib/
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Dec 8, 2021
1 parent 31b8e01 commit dd58e39
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 156 deletions.
156 changes: 2 additions & 154 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,157 +1,5 @@
/**
* @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
*
* @typedef Options
* Configuration.
* @property {string} [prefix='hljs-']
* Prefix to use before classes.
* @property {boolean|Array<string>} [subset]
* Scope of languages to check when auto-detecting (default: all languages).
* Pass `false` to not highlight code without language classes.
* @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`.
* @typedef {import('./lib/index.js').Options} Options
*/

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

const own = {}.hasOwnProperty

/**
* Plugin to highlight the syntax of code with lowlight (`highlight.js`).
*
* @type {import('unified').Plugin<[Options?] | Array<void>, Root>}
*/
export default function rehypeHighlight(options = {}) {
const {aliases, languages, prefix, plainText, ignoreMissing, subset} = options
let name = 'hljs'

if (aliases) {
lowlight.registerAlias(aliases)
}

if (languages) {
/** @type {string} */
let key

for (key in languages) {
if (own.call(languages, key)) {
lowlight.registerLanguage(key, languages[key])
}
}
}

if (prefix) {
const pos = prefix.indexOf('-')
name = pos > -1 ? prefix.slice(0, pos) : prefix
}

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

if (
!parent ||
!('tagName' in parent) ||
parent.tagName !== 'pre' ||
node.tagName !== 'code' ||
!node.properties
) {
return
}

const lang = language(node)

if (
lang === false ||
(!lang && subset === false) ||
(lang && plainText && plainText.includes(lang))
) {
return
}

if (!Array.isArray(node.properties.className)) {
node.properties.className = []
}

if (!node.properties.className.includes(name)) {
node.properties.className.unshift(name)
}

/** @type {LowlightRoot} */
let result

try {
result = lang
? lowlight.highlight(lang, toText(parent), {prefix})
: // @ts-expect-error: we checked that `subset` is not a boolean.
lowlight.highlightAuto(toText(parent), {prefix, subset})
} catch (error) {
const exception = /** @type {Error} */ (error)
if (!ignoreMissing || !/Unknown language/.test(exception.message)) {
throw error
}

return
}

if (!lang && result.data.language) {
node.properties.className.push('language-' + result.data.language)
}

if (Array.isArray(result.children) && result.children.length > 0) {
node.children = result.children
}
})
}
}

/**
* Get the programming language of `node`.
*
* @param {Element} node
* @returns {false|string|undefined}
*/
function language(node) {
const className = node.properties && node.properties.className
let index = -1

if (!Array.isArray(className)) {
return
}

while (++index < className.length) {
const value = String(className[index])

if (value === 'no-highlight' || value === 'nohighlight') {
return false
}

if (value.slice(0, 5) === 'lang-') {
return value.slice(5)
}

if (value.slice(0, 9) === 'language-') {
return value.slice(9)
}
}
}
export {default} from './lib/index.js'
157 changes: 157 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/**
* @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
*
* @typedef Options
* Configuration.
* @property {string} [prefix='hljs-']
* Prefix to use before classes.
* @property {boolean|Array<string>} [subset]
* Scope of languages to check when auto-detecting (default: all languages).
* Pass `false` to not highlight code without language classes.
* @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`.
*/

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

const own = {}.hasOwnProperty

/**
* Plugin to highlight the syntax of code with lowlight (`highlight.js`).
*
* @type {import('unified').Plugin<[Options?] | Array<void>, Root>}
*/
export default function rehypeHighlight(options = {}) {
const {aliases, languages, prefix, plainText, ignoreMissing, subset} = options
let name = 'hljs'

if (aliases) {
lowlight.registerAlias(aliases)
}

if (languages) {
/** @type {string} */
let key

for (key in languages) {
if (own.call(languages, key)) {
lowlight.registerLanguage(key, languages[key])
}
}
}

if (prefix) {
const pos = prefix.indexOf('-')
name = pos > -1 ? prefix.slice(0, pos) : prefix
}

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

if (
!parent ||
!('tagName' in parent) ||
parent.tagName !== 'pre' ||
node.tagName !== 'code' ||
!node.properties
) {
return
}

const lang = language(node)

if (
lang === false ||
(!lang && subset === false) ||
(lang && plainText && plainText.includes(lang))
) {
return
}

if (!Array.isArray(node.properties.className)) {
node.properties.className = []
}

if (!node.properties.className.includes(name)) {
node.properties.className.unshift(name)
}

/** @type {LowlightRoot} */
let result

try {
result = lang
? lowlight.highlight(lang, toText(parent), {prefix})
: // @ts-expect-error: we checked that `subset` is not a boolean.
lowlight.highlightAuto(toText(parent), {prefix, subset})
} catch (error) {
const exception = /** @type {Error} */ (error)
if (!ignoreMissing || !/Unknown language/.test(exception.message)) {
throw error
}

return
}

if (!lang && result.data.language) {
node.properties.className.push('language-' + result.data.language)
}

if (Array.isArray(result.children) && result.children.length > 0) {
node.children = result.children
}
})
}
}

/**
* Get the programming language of `node`.
*
* @param {Element} node
* @returns {false|string|undefined}
*/
function language(node) {
const className = node.properties && node.properties.className
let index = -1

if (!Array.isArray(className)) {
return
}

while (++index < className.length) {
const value = String(className[index])

if (value === 'no-highlight' || value === 'nohighlight') {
return false
}

if (value.slice(0, 5) === 'lang-') {
return value.slice(5)
}

if (value.slice(0, 9) === 'language-') {
return value.slice(9)
}
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"main": "index.js",
"types": "index.d.ts",
"files": [
"lib/",
"index.d.ts",
"index.js"
],
Expand All @@ -53,7 +54,7 @@
"xo": "^0.47.0"
},
"scripts": {
"build": "rimraf \"*.d.ts\" && tsc && type-coverage",
"build": "rimraf \"lib/**/*.d.ts\" \"*.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node --conditions development test.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"include": ["*.js"],
"include": ["lib/**/*.js", "*.js"],
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020"],
Expand Down

0 comments on commit dd58e39

Please sign in to comment.