astro-ghostcms/.pnpm-store/v3/files/de/caf6b943dc4e179a93767dcdad4...

5106 lines
140 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { StackElementMetadata, INITIAL, Registry as Registry$1 } from './textmate.mjs';
import { FontStyle } from './types.mjs';
/**
* List of HTML void tag names.
*
* @type {Array<string>}
*/
const htmlVoidElements = [
'area',
'base',
'basefont',
'bgsound',
'br',
'col',
'command',
'embed',
'frame',
'hr',
'image',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr'
];
/**
* @typedef {import('./info.js').Info} Info
* @typedef {Record<string, Info>} Properties
* @typedef {Record<string, string>} Normal
*/
class Schema {
/**
* @constructor
* @param {Properties} property
* @param {Normal} normal
* @param {string} [space]
*/
constructor(property, normal, space) {
this.property = property;
this.normal = normal;
if (space) {
this.space = space;
}
}
}
/** @type {Properties} */
Schema.prototype.property = {};
/** @type {Normal} */
Schema.prototype.normal = {};
/** @type {string|null} */
Schema.prototype.space = null;
/**
* @typedef {import('./schema.js').Properties} Properties
* @typedef {import('./schema.js').Normal} Normal
*/
/**
* @param {Schema[]} definitions
* @param {string} [space]
* @returns {Schema}
*/
function merge(definitions, space) {
/** @type {Properties} */
const property = {};
/** @type {Normal} */
const normal = {};
let index = -1;
while (++index < definitions.length) {
Object.assign(property, definitions[index].property);
Object.assign(normal, definitions[index].normal);
}
return new Schema(property, normal, space)
}
/**
* @param {string} value
* @returns {string}
*/
function normalize(value) {
return value.toLowerCase()
}
class Info {
/**
* @constructor
* @param {string} property
* @param {string} attribute
*/
constructor(property, attribute) {
/** @type {string} */
this.property = property;
/** @type {string} */
this.attribute = attribute;
}
}
/** @type {string|null} */
Info.prototype.space = null;
Info.prototype.boolean = false;
Info.prototype.booleanish = false;
Info.prototype.overloadedBoolean = false;
Info.prototype.number = false;
Info.prototype.commaSeparated = false;
Info.prototype.spaceSeparated = false;
Info.prototype.commaOrSpaceSeparated = false;
Info.prototype.mustUseProperty = false;
Info.prototype.defined = false;
let powers = 0;
const boolean = increment();
const booleanish = increment();
const overloadedBoolean = increment();
const number = increment();
const spaceSeparated = increment();
const commaSeparated = increment();
const commaOrSpaceSeparated = increment();
function increment() {
return 2 ** ++powers
}
var types = /*#__PURE__*/Object.freeze({
__proto__: null,
boolean: boolean,
booleanish: booleanish,
commaOrSpaceSeparated: commaOrSpaceSeparated,
commaSeparated: commaSeparated,
number: number,
overloadedBoolean: overloadedBoolean,
spaceSeparated: spaceSeparated
});
/** @type {Array<keyof types>} */
// @ts-expect-error: hush.
const checks = Object.keys(types);
class DefinedInfo extends Info {
/**
* @constructor
* @param {string} property
* @param {string} attribute
* @param {number|null} [mask]
* @param {string} [space]
*/
constructor(property, attribute, mask, space) {
let index = -1;
super(property, attribute);
mark(this, 'space', space);
if (typeof mask === 'number') {
while (++index < checks.length) {
const check = checks[index];
mark(this, checks[index], (mask & types[check]) === types[check]);
}
}
}
}
DefinedInfo.prototype.defined = true;
/**
* @param {DefinedInfo} values
* @param {string} key
* @param {unknown} value
*/
function mark(values, key, value) {
if (value) {
// @ts-expect-error: assume `value` matches the expected value of `key`.
values[key] = value;
}
}
/**
* @typedef {import('./schema.js').Properties} Properties
* @typedef {import('./schema.js').Normal} Normal
*
* @typedef {Record<string, string>} Attributes
*
* @typedef {Object} Definition
* @property {Record<string, number|null>} properties
* @property {(attributes: Attributes, property: string) => string} transform
* @property {string} [space]
* @property {Attributes} [attributes]
* @property {Array<string>} [mustUseProperty]
*/
const own$3 = {}.hasOwnProperty;
/**
* @param {Definition} definition
* @returns {Schema}
*/
function create(definition) {
/** @type {Properties} */
const property = {};
/** @type {Normal} */
const normal = {};
/** @type {string} */
let prop;
for (prop in definition.properties) {
if (own$3.call(definition.properties, prop)) {
const value = definition.properties[prop];
const info = new DefinedInfo(
prop,
definition.transform(definition.attributes || {}, prop),
value,
definition.space
);
if (
definition.mustUseProperty &&
definition.mustUseProperty.includes(prop)
) {
info.mustUseProperty = true;
}
property[prop] = info;
normal[normalize(prop)] = prop;
normal[normalize(info.attribute)] = prop;
}
}
return new Schema(property, normal, definition.space)
}
const xlink = create({
space: 'xlink',
transform(_, prop) {
return 'xlink:' + prop.slice(5).toLowerCase()
},
properties: {
xLinkActuate: null,
xLinkArcRole: null,
xLinkHref: null,
xLinkRole: null,
xLinkShow: null,
xLinkTitle: null,
xLinkType: null
}
});
const xml = create({
space: 'xml',
transform(_, prop) {
return 'xml:' + prop.slice(3).toLowerCase()
},
properties: {xmlLang: null, xmlBase: null, xmlSpace: null}
});
/**
* @param {Record<string, string>} attributes
* @param {string} attribute
* @returns {string}
*/
function caseSensitiveTransform(attributes, attribute) {
return attribute in attributes ? attributes[attribute] : attribute
}
/**
* @param {Record<string, string>} attributes
* @param {string} property
* @returns {string}
*/
function caseInsensitiveTransform(attributes, property) {
return caseSensitiveTransform(attributes, property.toLowerCase())
}
const xmlns = create({
space: 'xmlns',
attributes: {xmlnsxlink: 'xmlns:xlink'},
transform: caseInsensitiveTransform,
properties: {xmlns: null, xmlnsXLink: null}
});
const aria = create({
transform(_, prop) {
return prop === 'role' ? prop : 'aria-' + prop.slice(4).toLowerCase()
},
properties: {
ariaActiveDescendant: null,
ariaAtomic: booleanish,
ariaAutoComplete: null,
ariaBusy: booleanish,
ariaChecked: booleanish,
ariaColCount: number,
ariaColIndex: number,
ariaColSpan: number,
ariaControls: spaceSeparated,
ariaCurrent: null,
ariaDescribedBy: spaceSeparated,
ariaDetails: null,
ariaDisabled: booleanish,
ariaDropEffect: spaceSeparated,
ariaErrorMessage: null,
ariaExpanded: booleanish,
ariaFlowTo: spaceSeparated,
ariaGrabbed: booleanish,
ariaHasPopup: null,
ariaHidden: booleanish,
ariaInvalid: null,
ariaKeyShortcuts: null,
ariaLabel: null,
ariaLabelledBy: spaceSeparated,
ariaLevel: number,
ariaLive: null,
ariaModal: booleanish,
ariaMultiLine: booleanish,
ariaMultiSelectable: booleanish,
ariaOrientation: null,
ariaOwns: spaceSeparated,
ariaPlaceholder: null,
ariaPosInSet: number,
ariaPressed: booleanish,
ariaReadOnly: booleanish,
ariaRelevant: null,
ariaRequired: booleanish,
ariaRoleDescription: spaceSeparated,
ariaRowCount: number,
ariaRowIndex: number,
ariaRowSpan: number,
ariaSelected: booleanish,
ariaSetSize: number,
ariaSort: null,
ariaValueMax: number,
ariaValueMin: number,
ariaValueNow: number,
ariaValueText: null,
role: null
}
});
const html$3 = create({
space: 'html',
attributes: {
acceptcharset: 'accept-charset',
classname: 'class',
htmlfor: 'for',
httpequiv: 'http-equiv'
},
transform: caseInsensitiveTransform,
mustUseProperty: ['checked', 'multiple', 'muted', 'selected'],
properties: {
// Standard Properties.
abbr: null,
accept: commaSeparated,
acceptCharset: spaceSeparated,
accessKey: spaceSeparated,
action: null,
allow: null,
allowFullScreen: boolean,
allowPaymentRequest: boolean,
allowUserMedia: boolean,
alt: null,
as: null,
async: boolean,
autoCapitalize: null,
autoComplete: spaceSeparated,
autoFocus: boolean,
autoPlay: boolean,
capture: boolean,
charSet: null,
checked: boolean,
cite: null,
className: spaceSeparated,
cols: number,
colSpan: null,
content: null,
contentEditable: booleanish,
controls: boolean,
controlsList: spaceSeparated,
coords: number | commaSeparated,
crossOrigin: null,
data: null,
dateTime: null,
decoding: null,
default: boolean,
defer: boolean,
dir: null,
dirName: null,
disabled: boolean,
download: overloadedBoolean,
draggable: booleanish,
encType: null,
enterKeyHint: null,
form: null,
formAction: null,
formEncType: null,
formMethod: null,
formNoValidate: boolean,
formTarget: null,
headers: spaceSeparated,
height: number,
hidden: boolean,
high: number,
href: null,
hrefLang: null,
htmlFor: spaceSeparated,
httpEquiv: spaceSeparated,
id: null,
imageSizes: null,
imageSrcSet: null,
inputMode: null,
integrity: null,
is: null,
isMap: boolean,
itemId: null,
itemProp: spaceSeparated,
itemRef: spaceSeparated,
itemScope: boolean,
itemType: spaceSeparated,
kind: null,
label: null,
lang: null,
language: null,
list: null,
loading: null,
loop: boolean,
low: number,
manifest: null,
max: null,
maxLength: number,
media: null,
method: null,
min: null,
minLength: number,
multiple: boolean,
muted: boolean,
name: null,
nonce: null,
noModule: boolean,
noValidate: boolean,
onAbort: null,
onAfterPrint: null,
onAuxClick: null,
onBeforeMatch: null,
onBeforePrint: null,
onBeforeUnload: null,
onBlur: null,
onCancel: null,
onCanPlay: null,
onCanPlayThrough: null,
onChange: null,
onClick: null,
onClose: null,
onContextLost: null,
onContextMenu: null,
onContextRestored: null,
onCopy: null,
onCueChange: null,
onCut: null,
onDblClick: null,
onDrag: null,
onDragEnd: null,
onDragEnter: null,
onDragExit: null,
onDragLeave: null,
onDragOver: null,
onDragStart: null,
onDrop: null,
onDurationChange: null,
onEmptied: null,
onEnded: null,
onError: null,
onFocus: null,
onFormData: null,
onHashChange: null,
onInput: null,
onInvalid: null,
onKeyDown: null,
onKeyPress: null,
onKeyUp: null,
onLanguageChange: null,
onLoad: null,
onLoadedData: null,
onLoadedMetadata: null,
onLoadEnd: null,
onLoadStart: null,
onMessage: null,
onMessageError: null,
onMouseDown: null,
onMouseEnter: null,
onMouseLeave: null,
onMouseMove: null,
onMouseOut: null,
onMouseOver: null,
onMouseUp: null,
onOffline: null,
onOnline: null,
onPageHide: null,
onPageShow: null,
onPaste: null,
onPause: null,
onPlay: null,
onPlaying: null,
onPopState: null,
onProgress: null,
onRateChange: null,
onRejectionHandled: null,
onReset: null,
onResize: null,
onScroll: null,
onScrollEnd: null,
onSecurityPolicyViolation: null,
onSeeked: null,
onSeeking: null,
onSelect: null,
onSlotChange: null,
onStalled: null,
onStorage: null,
onSubmit: null,
onSuspend: null,
onTimeUpdate: null,
onToggle: null,
onUnhandledRejection: null,
onUnload: null,
onVolumeChange: null,
onWaiting: null,
onWheel: null,
open: boolean,
optimum: number,
pattern: null,
ping: spaceSeparated,
placeholder: null,
playsInline: boolean,
poster: null,
preload: null,
readOnly: boolean,
referrerPolicy: null,
rel: spaceSeparated,
required: boolean,
reversed: boolean,
rows: number,
rowSpan: number,
sandbox: spaceSeparated,
scope: null,
scoped: boolean,
seamless: boolean,
selected: boolean,
shape: null,
size: number,
sizes: null,
slot: null,
span: number,
spellCheck: booleanish,
src: null,
srcDoc: null,
srcLang: null,
srcSet: null,
start: number,
step: null,
style: null,
tabIndex: number,
target: null,
title: null,
translate: null,
type: null,
typeMustMatch: boolean,
useMap: null,
value: booleanish,
width: number,
wrap: null,
// Legacy.
// See: https://html.spec.whatwg.org/#other-elements,-attributes-and-apis
align: null, // Several. Use CSS `text-align` instead,
aLink: null, // `<body>`. Use CSS `a:active {color}` instead
archive: spaceSeparated, // `<object>`. List of URIs to archives
axis: null, // `<td>` and `<th>`. Use `scope` on `<th>`
background: null, // `<body>`. Use CSS `background-image` instead
bgColor: null, // `<body>` and table elements. Use CSS `background-color` instead
border: number, // `<table>`. Use CSS `border-width` instead,
borderColor: null, // `<table>`. Use CSS `border-color` instead,
bottomMargin: number, // `<body>`
cellPadding: null, // `<table>`
cellSpacing: null, // `<table>`
char: null, // Several table elements. When `align=char`, sets the character to align on
charOff: null, // Several table elements. When `char`, offsets the alignment
classId: null, // `<object>`
clear: null, // `<br>`. Use CSS `clear` instead
code: null, // `<object>`
codeBase: null, // `<object>`
codeType: null, // `<object>`
color: null, // `<font>` and `<hr>`. Use CSS instead
compact: boolean, // Lists. Use CSS to reduce space between items instead
declare: boolean, // `<object>`
event: null, // `<script>`
face: null, // `<font>`. Use CSS instead
frame: null, // `<table>`
frameBorder: null, // `<iframe>`. Use CSS `border` instead
hSpace: number, // `<img>` and `<object>`
leftMargin: number, // `<body>`
link: null, // `<body>`. Use CSS `a:link {color: *}` instead
longDesc: null, // `<frame>`, `<iframe>`, and `<img>`. Use an `<a>`
lowSrc: null, // `<img>`. Use a `<picture>`
marginHeight: number, // `<body>`
marginWidth: number, // `<body>`
noResize: boolean, // `<frame>`
noHref: boolean, // `<area>`. Use no href instead of an explicit `nohref`
noShade: boolean, // `<hr>`. Use background-color and height instead of borders
noWrap: boolean, // `<td>` and `<th>`
object: null, // `<applet>`
profile: null, // `<head>`
prompt: null, // `<isindex>`
rev: null, // `<link>`
rightMargin: number, // `<body>`
rules: null, // `<table>`
scheme: null, // `<meta>`
scrolling: booleanish, // `<frame>`. Use overflow in the child context
standby: null, // `<object>`
summary: null, // `<table>`
text: null, // `<body>`. Use CSS `color` instead
topMargin: number, // `<body>`
valueType: null, // `<param>`
version: null, // `<html>`. Use a doctype.
vAlign: null, // Several. Use CSS `vertical-align` instead
vLink: null, // `<body>`. Use CSS `a:visited {color}` instead
vSpace: number, // `<img>` and `<object>`
// Non-standard Properties.
allowTransparency: null,
autoCorrect: null,
autoSave: null,
disablePictureInPicture: boolean,
disableRemotePlayback: boolean,
prefix: null,
property: null,
results: number,
security: null,
unselectable: null
}
});
const svg$1 = create({
space: 'svg',
attributes: {
accentHeight: 'accent-height',
alignmentBaseline: 'alignment-baseline',
arabicForm: 'arabic-form',
baselineShift: 'baseline-shift',
capHeight: 'cap-height',
className: 'class',
clipPath: 'clip-path',
clipRule: 'clip-rule',
colorInterpolation: 'color-interpolation',
colorInterpolationFilters: 'color-interpolation-filters',
colorProfile: 'color-profile',
colorRendering: 'color-rendering',
crossOrigin: 'crossorigin',
dataType: 'datatype',
dominantBaseline: 'dominant-baseline',
enableBackground: 'enable-background',
fillOpacity: 'fill-opacity',
fillRule: 'fill-rule',
floodColor: 'flood-color',
floodOpacity: 'flood-opacity',
fontFamily: 'font-family',
fontSize: 'font-size',
fontSizeAdjust: 'font-size-adjust',
fontStretch: 'font-stretch',
fontStyle: 'font-style',
fontVariant: 'font-variant',
fontWeight: 'font-weight',
glyphName: 'glyph-name',
glyphOrientationHorizontal: 'glyph-orientation-horizontal',
glyphOrientationVertical: 'glyph-orientation-vertical',
hrefLang: 'hreflang',
horizAdvX: 'horiz-adv-x',
horizOriginX: 'horiz-origin-x',
horizOriginY: 'horiz-origin-y',
imageRendering: 'image-rendering',
letterSpacing: 'letter-spacing',
lightingColor: 'lighting-color',
markerEnd: 'marker-end',
markerMid: 'marker-mid',
markerStart: 'marker-start',
navDown: 'nav-down',
navDownLeft: 'nav-down-left',
navDownRight: 'nav-down-right',
navLeft: 'nav-left',
navNext: 'nav-next',
navPrev: 'nav-prev',
navRight: 'nav-right',
navUp: 'nav-up',
navUpLeft: 'nav-up-left',
navUpRight: 'nav-up-right',
onAbort: 'onabort',
onActivate: 'onactivate',
onAfterPrint: 'onafterprint',
onBeforePrint: 'onbeforeprint',
onBegin: 'onbegin',
onCancel: 'oncancel',
onCanPlay: 'oncanplay',
onCanPlayThrough: 'oncanplaythrough',
onChange: 'onchange',
onClick: 'onclick',
onClose: 'onclose',
onCopy: 'oncopy',
onCueChange: 'oncuechange',
onCut: 'oncut',
onDblClick: 'ondblclick',
onDrag: 'ondrag',
onDragEnd: 'ondragend',
onDragEnter: 'ondragenter',
onDragExit: 'ondragexit',
onDragLeave: 'ondragleave',
onDragOver: 'ondragover',
onDragStart: 'ondragstart',
onDrop: 'ondrop',
onDurationChange: 'ondurationchange',
onEmptied: 'onemptied',
onEnd: 'onend',
onEnded: 'onended',
onError: 'onerror',
onFocus: 'onfocus',
onFocusIn: 'onfocusin',
onFocusOut: 'onfocusout',
onHashChange: 'onhashchange',
onInput: 'oninput',
onInvalid: 'oninvalid',
onKeyDown: 'onkeydown',
onKeyPress: 'onkeypress',
onKeyUp: 'onkeyup',
onLoad: 'onload',
onLoadedData: 'onloadeddata',
onLoadedMetadata: 'onloadedmetadata',
onLoadStart: 'onloadstart',
onMessage: 'onmessage',
onMouseDown: 'onmousedown',
onMouseEnter: 'onmouseenter',
onMouseLeave: 'onmouseleave',
onMouseMove: 'onmousemove',
onMouseOut: 'onmouseout',
onMouseOver: 'onmouseover',
onMouseUp: 'onmouseup',
onMouseWheel: 'onmousewheel',
onOffline: 'onoffline',
onOnline: 'ononline',
onPageHide: 'onpagehide',
onPageShow: 'onpageshow',
onPaste: 'onpaste',
onPause: 'onpause',
onPlay: 'onplay',
onPlaying: 'onplaying',
onPopState: 'onpopstate',
onProgress: 'onprogress',
onRateChange: 'onratechange',
onRepeat: 'onrepeat',
onReset: 'onreset',
onResize: 'onresize',
onScroll: 'onscroll',
onSeeked: 'onseeked',
onSeeking: 'onseeking',
onSelect: 'onselect',
onShow: 'onshow',
onStalled: 'onstalled',
onStorage: 'onstorage',
onSubmit: 'onsubmit',
onSuspend: 'onsuspend',
onTimeUpdate: 'ontimeupdate',
onToggle: 'ontoggle',
onUnload: 'onunload',
onVolumeChange: 'onvolumechange',
onWaiting: 'onwaiting',
onZoom: 'onzoom',
overlinePosition: 'overline-position',
overlineThickness: 'overline-thickness',
paintOrder: 'paint-order',
panose1: 'panose-1',
pointerEvents: 'pointer-events',
referrerPolicy: 'referrerpolicy',
renderingIntent: 'rendering-intent',
shapeRendering: 'shape-rendering',
stopColor: 'stop-color',
stopOpacity: 'stop-opacity',
strikethroughPosition: 'strikethrough-position',
strikethroughThickness: 'strikethrough-thickness',
strokeDashArray: 'stroke-dasharray',
strokeDashOffset: 'stroke-dashoffset',
strokeLineCap: 'stroke-linecap',
strokeLineJoin: 'stroke-linejoin',
strokeMiterLimit: 'stroke-miterlimit',
strokeOpacity: 'stroke-opacity',
strokeWidth: 'stroke-width',
tabIndex: 'tabindex',
textAnchor: 'text-anchor',
textDecoration: 'text-decoration',
textRendering: 'text-rendering',
typeOf: 'typeof',
underlinePosition: 'underline-position',
underlineThickness: 'underline-thickness',
unicodeBidi: 'unicode-bidi',
unicodeRange: 'unicode-range',
unitsPerEm: 'units-per-em',
vAlphabetic: 'v-alphabetic',
vHanging: 'v-hanging',
vIdeographic: 'v-ideographic',
vMathematical: 'v-mathematical',
vectorEffect: 'vector-effect',
vertAdvY: 'vert-adv-y',
vertOriginX: 'vert-origin-x',
vertOriginY: 'vert-origin-y',
wordSpacing: 'word-spacing',
writingMode: 'writing-mode',
xHeight: 'x-height',
// These were camelcased in Tiny. Now lowercased in SVG 2
playbackOrder: 'playbackorder',
timelineBegin: 'timelinebegin'
},
transform: caseSensitiveTransform,
properties: {
about: commaOrSpaceSeparated,
accentHeight: number,
accumulate: null,
additive: null,
alignmentBaseline: null,
alphabetic: number,
amplitude: number,
arabicForm: null,
ascent: number,
attributeName: null,
attributeType: null,
azimuth: number,
bandwidth: null,
baselineShift: null,
baseFrequency: null,
baseProfile: null,
bbox: null,
begin: null,
bias: number,
by: null,
calcMode: null,
capHeight: number,
className: spaceSeparated,
clip: null,
clipPath: null,
clipPathUnits: null,
clipRule: null,
color: null,
colorInterpolation: null,
colorInterpolationFilters: null,
colorProfile: null,
colorRendering: null,
content: null,
contentScriptType: null,
contentStyleType: null,
crossOrigin: null,
cursor: null,
cx: null,
cy: null,
d: null,
dataType: null,
defaultAction: null,
descent: number,
diffuseConstant: number,
direction: null,
display: null,
dur: null,
divisor: number,
dominantBaseline: null,
download: boolean,
dx: null,
dy: null,
edgeMode: null,
editable: null,
elevation: number,
enableBackground: null,
end: null,
event: null,
exponent: number,
externalResourcesRequired: null,
fill: null,
fillOpacity: number,
fillRule: null,
filter: null,
filterRes: null,
filterUnits: null,
floodColor: null,
floodOpacity: null,
focusable: null,
focusHighlight: null,
fontFamily: null,
fontSize: null,
fontSizeAdjust: null,
fontStretch: null,
fontStyle: null,
fontVariant: null,
fontWeight: null,
format: null,
fr: null,
from: null,
fx: null,
fy: null,
g1: commaSeparated,
g2: commaSeparated,
glyphName: commaSeparated,
glyphOrientationHorizontal: null,
glyphOrientationVertical: null,
glyphRef: null,
gradientTransform: null,
gradientUnits: null,
handler: null,
hanging: number,
hatchContentUnits: null,
hatchUnits: null,
height: null,
href: null,
hrefLang: null,
horizAdvX: number,
horizOriginX: number,
horizOriginY: number,
id: null,
ideographic: number,
imageRendering: null,
initialVisibility: null,
in: null,
in2: null,
intercept: number,
k: number,
k1: number,
k2: number,
k3: number,
k4: number,
kernelMatrix: commaOrSpaceSeparated,
kernelUnitLength: null,
keyPoints: null, // SEMI_COLON_SEPARATED
keySplines: null, // SEMI_COLON_SEPARATED
keyTimes: null, // SEMI_COLON_SEPARATED
kerning: null,
lang: null,
lengthAdjust: null,
letterSpacing: null,
lightingColor: null,
limitingConeAngle: number,
local: null,
markerEnd: null,
markerMid: null,
markerStart: null,
markerHeight: null,
markerUnits: null,
markerWidth: null,
mask: null,
maskContentUnits: null,
maskUnits: null,
mathematical: null,
max: null,
media: null,
mediaCharacterEncoding: null,
mediaContentEncodings: null,
mediaSize: number,
mediaTime: null,
method: null,
min: null,
mode: null,
name: null,
navDown: null,
navDownLeft: null,
navDownRight: null,
navLeft: null,
navNext: null,
navPrev: null,
navRight: null,
navUp: null,
navUpLeft: null,
navUpRight: null,
numOctaves: null,
observer: null,
offset: null,
onAbort: null,
onActivate: null,
onAfterPrint: null,
onBeforePrint: null,
onBegin: null,
onCancel: null,
onCanPlay: null,
onCanPlayThrough: null,
onChange: null,
onClick: null,
onClose: null,
onCopy: null,
onCueChange: null,
onCut: null,
onDblClick: null,
onDrag: null,
onDragEnd: null,
onDragEnter: null,
onDragExit: null,
onDragLeave: null,
onDragOver: null,
onDragStart: null,
onDrop: null,
onDurationChange: null,
onEmptied: null,
onEnd: null,
onEnded: null,
onError: null,
onFocus: null,
onFocusIn: null,
onFocusOut: null,
onHashChange: null,
onInput: null,
onInvalid: null,
onKeyDown: null,
onKeyPress: null,
onKeyUp: null,
onLoad: null,
onLoadedData: null,
onLoadedMetadata: null,
onLoadStart: null,
onMessage: null,
onMouseDown: null,
onMouseEnter: null,
onMouseLeave: null,
onMouseMove: null,
onMouseOut: null,
onMouseOver: null,
onMouseUp: null,
onMouseWheel: null,
onOffline: null,
onOnline: null,
onPageHide: null,
onPageShow: null,
onPaste: null,
onPause: null,
onPlay: null,
onPlaying: null,
onPopState: null,
onProgress: null,
onRateChange: null,
onRepeat: null,
onReset: null,
onResize: null,
onScroll: null,
onSeeked: null,
onSeeking: null,
onSelect: null,
onShow: null,
onStalled: null,
onStorage: null,
onSubmit: null,
onSuspend: null,
onTimeUpdate: null,
onToggle: null,
onUnload: null,
onVolumeChange: null,
onWaiting: null,
onZoom: null,
opacity: null,
operator: null,
order: null,
orient: null,
orientation: null,
origin: null,
overflow: null,
overlay: null,
overlinePosition: number,
overlineThickness: number,
paintOrder: null,
panose1: null,
path: null,
pathLength: number,
patternContentUnits: null,
patternTransform: null,
patternUnits: null,
phase: null,
ping: spaceSeparated,
pitch: null,
playbackOrder: null,
pointerEvents: null,
points: null,
pointsAtX: number,
pointsAtY: number,
pointsAtZ: number,
preserveAlpha: null,
preserveAspectRatio: null,
primitiveUnits: null,
propagate: null,
property: commaOrSpaceSeparated,
r: null,
radius: null,
referrerPolicy: null,
refX: null,
refY: null,
rel: commaOrSpaceSeparated,
rev: commaOrSpaceSeparated,
renderingIntent: null,
repeatCount: null,
repeatDur: null,
requiredExtensions: commaOrSpaceSeparated,
requiredFeatures: commaOrSpaceSeparated,
requiredFonts: commaOrSpaceSeparated,
requiredFormats: commaOrSpaceSeparated,
resource: null,
restart: null,
result: null,
rotate: null,
rx: null,
ry: null,
scale: null,
seed: null,
shapeRendering: null,
side: null,
slope: null,
snapshotTime: null,
specularConstant: number,
specularExponent: number,
spreadMethod: null,
spacing: null,
startOffset: null,
stdDeviation: null,
stemh: null,
stemv: null,
stitchTiles: null,
stopColor: null,
stopOpacity: null,
strikethroughPosition: number,
strikethroughThickness: number,
string: null,
stroke: null,
strokeDashArray: commaOrSpaceSeparated,
strokeDashOffset: null,
strokeLineCap: null,
strokeLineJoin: null,
strokeMiterLimit: number,
strokeOpacity: number,
strokeWidth: null,
style: null,
surfaceScale: number,
syncBehavior: null,
syncBehaviorDefault: null,
syncMaster: null,
syncTolerance: null,
syncToleranceDefault: null,
systemLanguage: commaOrSpaceSeparated,
tabIndex: number,
tableValues: null,
target: null,
targetX: number,
targetY: number,
textAnchor: null,
textDecoration: null,
textRendering: null,
textLength: null,
timelineBegin: null,
title: null,
transformBehavior: null,
type: null,
typeOf: commaOrSpaceSeparated,
to: null,
transform: null,
u1: null,
u2: null,
underlinePosition: number,
underlineThickness: number,
unicode: null,
unicodeBidi: null,
unicodeRange: null,
unitsPerEm: number,
values: null,
vAlphabetic: number,
vMathematical: number,
vectorEffect: null,
vHanging: number,
vIdeographic: number,
version: null,
vertAdvY: number,
vertOriginX: number,
vertOriginY: number,
viewBox: null,
viewTarget: null,
visibility: null,
width: null,
widths: null,
wordSpacing: null,
writingMode: null,
x: null,
x1: null,
x2: null,
xChannelSelector: null,
xHeight: number,
y: null,
y1: null,
y2: null,
yChannelSelector: null,
z: null,
zoomAndPan: null
}
});
/**
* @typedef {import('./util/schema.js').Schema} Schema
*/
const valid = /^data[-\w.:]+$/i;
const dash = /-[a-z]/g;
const cap = /[A-Z]/g;
/**
* @param {Schema} schema
* @param {string} value
* @returns {Info}
*/
function find(schema, value) {
const normal = normalize(value);
let prop = value;
let Type = Info;
if (normal in schema.normal) {
return schema.property[schema.normal[normal]]
}
if (normal.length > 4 && normal.slice(0, 4) === 'data' && valid.test(value)) {
// Attribute or property.
if (value.charAt(4) === '-') {
// Turn it into a property.
const rest = value.slice(5).replace(dash, camelcase);
prop = 'data' + rest.charAt(0).toUpperCase() + rest.slice(1);
} else {
// Turn it into an attribute.
const rest = value.slice(4);
if (!dash.test(rest)) {
let dashes = rest.replace(cap, kebab);
if (dashes.charAt(0) !== '-') {
dashes = '-' + dashes;
}
value = 'data' + dashes;
}
}
Type = DefinedInfo;
}
return new Type(prop, value)
}
/**
* @param {string} $0
* @returns {string}
*/
function kebab($0) {
return '-' + $0.toLowerCase()
}
/**
* @param {string} $0
* @returns {string}
*/
function camelcase($0) {
return $0.charAt(1).toUpperCase()
}
/**
* @typedef {import('./lib/util/info.js').Info} Info
* @typedef {import('./lib/util/schema.js').Schema} Schema
*/
const html$2 = merge([xml, xlink, xmlns, aria, html$3], 'html');
const svg = merge([xml, xlink, xmlns, aria, svg$1], 'svg');
/**
* @callback Handler
* Handle a value, with a certain ID field set to a certain value.
* The ID field is passed to `zwitch`, and its value is this functions
* place on the `handlers` record.
* @param {...any} parameters
* Arbitrary parameters passed to the zwitch.
* The first will be an object with a certain ID field set to a certain value.
* @returns {any}
* Anything!
*/
/**
* @callback UnknownHandler
* Handle values that do have a certain ID field, but its set to a value
* that is not listed in the `handlers` record.
* @param {unknown} value
* An object with a certain ID field set to an unknown value.
* @param {...any} rest
* Arbitrary parameters passed to the zwitch.
* @returns {any}
* Anything!
*/
/**
* @callback InvalidHandler
* Handle values that do not have a certain ID field.
* @param {unknown} value
* Any unknown value.
* @param {...any} rest
* Arbitrary parameters passed to the zwitch.
* @returns {void|null|undefined|never}
* This should crash or return nothing.
*/
/**
* @template {InvalidHandler} [Invalid=InvalidHandler]
* @template {UnknownHandler} [Unknown=UnknownHandler]
* @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
* @typedef Options
* Configuration (required).
* @property {Invalid} [invalid]
* Handler to use for invalid values.
* @property {Unknown} [unknown]
* Handler to use for unknown values.
* @property {Handlers} [handlers]
* Handlers to use.
*/
const own$2 = {}.hasOwnProperty;
/**
* Handle values based on a field.
*
* @template {InvalidHandler} [Invalid=InvalidHandler]
* @template {UnknownHandler} [Unknown=UnknownHandler]
* @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
* @param {string} key
* Field to switch on.
* @param {Options<Invalid, Unknown, Handlers>} [options]
* Configuration (required).
* @returns {{unknown: Unknown, invalid: Invalid, handlers: Handlers, (...parameters: Parameters<Handlers[keyof Handlers]>): ReturnType<Handlers[keyof Handlers]>, (...parameters: Parameters<Unknown>): ReturnType<Unknown>}}
*/
function zwitch(key, options) {
const settings = options || {};
/**
* Handle one value.
*
* Based on the bound `key`, a respective handler will be called.
* If `value` is not an object, or doesnt have a `key` property, the special
* “invalid” handler will be called.
* If `value` has an unknown `key`, the special “unknown” handler will be
* called.
*
* All arguments, and the context object, are passed through to the handler,
* and its result is returned.
*
* @this {unknown}
* Any context object.
* @param {unknown} [value]
* Any value.
* @param {...unknown} parameters
* Arbitrary parameters passed to the zwitch.
* @property {Handler} invalid
* Handle for values that do not have a certain ID field.
* @property {Handler} unknown
* Handle values that do have a certain ID field, but its set to a value
* that is not listed in the `handlers` record.
* @property {Handlers} handlers
* Record of handlers.
* @returns {unknown}
* Anything.
*/
function one(value, ...parameters) {
/** @type {Handler|undefined} */
let fn = one.invalid;
const handlers = one.handlers;
if (value && own$2.call(value, key)) {
// @ts-expect-error Indexable.
const id = String(value[key]);
// @ts-expect-error Indexable.
fn = own$2.call(handlers, id) ? handlers[id] : one.unknown;
}
if (fn) {
return fn.call(this, value, ...parameters)
}
}
one.handlers = settings.handlers || {};
one.invalid = settings.invalid;
one.unknown = settings.unknown;
// @ts-expect-error: matches!
return one
}
/**
* @typedef CoreOptions
* @property {Array<string>} [subset=[]]
* Whether to only escape the given subset of characters.
* @property {boolean} [escapeOnly=false]
* Whether to only escape possibly dangerous characters.
* Those characters are `"`, `&`, `'`, `<`, `>`, and `` ` ``.
*
* @typedef FormatOptions
* @property {(code: number, next: number, options: CoreWithFormatOptions) => string} format
* Format strategy.
*
* @typedef {CoreOptions & FormatOptions & import('./util/format-smart.js').FormatSmartOptions} CoreWithFormatOptions
*/
/**
* Encode certain characters in `value`.
*
* @param {string} value
* @param {CoreWithFormatOptions} options
* @returns {string}
*/
function core(value, options) {
value = value.replace(
options.subset ? charactersToExpression(options.subset) : /["&'<>`]/g,
basic
);
if (options.subset || options.escapeOnly) {
return value
}
return (
value
// Surrogate pairs.
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, surrogate)
// BMP control characters (C0 except for LF, CR, SP; DEL; and some more
// non-ASCII ones).
.replace(
// eslint-disable-next-line no-control-regex, unicorn/no-hex-escape
/[\x01-\t\v\f\x0E-\x1F\x7F\x81\x8D\x8F\x90\x9D\xA0-\uFFFF]/g,
basic
)
)
/**
* @param {string} pair
* @param {number} index
* @param {string} all
*/
function surrogate(pair, index, all) {
return options.format(
(pair.charCodeAt(0) - 0xd800) * 0x400 +
pair.charCodeAt(1) -
0xdc00 +
0x10000,
all.charCodeAt(index + 2),
options
)
}
/**
* @param {string} character
* @param {number} index
* @param {string} all
*/
function basic(character, index, all) {
return options.format(
character.charCodeAt(0),
all.charCodeAt(index + 1),
options
)
}
}
/**
* @param {Array<string>} subset
* @returns {RegExp}
*/
function charactersToExpression(subset) {
/** @type {Array<string>} */
const groups = [];
let index = -1;
while (++index < subset.length) {
groups.push(subset[index].replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'));
}
return new RegExp('(?:' + groups.join('|') + ')', 'g')
}
/**
* Configurable ways to encode characters as hexadecimal references.
*
* @param {number} code
* @param {number} next
* @param {boolean|undefined} omit
* @returns {string}
*/
function toHexadecimal(code, next, omit) {
const value = '&#x' + code.toString(16).toUpperCase();
return omit && next && !/[\dA-Fa-f]/.test(String.fromCharCode(next))
? value
: value + ';'
}
/**
* Configurable ways to encode characters as decimal references.
*
* @param {number} code
* @param {number} next
* @param {boolean|undefined} omit
* @returns {string}
*/
function toDecimal(code, next, omit) {
const value = '&#' + String(code);
return omit && next && !/\d/.test(String.fromCharCode(next))
? value
: value + ';'
}
/**
* List of legacy HTML named character references that dont need a trailing semicolon.
*
* @type {Array<string>}
*/
const characterEntitiesLegacy = [
'AElig',
'AMP',
'Aacute',
'Acirc',
'Agrave',
'Aring',
'Atilde',
'Auml',
'COPY',
'Ccedil',
'ETH',
'Eacute',
'Ecirc',
'Egrave',
'Euml',
'GT',
'Iacute',
'Icirc',
'Igrave',
'Iuml',
'LT',
'Ntilde',
'Oacute',
'Ocirc',
'Ograve',
'Oslash',
'Otilde',
'Ouml',
'QUOT',
'REG',
'THORN',
'Uacute',
'Ucirc',
'Ugrave',
'Uuml',
'Yacute',
'aacute',
'acirc',
'acute',
'aelig',
'agrave',
'amp',
'aring',
'atilde',
'auml',
'brvbar',
'ccedil',
'cedil',
'cent',
'copy',
'curren',
'deg',
'divide',
'eacute',
'ecirc',
'egrave',
'eth',
'euml',
'frac12',
'frac14',
'frac34',
'gt',
'iacute',
'icirc',
'iexcl',
'igrave',
'iquest',
'iuml',
'laquo',
'lt',
'macr',
'micro',
'middot',
'nbsp',
'not',
'ntilde',
'oacute',
'ocirc',
'ograve',
'ordf',
'ordm',
'oslash',
'otilde',
'ouml',
'para',
'plusmn',
'pound',
'quot',
'raquo',
'reg',
'sect',
'shy',
'sup1',
'sup2',
'sup3',
'szlig',
'thorn',
'times',
'uacute',
'ucirc',
'ugrave',
'uml',
'uuml',
'yacute',
'yen',
'yuml'
];
/**
* Map of named character references from HTML 4.
*
* @type {Record<string, string>}
*/
const characterEntitiesHtml4 = {
nbsp: ' ',
iexcl: '¡',
cent: '¢',
pound: '£',
curren: '¤',
yen: '¥',
brvbar: '¦',
sect: '§',
uml: '¨',
copy: '©',
ordf: 'ª',
laquo: '«',
not: '¬',
shy: '­',
reg: '®',
macr: '¯',
deg: '°',
plusmn: '±',
sup2: '²',
sup3: '³',
acute: '´',
micro: 'µ',
para: '¶',
middot: '·',
cedil: '¸',
sup1: '¹',
ordm: 'º',
raquo: '»',
frac14: '¼',
frac12: '½',
frac34: '¾',
iquest: '¿',
Agrave: 'À',
Aacute: 'Á',
Acirc: 'Â',
Atilde: 'Ã',
Auml: 'Ä',
Aring: 'Å',
AElig: 'Æ',
Ccedil: 'Ç',
Egrave: 'È',
Eacute: 'É',
Ecirc: 'Ê',
Euml: 'Ë',
Igrave: 'Ì',
Iacute: 'Í',
Icirc: 'Î',
Iuml: 'Ï',
ETH: 'Ð',
Ntilde: 'Ñ',
Ograve: 'Ò',
Oacute: 'Ó',
Ocirc: 'Ô',
Otilde: 'Õ',
Ouml: 'Ö',
times: '×',
Oslash: 'Ø',
Ugrave: 'Ù',
Uacute: 'Ú',
Ucirc: 'Û',
Uuml: 'Ü',
Yacute: 'Ý',
THORN: 'Þ',
szlig: 'ß',
agrave: 'à',
aacute: 'á',
acirc: 'â',
atilde: 'ã',
auml: 'ä',
aring: 'å',
aelig: 'æ',
ccedil: 'ç',
egrave: 'è',
eacute: 'é',
ecirc: 'ê',
euml: 'ë',
igrave: 'ì',
iacute: 'í',
icirc: 'î',
iuml: 'ï',
eth: 'ð',
ntilde: 'ñ',
ograve: 'ò',
oacute: 'ó',
ocirc: 'ô',
otilde: 'õ',
ouml: 'ö',
divide: '÷',
oslash: 'ø',
ugrave: 'ù',
uacute: 'ú',
ucirc: 'û',
uuml: 'ü',
yacute: 'ý',
thorn: 'þ',
yuml: 'ÿ',
fnof: 'ƒ',
Alpha: 'Α',
Beta: 'Β',
Gamma: 'Γ',
Delta: 'Δ',
Epsilon: 'Ε',
Zeta: 'Ζ',
Eta: 'Η',
Theta: 'Θ',
Iota: 'Ι',
Kappa: 'Κ',
Lambda: 'Λ',
Mu: 'Μ',
Nu: 'Ν',
Xi: 'Ξ',
Omicron: 'Ο',
Pi: 'Π',
Rho: 'Ρ',
Sigma: 'Σ',
Tau: 'Τ',
Upsilon: 'Υ',
Phi: 'Φ',
Chi: 'Χ',
Psi: 'Ψ',
Omega: 'Ω',
alpha: 'α',
beta: 'β',
gamma: 'γ',
delta: 'δ',
epsilon: 'ε',
zeta: 'ζ',
eta: 'η',
theta: 'θ',
iota: 'ι',
kappa: 'κ',
lambda: 'λ',
mu: 'μ',
nu: 'ν',
xi: 'ξ',
omicron: 'ο',
pi: 'π',
rho: 'ρ',
sigmaf: 'ς',
sigma: 'σ',
tau: 'τ',
upsilon: 'υ',
phi: 'φ',
chi: 'χ',
psi: 'ψ',
omega: 'ω',
thetasym: 'ϑ',
upsih: 'ϒ',
piv: 'ϖ',
bull: '•',
hellip: '…',
prime: '',
Prime: '″',
oline: '‾',
frasl: '',
weierp: '℘',
image: '',
real: '',
trade: '™',
alefsym: 'ℵ',
larr: '←',
uarr: '↑',
rarr: '→',
darr: '↓',
harr: '↔',
crarr: '↵',
lArr: '⇐',
uArr: '⇑',
rArr: '⇒',
dArr: '⇓',
hArr: '⇔',
forall: '∀',
part: '∂',
exist: '∃',
empty: '∅',
nabla: '∇',
isin: '∈',
notin: '∉',
ni: '∋',
prod: '∏',
sum: '∑',
minus: '',
lowast: '',
radic: '√',
prop: '∝',
infin: '∞',
ang: '∠',
and: '∧',
or: '',
cap: '∩',
cup: '',
int: '∫',
there4: '∴',
sim: '',
cong: '≅',
asymp: '≈',
ne: '≠',
equiv: '≡',
le: '≤',
ge: '≥',
sub: '⊂',
sup: '⊃',
nsub: '⊄',
sube: '⊆',
supe: '⊇',
oplus: '⊕',
otimes: '⊗',
perp: '⊥',
sdot: '⋅',
lceil: '⌈',
rceil: '⌉',
lfloor: '⌊',
rfloor: '⌋',
lang: '〈',
rang: '〉',
loz: '◊',
spades: '♠',
clubs: '♣',
hearts: '♥',
diams: '♦',
quot: '"',
amp: '&',
lt: '<',
gt: '>',
OElig: 'Œ',
oelig: 'œ',
Scaron: 'Š',
scaron: 'š',
Yuml: 'Ÿ',
circ: 'ˆ',
tilde: '˜',
ensp: '',
emsp: '',
thinsp: '',
zwnj: '',
zwj: '',
lrm: '',
rlm: '',
ndash: '',
mdash: '—',
lsquo: '',
rsquo: '',
sbquo: '',
ldquo: '“',
rdquo: '”',
bdquo: '„',
dagger: '†',
Dagger: '‡',
permil: '‰',
lsaquo: '',
rsaquo: '',
euro: '€'
};
/**
* List of legacy (that dont need a trailing `;`) named references which could,
* depending on what follows them, turn into a different meaning
*
* @type {Array<string>}
*/
const dangerous = [
'cent',
'copy',
'divide',
'gt',
'lt',
'not',
'para',
'times'
];
const own$1 = {}.hasOwnProperty;
/**
* `characterEntitiesHtml4` but inverted.
*
* @type {Record<string, string>}
*/
const characters = {};
/** @type {string} */
let key;
for (key in characterEntitiesHtml4) {
if (own$1.call(characterEntitiesHtml4, key)) {
characters[characterEntitiesHtml4[key]] = key;
}
}
/**
* Configurable ways to encode characters as named references.
*
* @param {number} code
* @param {number} next
* @param {boolean|undefined} omit
* @param {boolean|undefined} attribute
* @returns {string}
*/
function toNamed(code, next, omit, attribute) {
const character = String.fromCharCode(code);
if (own$1.call(characters, character)) {
const name = characters[character];
const value = '&' + name;
if (
omit &&
characterEntitiesLegacy.includes(name) &&
!dangerous.includes(name) &&
(!attribute ||
(next &&
next !== 61 /* `=` */ &&
/[^\da-z]/i.test(String.fromCharCode(next))))
) {
return value
}
return value + ';'
}
return ''
}
/**
* @typedef FormatSmartOptions
* @property {boolean} [useNamedReferences=false]
* Prefer named character references (`&amp;`) where possible.
* @property {boolean} [useShortestReferences=false]
* Prefer the shortest possible reference, if that results in less bytes.
* **Note**: `useNamedReferences` can be omitted when using `useShortestReferences`.
* @property {boolean} [omitOptionalSemicolons=false]
* Whether to omit semicolons when possible.
* **Note**: This creates what HTML calls “parse errors” but is otherwise still valid HTML — dont use this except when building a minifier.
* Omitting semicolons is possible for certain named and numeric references in some cases.
* @property {boolean} [attribute=false]
* Create character references which dont fail in attributes.
* **Note**: `attribute` only applies when operating dangerously with
* `omitOptionalSemicolons: true`.
*/
/**
* Configurable ways to encode a character yielding pretty or small results.
*
* @param {number} code
* @param {number} next
* @param {FormatSmartOptions} options
* @returns {string}
*/
function formatSmart(code, next, options) {
let numeric = toHexadecimal(code, next, options.omitOptionalSemicolons);
/** @type {string|undefined} */
let named;
if (options.useNamedReferences || options.useShortestReferences) {
named = toNamed(
code,
next,
options.omitOptionalSemicolons,
options.attribute
);
}
// Use the shortest numeric reference when requested.
// A simple algorithm would use decimal for all code points under 100, as
// those are shorter than hexadecimal:
//
// * `&#99;` vs `&#x63;` (decimal shorter)
// * `&#100;` vs `&#x64;` (equal)
//
// However, because we take `next` into consideration when `omit` is used,
// And it would be possible that decimals are shorter on bigger values as
// well if `next` is hexadecimal but not decimal, we instead compare both.
if (
(options.useShortestReferences || !named) &&
options.useShortestReferences
) {
const decimal = toDecimal(code, next, options.omitOptionalSemicolons);
if (decimal.length < numeric.length) {
numeric = decimal;
}
}
return named &&
(!options.useShortestReferences || named.length < numeric.length)
? named
: numeric
}
/**
* @typedef {import('./core.js').CoreOptions & import('./util/format-smart.js').FormatSmartOptions} Options
* @typedef {import('./core.js').CoreOptions} LightOptions
*/
/**
* Encode special characters in `value`.
*
* @param {string} value
* Value to encode.
* @param {Options} [options]
* Configuration.
* @returns {string}
* Encoded value.
*/
function stringifyEntities(value, options) {
return core(value, Object.assign({format: formatSmart}, options))
}
/**
* @typedef {import('hast').Comment} Comment
* @typedef {import('hast').Parents} Parents
*
* @typedef {import('../index.js').State} State
*/
/**
* Serialize a comment.
*
* @param {Comment} node
* Node to handle.
* @param {number | undefined} _1
* Index of `node` in `parent.
* @param {Parents | undefined} _2
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function comment(node, _1, _2, state) {
// See: <https://html.spec.whatwg.org/multipage/syntax.html#comments>
return state.settings.bogusComments
? '<?' +
stringifyEntities(
node.value,
Object.assign({}, state.settings.characterReferences, {subset: ['>']})
) +
'>'
: '<!--' + node.value.replace(/^>|^->|<!--|-->|--!>|<!-$/g, encode) + '-->'
/**
* @param {string} $0
*/
function encode($0) {
return stringifyEntities(
$0,
Object.assign({}, state.settings.characterReferences, {
subset: ['<', '>']
})
)
}
}
/**
* @typedef {import('hast').Doctype} Doctype
* @typedef {import('hast').Parents} Parents
*
* @typedef {import('../index.js').State} State
*/
/**
* Serialize a doctype.
*
* @param {Doctype} _1
* Node to handle.
* @param {number | undefined} _2
* Index of `node` in `parent.
* @param {Parents | undefined} _3
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function doctype(_1, _2, _3, state) {
return (
'<!' +
(state.settings.upperDoctype ? 'DOCTYPE' : 'doctype') +
(state.settings.tightDoctype ? '' : ' ') +
'html>'
)
}
/**
* Count how often a character (or substring) is used in a string.
*
* @param {string} value
* Value to search in.
* @param {string} character
* Character (or substring) to look for.
* @return {number}
* Number of times `character` occurred in `value`.
*/
function ccount(value, character) {
const source = String(value);
if (typeof character !== 'string') {
throw new TypeError('Expected character')
}
let count = 0;
let index = source.indexOf(character);
while (index !== -1) {
count++;
index = source.indexOf(character, index + character.length);
}
return count
}
/**
* @typedef Options
* Configuration for `stringify`.
* @property {boolean} [padLeft=true]
* Whether to pad a space before a token.
* @property {boolean} [padRight=false]
* Whether to pad a space after a token.
*/
/**
* Serialize an array of strings or numbers to comma-separated tokens.
*
* @param {Array<string|number>} values
* List of tokens.
* @param {Options} [options]
* Configuration for `stringify` (optional).
* @returns {string}
* Comma-separated tokens.
*/
function stringify$1(values, options) {
const settings = options || {};
// Ensure the last empty entry is seen.
const input = values[values.length - 1] === '' ? [...values, ''] : values;
return input
.join(
(settings.padRight ? ' ' : '') +
',' +
(settings.padLeft === false ? '' : ' ')
)
.trim()
}
/**
* Parse space-separated tokens to an array of strings.
*
* @param {string} value
* Space-separated tokens.
* @returns {Array<string>}
* List of tokens.
*/
/**
* Serialize an array of strings as space separated-tokens.
*
* @param {Array<string|number>} values
* List of tokens.
* @returns {string}
* Space-separated tokens.
*/
function stringify(values) {
return values.join(' ').trim()
}
/**
* @typedef {import('hast').Nodes} Nodes
*/
// HTML whitespace expression.
// See <https://infra.spec.whatwg.org/#ascii-whitespace>.
const re = /[ \t\n\f\r]/g;
/**
* Check if the given value is *inter-element whitespace*.
*
* @param {Nodes | string} thing
* Thing to check (`Node` or `string`).
* @returns {boolean}
* Whether the `value` is inter-element whitespace (`boolean`): consisting of
* zero or more of space, tab (`\t`), line feed (`\n`), carriage return
* (`\r`), or form feed (`\f`); if a node is passed it must be a `Text` node,
* whose `value` field is checked.
*/
function whitespace(thing) {
return typeof thing === 'object'
? thing.type === 'text'
? empty(thing.value)
: false
: empty(thing)
}
/**
* @param {string} value
* @returns {boolean}
*/
function empty(value) {
return value.replace(re, '') === ''
}
/**
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').RootContent} RootContent
*/
const siblingAfter = siblings(1);
const siblingBefore = siblings(-1);
/** @type {Array<RootContent>} */
const emptyChildren$1 = [];
/**
* Factory to check siblings in a direction.
*
* @param {number} increment
*/
function siblings(increment) {
return sibling
/**
* Find applicable siblings in a direction.
*
* @template {Parents} Parent
* Parent type.
* @param {Parent | undefined} parent
* Parent.
* @param {number | undefined} index
* Index of child in `parent`.
* @param {boolean | undefined} [includeWhitespace=false]
* Whether to include whitespace (default: `false`).
* @returns {Parent extends {children: Array<infer Child>} ? Child | undefined : never}
* Child of parent.
*/
function sibling(parent, index, includeWhitespace) {
const siblings = parent ? parent.children : emptyChildren$1;
let offset = (index || 0) + increment;
let next = siblings[offset];
if (!includeWhitespace) {
while (next && whitespace(next)) {
offset += increment;
next = siblings[offset];
}
}
// @ts-expect-error: its a correct child.
return next
}
}
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Parents} Parents
*/
/**
* @callback OmitHandle
* Check if a tag can be omitted.
* @param {Element} element
* Element to check.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether to omit a tag.
*
*/
const own = {}.hasOwnProperty;
/**
* Factory to check if a given node can have a tag omitted.
*
* @param {Record<string, OmitHandle>} handlers
* Omission handlers, where each key is a tag name, and each value is the
* corresponding handler.
* @returns {OmitHandle}
* Whether to omit a tag of an element.
*/
function omission(handlers) {
return omit
/**
* Check if a given node can have a tag omitted.
*
* @type {OmitHandle}
*/
function omit(node, index, parent) {
return (
own.call(handlers, node.tagName) &&
handlers[node.tagName](node, index, parent)
)
}
}
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Parents} Parents
*/
const closing = omission({
body: body$1,
caption: headOrColgroupOrCaption,
colgroup: headOrColgroupOrCaption,
dd,
dt,
head: headOrColgroupOrCaption,
html: html$1,
li,
optgroup,
option,
p,
rp: rubyElement,
rt: rubyElement,
tbody: tbody$1,
td: cells,
tfoot,
th: cells,
thead,
tr
});
/**
* Macro for `</head>`, `</colgroup>`, and `</caption>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function headOrColgroupOrCaption(_, index, parent) {
const next = siblingAfter(parent, index, true);
return (
!next ||
(next.type !== 'comment' &&
!(next.type === 'text' && whitespace(next.value.charAt(0))))
)
}
/**
* Whether to omit `</html>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function html$1(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || next.type !== 'comment'
}
/**
* Whether to omit `</body>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function body$1(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || next.type !== 'comment'
}
/**
* Whether to omit `</p>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function p(_, index, parent) {
const next = siblingAfter(parent, index);
return next
? next.type === 'element' &&
(next.tagName === 'address' ||
next.tagName === 'article' ||
next.tagName === 'aside' ||
next.tagName === 'blockquote' ||
next.tagName === 'details' ||
next.tagName === 'div' ||
next.tagName === 'dl' ||
next.tagName === 'fieldset' ||
next.tagName === 'figcaption' ||
next.tagName === 'figure' ||
next.tagName === 'footer' ||
next.tagName === 'form' ||
next.tagName === 'h1' ||
next.tagName === 'h2' ||
next.tagName === 'h3' ||
next.tagName === 'h4' ||
next.tagName === 'h5' ||
next.tagName === 'h6' ||
next.tagName === 'header' ||
next.tagName === 'hgroup' ||
next.tagName === 'hr' ||
next.tagName === 'main' ||
next.tagName === 'menu' ||
next.tagName === 'nav' ||
next.tagName === 'ol' ||
next.tagName === 'p' ||
next.tagName === 'pre' ||
next.tagName === 'section' ||
next.tagName === 'table' ||
next.tagName === 'ul')
: !parent ||
// Confusing parent.
!(
parent.type === 'element' &&
(parent.tagName === 'a' ||
parent.tagName === 'audio' ||
parent.tagName === 'del' ||
parent.tagName === 'ins' ||
parent.tagName === 'map' ||
parent.tagName === 'noscript' ||
parent.tagName === 'video')
)
}
/**
* Whether to omit `</li>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function li(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || (next.type === 'element' && next.tagName === 'li')
}
/**
* Whether to omit `</dt>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function dt(_, index, parent) {
const next = siblingAfter(parent, index);
return Boolean(
next &&
next.type === 'element' &&
(next.tagName === 'dt' || next.tagName === 'dd')
)
}
/**
* Whether to omit `</dd>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function dd(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'dt' || next.tagName === 'dd'))
)
}
/**
* Whether to omit `</rt>` or `</rp>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function rubyElement(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'rp' || next.tagName === 'rt'))
)
}
/**
* Whether to omit `</optgroup>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function optgroup(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || (next.type === 'element' && next.tagName === 'optgroup')
}
/**
* Whether to omit `</option>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function option(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'option' || next.tagName === 'optgroup'))
)
}
/**
* Whether to omit `</thead>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function thead(_, index, parent) {
const next = siblingAfter(parent, index);
return Boolean(
next &&
next.type === 'element' &&
(next.tagName === 'tbody' || next.tagName === 'tfoot')
)
}
/**
* Whether to omit `</tbody>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function tbody$1(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'tbody' || next.tagName === 'tfoot'))
)
}
/**
* Whether to omit `</tfoot>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function tfoot(_, index, parent) {
return !siblingAfter(parent, index)
}
/**
* Whether to omit `</tr>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function tr(_, index, parent) {
const next = siblingAfter(parent, index);
return !next || (next.type === 'element' && next.tagName === 'tr')
}
/**
* Whether to omit `</td>` or `</th>`.
*
* @param {Element} _
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the closing tag can be omitted.
*/
function cells(_, index, parent) {
const next = siblingAfter(parent, index);
return (
!next ||
(next.type === 'element' &&
(next.tagName === 'td' || next.tagName === 'th'))
)
}
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Parents} Parents
*/
const opening = omission({
body,
colgroup,
head,
html,
tbody
});
/**
* Whether to omit `<html>`.
*
* @param {Element} node
* Element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function html(node) {
const head = siblingAfter(node, -1);
return !head || head.type !== 'comment'
}
/**
* Whether to omit `<head>`.
*
* @param {Element} node
* Element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function head(node) {
const children = node.children;
/** @type {Array<string>} */
const seen = [];
let index = -1;
while (++index < children.length) {
const child = children[index];
if (
child.type === 'element' &&
(child.tagName === 'title' || child.tagName === 'base')
) {
if (seen.includes(child.tagName)) return false
seen.push(child.tagName);
}
}
return children.length > 0
}
/**
* Whether to omit `<body>`.
*
* @param {Element} node
* Element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function body(node) {
const head = siblingAfter(node, -1, true);
return (
!head ||
(head.type !== 'comment' &&
!(head.type === 'text' && whitespace(head.value.charAt(0))) &&
!(
head.type === 'element' &&
(head.tagName === 'meta' ||
head.tagName === 'link' ||
head.tagName === 'script' ||
head.tagName === 'style' ||
head.tagName === 'template')
))
)
}
/**
* Whether to omit `<colgroup>`.
* The spec describes some logic for the opening tag, but its easier to
* implement in the closing tag, to the same effect, so we handle it there
* instead.
*
* @param {Element} node
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function colgroup(node, index, parent) {
const previous = siblingBefore(parent, index);
const head = siblingAfter(node, -1, true);
// Previous colgroup was already omitted.
if (
parent &&
previous &&
previous.type === 'element' &&
previous.tagName === 'colgroup' &&
closing(previous, parent.children.indexOf(previous), parent)
) {
return false
}
return Boolean(head && head.type === 'element' && head.tagName === 'col')
}
/**
* Whether to omit `<tbody>`.
*
* @param {Element} node
* Element.
* @param {number | undefined} index
* Index of element in parent.
* @param {Parents | undefined} parent
* Parent of element.
* @returns {boolean}
* Whether the opening tag can be omitted.
*/
function tbody(node, index, parent) {
const previous = siblingBefore(parent, index);
const head = siblingAfter(node, -1);
// Previous table section was already omitted.
if (
parent &&
previous &&
previous.type === 'element' &&
(previous.tagName === 'thead' || previous.tagName === 'tbody') &&
closing(previous, parent.children.indexOf(previous), parent)
) {
return false
}
return Boolean(head && head.type === 'element' && head.tagName === 'tr')
}
/**
* @typedef {import('hast').Element} Element
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').Properties} Properties
*
* @typedef {import('../index.js').State} State
*/
/**
* Maps of subsets.
*
* Each value is a matrix of tuples.
* The value at `0` causes parse errors, the value at `1` is valid.
* Of both, the value at `0` is unsafe, and the value at `1` is safe.
*
* @type {Record<'double' | 'name' | 'single' | 'unquoted', Array<[Array<string>, Array<string>]>>}
*/
const constants = {
// See: <https://html.spec.whatwg.org/#attribute-name-state>.
name: [
['\t\n\f\r &/=>'.split(''), '\t\n\f\r "&\'/=>`'.split('')],
['\0\t\n\f\r "&\'/<=>'.split(''), '\0\t\n\f\r "&\'/<=>`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(unquoted)-state>.
unquoted: [
['\t\n\f\r &>'.split(''), '\0\t\n\f\r "&\'<=>`'.split('')],
['\0\t\n\f\r "&\'<=>`'.split(''), '\0\t\n\f\r "&\'<=>`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(single-quoted)-state>.
single: [
["&'".split(''), '"&\'`'.split('')],
["\0&'".split(''), '\0"&\'`'.split('')]
],
// See: <https://html.spec.whatwg.org/#attribute-value-(double-quoted)-state>.
double: [
['"&'.split(''), '"&\'`'.split('')],
['\0"&'.split(''), '\0"&\'`'.split('')]
]
};
/**
* Serialize an element node.
*
* @param {Element} node
* Node to handle.
* @param {number | undefined} index
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function element(node, index, parent, state) {
const schema = state.schema;
const omit = schema.space === 'svg' ? false : state.settings.omitOptionalTags;
let selfClosing =
schema.space === 'svg'
? state.settings.closeEmptyElements
: state.settings.voids.includes(node.tagName.toLowerCase());
/** @type {Array<string>} */
const parts = [];
/** @type {string} */
let last;
if (schema.space === 'html' && node.tagName === 'svg') {
state.schema = svg;
}
const attrs = serializeAttributes(state, node.properties);
const content = state.all(
schema.space === 'html' && node.tagName === 'template' ? node.content : node
);
state.schema = schema;
// If the node is categorised as void, but it has children, remove the
// categorisation.
// This enables for example `menuitem`s, which are void in W3C HTML but not
// void in WHATWG HTML, to be stringified properly.
// Note: `menuitem` has since been removed from the HTML spec, and so is no
// longer void.
if (content) selfClosing = false;
if (attrs || !omit || !opening(node, index, parent)) {
parts.push('<', node.tagName, attrs ? ' ' + attrs : '');
if (
selfClosing &&
(schema.space === 'svg' || state.settings.closeSelfClosing)
) {
last = attrs.charAt(attrs.length - 1);
if (
!state.settings.tightSelfClosing ||
last === '/' ||
(last && last !== '"' && last !== "'")
) {
parts.push(' ');
}
parts.push('/');
}
parts.push('>');
}
parts.push(content);
if (!selfClosing && (!omit || !closing(node, index, parent))) {
parts.push('</' + node.tagName + '>');
}
return parts.join('')
}
/**
* @param {State} state
* @param {Properties | null | undefined} props
* @returns {string}
*/
function serializeAttributes(state, props) {
/** @type {Array<string>} */
const values = [];
let index = -1;
/** @type {string} */
let key;
if (props) {
for (key in props) {
if (props[key] !== null && props[key] !== undefined) {
const value = serializeAttribute(state, key, props[key]);
if (value) values.push(value);
}
}
}
while (++index < values.length) {
const last = state.settings.tightAttributes
? values[index].charAt(values[index].length - 1)
: undefined;
// In tight mode, dont add a space after quoted attributes.
if (index !== values.length - 1 && last !== '"' && last !== "'") {
values[index] += ' ';
}
}
return values.join('')
}
/**
* @param {State} state
* @param {string} key
* @param {Properties[keyof Properties]} value
* @returns {string}
*/
function serializeAttribute(state, key, value) {
const info = find(state.schema, key);
const x =
state.settings.allowParseErrors && state.schema.space === 'html' ? 0 : 1;
const y = state.settings.allowDangerousCharacters ? 0 : 1;
let quote = state.quote;
/** @type {string | undefined} */
let result;
if (info.overloadedBoolean && (value === info.attribute || value === '')) {
value = true;
} else if (
info.boolean ||
(info.overloadedBoolean && typeof value !== 'string')
) {
value = Boolean(value);
}
if (
value === null ||
value === undefined ||
value === false ||
(typeof value === 'number' && Number.isNaN(value))
) {
return ''
}
const name = stringifyEntities(
info.attribute,
Object.assign({}, state.settings.characterReferences, {
// Always encode without parse errors in non-HTML.
subset: constants.name[x][y]
})
);
// No value.
// There is currently only one boolean property in SVG: `[download]` on
// `<a>`.
// This property does not seem to work in browsers (Firefox, Safari, Chrome),
// so I cant test if dropping the value works.
// But I assume that it should:
//
// ```html
// <!doctype html>
// <svg viewBox="0 0 100 100">
// <a href=https://example.com download>
// <circle cx=50 cy=40 r=35 />
// </a>
// </svg>
// ```
//
// See: <https://github.com/wooorm/property-information/blob/main/lib/svg.js>
if (value === true) return name
// `spaces` doesnt accept a second argument, but its given here just to
// keep the code cleaner.
value = Array.isArray(value)
? (info.commaSeparated ? stringify$1 : stringify)(value, {
padLeft: !state.settings.tightCommaSeparatedLists
})
: String(value);
if (state.settings.collapseEmptyAttributes && !value) return name
// Check unquoted value.
if (state.settings.preferUnquoted) {
result = stringifyEntities(
value,
Object.assign({}, state.settings.characterReferences, {
attribute: true,
subset: constants.unquoted[x][y]
})
);
}
// If we dont want unquoted, or if `value` contains character references when
// unquoted…
if (result !== value) {
// If the alternative is less common than `quote`, switch.
if (
state.settings.quoteSmart &&
ccount(value, quote) > ccount(value, state.alternative)
) {
quote = state.alternative;
}
result =
quote +
stringifyEntities(
value,
Object.assign({}, state.settings.characterReferences, {
// Always encode without parse errors in non-HTML.
subset: (quote === "'" ? constants.single : constants.double)[x][y],
attribute: true
})
) +
quote;
}
// Dont add a `=` for unquoted empties.
return name + (result ? '=' + result : result)
}
/**
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').Text} Text
*
* @typedef {import('mdast-util-to-hast').Raw} Raw
*
* @typedef {import('../index.js').State} State
*/
/**
* Serialize a text node.
*
* @param {Raw | Text} node
* Node to handle.
* @param {number | undefined} _
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function text(node, _, parent, state) {
// Check if content of `node` should be escaped.
return parent &&
parent.type === 'element' &&
(parent.tagName === 'script' || parent.tagName === 'style')
? node.value
: stringifyEntities(
node.value,
Object.assign({}, state.settings.characterReferences, {
subset: ['<', '&']
})
)
}
/**
* @typedef {import('hast').Parents} Parents
*
* @typedef {import('mdast-util-to-hast').Raw} Raw
*
* @typedef {import('../index.js').State} State
*/
/**
* Serialize a raw node.
*
* @param {Raw} node
* Node to handle.
* @param {number | undefined} index
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function raw(node, index, parent, state) {
return state.settings.allowDangerousHtml
? node.value
: text(node, index, parent, state)
}
/**
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').Root} Root
*
* @typedef {import('../index.js').State} State
*/
/**
* Serialize a root.
*
* @param {Root} node
* Node to handle.
* @param {number | undefined} _1
* Index of `node` in `parent.
* @param {Parents | undefined} _2
* Parent of `node`.
* @param {State} state
* Info passed around about the current state.
* @returns {string}
* Serialized node.
*/
function root(node, _1, _2, state) {
return state.all(node)
}
/**
* @typedef {import('hast').Nodes} Nodes
* @typedef {import('hast').Parents} Parents
*
* @typedef {import('../index.js').State} State
*/
/**
* @type {(node: Nodes, index: number | undefined, parent: Parents | undefined, state: State) => string}
*/
const handle = zwitch('type', {
invalid,
unknown,
handlers: {comment, doctype, element, raw, root, text}
});
/**
* Fail when a non-node is found in the tree.
*
* @param {unknown} node
* Unknown value.
* @returns {never}
* Never.
*/
function invalid(node) {
throw new Error('Expected node, not `' + node + '`')
}
/**
* Fail when a node with an unknown type is found in the tree.
*
* @param {unknown} node_
* Unknown node.
* @returns {never}
* Never.
*/
function unknown(node_) {
// `type` is guaranteed by runtime JS.
const node = /** @type {Nodes} */ (node_);
throw new Error('Cannot compile unknown node `' + node.type + '`')
}
/**
* @typedef {import('hast').Nodes} Nodes
* @typedef {import('hast').Parents} Parents
* @typedef {import('hast').RootContent} RootContent
*
* @typedef {import('property-information').Schema} Schema
*
* @typedef {import('stringify-entities').Options} StringifyEntitiesOptions
*/
/** @type {Options} */
const emptyOptions = {};
/** @type {CharacterReferences} */
const emptyCharacterReferences = {};
/** @type {Array<never>} */
const emptyChildren = [];
/**
* Serialize hast as HTML.
*
* @param {Array<RootContent> | Nodes} tree
* Tree to serialize.
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {string}
* Serialized HTML.
*/
function toHtml(tree, options) {
const options_ = options || emptyOptions;
const quote = options_.quote || '"';
const alternative = quote === '"' ? "'" : '"';
if (quote !== '"' && quote !== "'") {
throw new Error('Invalid quote `' + quote + '`, expected `\'` or `"`')
}
/** @type {State} */
const state = {
one,
all,
settings: {
omitOptionalTags: options_.omitOptionalTags || false,
allowParseErrors: options_.allowParseErrors || false,
allowDangerousCharacters: options_.allowDangerousCharacters || false,
quoteSmart: options_.quoteSmart || false,
preferUnquoted: options_.preferUnquoted || false,
tightAttributes: options_.tightAttributes || false,
upperDoctype: options_.upperDoctype || false,
tightDoctype: options_.tightDoctype || false,
bogusComments: options_.bogusComments || false,
tightCommaSeparatedLists: options_.tightCommaSeparatedLists || false,
tightSelfClosing: options_.tightSelfClosing || false,
collapseEmptyAttributes: options_.collapseEmptyAttributes || false,
allowDangerousHtml: options_.allowDangerousHtml || false,
voids: options_.voids || htmlVoidElements,
characterReferences:
options_.characterReferences || emptyCharacterReferences,
closeSelfClosing: options_.closeSelfClosing || false,
closeEmptyElements: options_.closeEmptyElements || false
},
schema: options_.space === 'svg' ? svg : html$2,
quote,
alternative
};
return state.one(
Array.isArray(tree) ? {type: 'root', children: tree} : tree,
undefined,
undefined
)
}
/**
* Serialize a node.
*
* @this {State}
* Info passed around about the current state.
* @param {Nodes} node
* Node to handle.
* @param {number | undefined} index
* Index of `node` in `parent.
* @param {Parents | undefined} parent
* Parent of `node`.
* @returns {string}
* Serialized node.
*/
function one(node, index, parent) {
return handle(node, index, parent, this)
}
/**
* Serialize all children of `parent`.
*
* @this {State}
* Info passed around about the current state.
* @param {Parents | undefined} parent
* Parent whose children to serialize.
* @returns {string}
*/
function all(parent) {
/** @type {Array<string>} */
const results = [];
const children = (parent && parent.children) || emptyChildren;
let index = -1;
while (++index < children.length) {
results[index] = this.one(children[index], index, parent);
}
return results.join('')
}
function toArray(x) {
return Array.isArray(x) ? x : [x];
}
/**
* Check if the language is plaintext that is ignored by Shikiji.
*
* Hard-coded languages: `plaintext`, `txt`, `text`, `plain`.
*/
function isPlaintext(lang) {
return !lang || ['plaintext', 'txt', 'text', 'plain'].includes(lang);
}
/**
* Check if the language is specially handled by Shikiji.
*
* Hard-coded languages: `ansi` and plaintexts like `plaintext`, `txt`, `text`, `plain`.
*/
function isSpecialLang(lang) {
return lang === 'ansi' || isPlaintext(lang);
}
/**
* Utility to append class to a hast node
*
* If the `property.class` is a string, it will be splitted by space and converted to an array.
*/
function addClassToHast(node, className) {
if (!className)
return;
node.properties ||= {};
node.properties.class ||= [];
if (typeof node.properties.class === 'string')
node.properties.class = node.properties.class.split(/\s+/g);
if (!Array.isArray(node.properties.class))
node.properties.class = [];
const targets = Array.isArray(className) ? className : className.split(/\s+/g);
for (const c of targets) {
if (c && !node.properties.class.includes(c))
node.properties.class.push(c);
}
}
// src/colors.ts
var namedColors = [
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
"brightBlack",
"brightRed",
"brightGreen",
"brightYellow",
"brightBlue",
"brightMagenta",
"brightCyan",
"brightWhite"
];
// src/decorations.ts
var decorations = {
1: "bold",
2: "dim",
3: "italic",
4: "underline",
7: "reverse",
9: "strikethrough"
};
// src/parser.ts
function findSequence(value, position) {
const nextEscape = value.indexOf("\x1B[", position);
if (nextEscape !== -1) {
const nextClose = value.indexOf("m", nextEscape);
return {
sequence: value.substring(nextEscape + 2, nextClose).split(";"),
startPosition: nextEscape,
position: nextClose + 1
};
}
return {
position: value.length
};
}
function parseColor(sequence, index) {
let offset = 1;
const colorMode = sequence[index + offset++];
let color;
if (colorMode === "2") {
const rgb = [
sequence[index + offset++],
sequence[index + offset++],
sequence[index + offset]
].map((x) => Number.parseInt(x));
if (rgb.length === 3 && !rgb.some((x) => Number.isNaN(x))) {
color = {
type: "rgb",
rgb
};
}
} else if (colorMode === "5") {
const colorIndex = Number.parseInt(sequence[index + offset]);
if (!Number.isNaN(colorIndex)) {
color = { type: "table", index: Number(colorIndex) };
}
}
return [offset, color];
}
function parseSequence(sequence) {
const commands = [];
for (let i = 0; i < sequence.length; i++) {
const code = sequence[i];
const codeInt = Number.parseInt(code);
if (Number.isNaN(codeInt))
continue;
if (codeInt === 0) {
commands.push({ type: "resetAll" });
} else if (codeInt <= 9) {
const decoration = decorations[codeInt];
if (decoration) {
commands.push({
type: "setDecoration",
value: decorations[codeInt]
});
}
} else if (codeInt <= 29) {
const decoration = decorations[codeInt - 20];
if (decoration) {
commands.push({
type: "resetDecoration",
value: decoration
});
}
} else if (codeInt <= 37) {
commands.push({
type: "setForegroundColor",
value: { type: "named", name: namedColors[codeInt - 30] }
});
} else if (codeInt === 38) {
const [offset, color] = parseColor(sequence, i);
if (color) {
commands.push({
type: "setForegroundColor",
value: color
});
}
i += offset;
} else if (codeInt === 39) {
commands.push({
type: "resetForegroundColor"
});
} else if (codeInt <= 47) {
commands.push({
type: "setBackgroundColor",
value: { type: "named", name: namedColors[codeInt - 40] }
});
} else if (codeInt === 48) {
const [offset, color] = parseColor(sequence, i);
if (color) {
commands.push({
type: "setBackgroundColor",
value: color
});
}
i += offset;
} else if (codeInt === 49) {
commands.push({
type: "resetBackgroundColor"
});
} else if (codeInt >= 90 && codeInt <= 97) {
commands.push({
type: "setForegroundColor",
value: { type: "named", name: namedColors[codeInt - 90 + 8] }
});
} else if (codeInt >= 100 && codeInt <= 107) {
commands.push({
type: "setBackgroundColor",
value: { type: "named", name: namedColors[codeInt - 100 + 8] }
});
}
}
return commands;
}
function createAnsiSequenceParser() {
let foreground = null;
let background = null;
let decorations2 = /* @__PURE__ */ new Set();
return {
parse(value) {
const tokens = [];
let position = 0;
do {
const findResult = findSequence(value, position);
const text = findResult.sequence ? value.substring(position, findResult.startPosition) : value.substring(position);
if (text.length > 0) {
tokens.push({
value: text,
foreground,
background,
decorations: new Set(decorations2)
});
}
if (findResult.sequence) {
const commands = parseSequence(findResult.sequence);
for (const styleToken of commands) {
if (styleToken.type === "resetAll") {
foreground = null;
background = null;
decorations2.clear();
} else if (styleToken.type === "resetForegroundColor") {
foreground = null;
} else if (styleToken.type === "resetBackgroundColor") {
background = null;
} else if (styleToken.type === "resetDecoration") {
decorations2.delete(styleToken.value);
}
}
for (const styleToken of commands) {
if (styleToken.type === "setForegroundColor") {
foreground = styleToken.value;
} else if (styleToken.type === "setBackgroundColor") {
background = styleToken.value;
} else if (styleToken.type === "setDecoration") {
decorations2.add(styleToken.value);
}
}
}
position = findResult.position;
} while (position < value.length);
return tokens;
}
};
}
// src/palette.ts
var defaultNamedColorsMap = {
black: "#000000",
red: "#bb0000",
green: "#00bb00",
yellow: "#bbbb00",
blue: "#0000bb",
magenta: "#ff00ff",
cyan: "#00bbbb",
white: "#eeeeee",
brightBlack: "#555555",
brightRed: "#ff5555",
brightGreen: "#00ff00",
brightYellow: "#ffff55",
brightBlue: "#5555ff",
brightMagenta: "#ff55ff",
brightCyan: "#55ffff",
brightWhite: "#ffffff"
};
function createColorPalette(namedColorsMap = defaultNamedColorsMap) {
function namedColor(name) {
return namedColorsMap[name];
}
function rgbColor(rgb) {
return `#${rgb.map((x) => Math.max(0, Math.min(x, 255)).toString(16).padStart(2, "0")).join("")}`;
}
let colorTable;
function getColorTable() {
if (colorTable) {
return colorTable;
}
colorTable = [];
for (let i = 0; i < namedColors.length; i++) {
colorTable.push(namedColor(namedColors[i]));
}
let levels = [0, 95, 135, 175, 215, 255];
for (let r = 0; r < 6; r++) {
for (let g = 0; g < 6; g++) {
for (let b = 0; b < 6; b++) {
colorTable.push(rgbColor([levels[r], levels[g], levels[b]]));
}
}
}
let level = 8;
for (let i = 0; i < 24; i++, level += 10) {
colorTable.push(rgbColor([level, level, level]));
}
return colorTable;
}
function tableColor(index) {
return getColorTable()[index];
}
function value(color) {
switch (color.type) {
case "named":
return namedColor(color.name);
case "rgb":
return rgbColor(color.rgb);
case "table":
return tableColor(color.index);
}
}
return {
value
};
}
function tokenizeAnsiWithTheme(theme, fileContents, options) {
const colorReplacements = {
...theme.colorReplacements,
...options?.colorReplacements,
};
const lines = fileContents.split(/\r?\n/);
const colorPalette = createColorPalette(Object.fromEntries(namedColors.map(name => [
name,
theme.colors?.[`terminal.ansi${name[0].toUpperCase()}${name.substring(1)}`],
])));
const parser = createAnsiSequenceParser();
return lines.map(line => parser.parse(line).map((token) => {
let color;
if (token.decorations.has('reverse'))
color = token.background ? colorPalette.value(token.background) : theme.bg;
else
color = token.foreground ? colorPalette.value(token.foreground) : theme.fg;
color = applyColorReplacements(color, colorReplacements);
if (token.decorations.has('dim'))
color = dimColor(color);
let fontStyle = FontStyle.None;
if (token.decorations.has('bold'))
fontStyle |= FontStyle.Bold;
if (token.decorations.has('italic'))
fontStyle |= FontStyle.Italic;
if (token.decorations.has('underline'))
fontStyle |= FontStyle.Underline;
return {
content: token.value,
color,
fontStyle,
};
}));
}
/**
* Adds 50% alpha to a hex color string or the "-dim" postfix to a CSS variable
*/
function dimColor(color) {
const hexMatch = color.match(/#([0-9a-f]{3})([0-9a-f]{3})?([0-9a-f]{2})?/);
if (hexMatch) {
if (hexMatch[3]) {
// convert from #rrggbbaa to #rrggbb(aa/2)
const alpha = Math.round(Number.parseInt(hexMatch[3], 16) / 2)
.toString(16)
.padStart(2, '0');
return `#${hexMatch[1]}${hexMatch[2]}${alpha}`;
}
else if (hexMatch[2]) {
// convert from #rrggbb to #rrggbb80
return `#${hexMatch[1]}${hexMatch[2]}80`;
}
else {
// convert from #rgb to #rrggbb80
return `#${Array.from(hexMatch[1])
.map(x => `${x}${x}`)
.join('')}80`;
}
}
const cssVarMatch = color.match(/var\((--[\w-]+-ansi-[\w-]+)\)/);
if (cssVarMatch)
return `var(${cssVarMatch[1]}-dim)`;
return color;
}
function codeToThemedTokens(internal, code, options = {}) {
const { lang = 'text', theme: themeName = internal.getLoadedThemes()[0], } = options;
if (isPlaintext(lang)) {
const lines = code.split(/\r\n|\r|\n/);
return [...lines.map(line => [{ content: line }])];
}
const { theme, colorMap } = internal.setTheme(themeName);
if (lang === 'ansi')
return tokenizeAnsiWithTheme(theme, code, options);
const _grammar = internal.getLangGrammar(lang);
return tokenizeWithTheme(code, _grammar, theme, colorMap, options);
}
function tokenizeWithTheme(code, grammar, theme, colorMap, options) {
const colorReplacements = {
...theme.colorReplacements,
...options?.colorReplacements,
};
const lines = code.split(/\r\n|\r|\n/);
let ruleStack = INITIAL;
let actual = [];
const final = [];
for (let i = 0, len = lines.length; i < len; i++) {
const line = lines[i];
if (line === '') {
actual = [];
final.push([]);
continue;
}
let resultWithScopes;
let tokensWithScopes;
let tokensWithScopesIndex;
if (options.includeExplanation) {
resultWithScopes = grammar.tokenizeLine(line, ruleStack);
tokensWithScopes = resultWithScopes.tokens;
tokensWithScopesIndex = 0;
}
const result = grammar.tokenizeLine2(line, ruleStack);
const tokensLength = result.tokens.length / 2;
for (let j = 0; j < tokensLength; j++) {
const startIndex = result.tokens[2 * j];
const nextStartIndex = j + 1 < tokensLength ? result.tokens[2 * j + 2] : line.length;
if (startIndex === nextStartIndex)
continue;
const metadata = result.tokens[2 * j + 1];
const foreground = StackElementMetadata.getForeground(metadata);
const foregroundColor = applyColorReplacements(colorMap[foreground], colorReplacements);
const fontStyle = StackElementMetadata.getFontStyle(metadata);
const token = {
content: line.substring(startIndex, nextStartIndex),
color: foregroundColor,
fontStyle,
};
if (options.includeExplanation) {
token.explanation = [];
let offset = 0;
while (startIndex + offset < nextStartIndex) {
const tokenWithScopes = tokensWithScopes[tokensWithScopesIndex];
const tokenWithScopesText = line.substring(tokenWithScopes.startIndex, tokenWithScopes.endIndex);
offset += tokenWithScopesText.length;
token.explanation.push({
content: tokenWithScopesText,
scopes: explainThemeScopes(theme, tokenWithScopes.scopes),
});
tokensWithScopesIndex += 1;
}
}
actual.push(token);
}
final.push(actual);
actual = [];
ruleStack = result.ruleStack;
}
return final;
}
function explainThemeScopes(theme, scopes) {
const result = [];
for (let i = 0, len = scopes.length; i < len; i++) {
const parentScopes = scopes.slice(0, i);
const scope = scopes[i];
result[i] = {
scopeName: scope,
themeMatches: explainThemeScope(theme, scope, parentScopes),
};
}
return result;
}
function matchesOne(selector, scope) {
const selectorPrefix = `${selector}.`;
if (selector === scope || scope.substring(0, selectorPrefix.length) === selectorPrefix)
return true;
return false;
}
function matches(selector, selectorParentScopes, scope, parentScopes) {
if (!matchesOne(selector, scope))
return false;
let selectorParentIndex = selectorParentScopes.length - 1;
let parentIndex = parentScopes.length - 1;
while (selectorParentIndex >= 0 && parentIndex >= 0) {
if (matchesOne(selectorParentScopes[selectorParentIndex], parentScopes[parentIndex]))
selectorParentIndex -= 1;
parentIndex -= 1;
}
if (selectorParentIndex === -1)
return true;
return false;
}
function explainThemeScope(theme, scope, parentScopes) {
const result = [];
let resultLen = 0;
for (let i = 0, len = theme.settings.length; i < len; i++) {
const setting = theme.settings[i];
let selectors;
if (typeof setting.scope === 'string')
selectors = setting.scope.split(/,/).map(scope => scope.trim());
else if (Array.isArray(setting.scope))
selectors = setting.scope;
else
continue;
for (let j = 0, lenJ = selectors.length; j < lenJ; j++) {
const rawSelector = selectors[j];
const rawSelectorPieces = rawSelector.split(/ /);
const selector = rawSelectorPieces[rawSelectorPieces.length - 1];
const selectorParentScopes = rawSelectorPieces.slice(0, rawSelectorPieces.length - 1);
if (matches(selector, selectorParentScopes, scope, parentScopes)) {
// match!
result[resultLen++] = setting;
// break the loop
j = lenJ;
}
}
}
return result;
}
function applyColorReplacements(color, replacements) {
return replacements?.[color.toLowerCase()] || color;
}
/**
* Get tokens with multiple themes
*/
function codeToTokensWithThemes(internal, code, options) {
const themes = Object.entries(options.themes)
.filter(i => i[1])
.map(i => ({ color: i[0], theme: i[1] }));
const tokens = syncThemesTokenization(...themes.map(t => codeToThemedTokens(internal, code, {
...options,
theme: t.theme,
includeExplanation: false,
})));
const mergedTokens = tokens[0]
.map((line, lineIdx) => line
.map((_token, tokenIdx) => {
const mergedToken = {
content: _token.content,
variants: {},
};
tokens.forEach((t, themeIdx) => {
const { content: _, explanation: __, ...styles } = t[lineIdx][tokenIdx];
mergedToken.variants[themes[themeIdx].color] = styles;
});
return mergedToken;
}));
return mergedTokens;
}
/**
* Break tokens from multiple themes into same tokenization.
*
* For example, given two themes that tokenize `console.log("hello")` as:
*
* - `console . log (" hello ")` (6 tokens)
* - `console .log ( "hello" )` (5 tokens)
*
* This function will return:
*
* - `console . log ( " hello " )` (8 tokens)
* - `console . log ( " hello " )` (8 tokens)
*/
function syncThemesTokenization(...themes) {
const outThemes = themes.map(() => []);
const count = themes.length;
for (let i = 0; i < themes[0].length; i++) {
const lines = themes.map(t => t[i]);
const outLines = outThemes.map(() => []);
outThemes.forEach((t, i) => t.push(outLines[i]));
const indexes = lines.map(() => 0);
const current = lines.map(l => l[0]);
while (current.every(t => t)) {
const minLength = Math.min(...current.map(t => t.content.length));
for (let n = 0; n < count; n++) {
const token = current[n];
if (token.content.length === minLength) {
outLines[n].push(token);
indexes[n] += 1;
current[n] = lines[n][indexes[n]];
}
else {
outLines[n].push({
...token,
content: token.content.slice(0, minLength),
});
current[n] = {
...token,
content: token.content.slice(minLength),
};
}
}
}
}
return outThemes;
}
function codeToHast(internal, code, options, transformerContext = {
meta: {},
options,
codeToHast: (_code, _options) => codeToHast(internal, _code, _options),
}) {
let input = code;
for (const transformer of options.transformers || [])
input = transformer.preprocess?.call(transformerContext, input, options) || input;
let bg;
let fg;
let tokens;
let themeName;
let rootStyle;
if ('themes' in options) {
const { defaultColor = 'light', cssVariablePrefix = '--shiki-', } = options;
const themes = Object.entries(options.themes)
.filter(i => i[1])
.map(i => ({ color: i[0], theme: i[1] }))
.sort((a, b) => a.color === defaultColor ? -1 : b.color === defaultColor ? 1 : 0);
if (themes.length === 0)
throw new Error('[shikiji] `themes` option must not be empty');
const themeTokens = codeToTokensWithThemes(internal, input, options);
if (defaultColor && !themes.find(t => t.color === defaultColor))
throw new Error(`[shikiji] \`themes\` option must contain the defaultColor key \`${defaultColor}\``);
const themeRegs = themes.map(t => internal.getTheme(t.theme));
const themesOrder = themes.map(t => t.color);
tokens = themeTokens
.map(line => line.map(token => mergeToken(token, themesOrder, cssVariablePrefix, defaultColor)));
fg = themes.map((t, idx) => (idx === 0 && defaultColor ? '' : `${cssVariablePrefix + t.color}:`) + themeRegs[idx].fg).join(';');
bg = themes.map((t, idx) => (idx === 0 && defaultColor ? '' : `${cssVariablePrefix + t.color}-bg:`) + themeRegs[idx].bg).join(';');
themeName = `shiki-themes ${themeRegs.map(t => t.name).join(' ')}`;
rootStyle = defaultColor ? undefined : [fg, bg].join(';');
}
else if ('theme' in options) {
tokens = codeToThemedTokens(internal, input, {
...options,
includeExplanation: false,
});
const _theme = internal.getTheme(options.theme);
bg = _theme.bg;
fg = _theme.fg;
themeName = _theme.name;
}
else {
throw new Error('[shikiji] Invalid options, either `theme` or `themes` must be provided');
}
return tokensToHast(tokens, {
...options,
fg,
bg,
themeName,
rootStyle,
}, transformerContext);
}
/**
*
*/
function mergeToken(merged, variantsOrder, cssVariablePrefix, defaultColor) {
const token = {
content: merged.content,
explanation: merged.explanation,
};
const styles = variantsOrder.map(t => getTokenStyleObject(merged.variants[t]));
// Get all style keys, for themes that missing some style, we put `inherit` to override as needed
const styleKeys = new Set(styles.flatMap(t => Object.keys(t)));
const mergedStyles = styles.reduce((acc, cur, idx) => {
for (const key of styleKeys) {
const value = cur[key] || 'inherit';
if (idx === 0 && defaultColor) {
acc[key] = value;
}
else {
const varKey = cssVariablePrefix + variantsOrder[idx] + (key === 'color' ? '' : `-${key}`);
if (acc[key])
acc[key] += `;${varKey}:${value}`;
else
acc[key] = `${varKey}:${value}`;
}
}
return acc;
}, {});
token.htmlStyle = defaultColor
? stringifyTokenStyle(mergedStyles)
: Object.values(mergedStyles).join(';');
return token;
}
function tokensToHast(tokens, options, transformerContext) {
const { mergeWhitespaces = true, transformers = [], } = options;
// TODO: remove this in next major version
if (options.transforms) {
transformers.push(options.transforms);
console.warn('[shikiji] `transforms` option is deprecated, use `transformers` instead');
}
if (mergeWhitespaces === true)
tokens = mergeWhitespaceTokens(tokens);
else if (mergeWhitespaces === 'never')
tokens = splitWhitespaceTokens(tokens);
const lines = [];
const tree = {
type: 'root',
children: [],
};
let preNode = {
type: 'element',
tagName: 'pre',
properties: {
class: `shiki ${options.themeName || ''}`,
style: options.rootStyle || `background-color:${options.bg};color:${options.fg}`,
tabindex: '0',
...Object.fromEntries(Array.from(Object.entries(options.meta || {}))
.filter(([key]) => !key.startsWith('_'))),
},
children: [],
};
let codeNode = {
type: 'element',
tagName: 'code',
properties: {},
children: lines,
};
const lineNodes = [];
const context = {
...transformerContext,
get tokens() {
return tokens;
},
get options() {
return options;
},
get root() {
return tree;
},
get pre() {
return preNode;
},
get code() {
return codeNode;
},
get lines() {
return lineNodes;
},
};
tokens.forEach((line, idx) => {
if (idx)
lines.push({ type: 'text', value: '\n' });
let lineNode = {
type: 'element',
tagName: 'span',
properties: { class: 'line' },
children: [],
};
let col = 0;
for (const token of line) {
let tokenNode = {
type: 'element',
tagName: 'span',
properties: {},
children: [{ type: 'text', value: token.content }],
};
const style = token.htmlStyle || stringifyTokenStyle(getTokenStyleObject(token));
if (style)
tokenNode.properties.style = style;
for (const transformer of transformers)
tokenNode = transformer?.token?.call(context, tokenNode, idx + 1, col, lineNode) || tokenNode;
lineNode.children.push(tokenNode);
col += token.content.length;
}
for (const transformer of transformers)
lineNode = transformer?.line?.call(context, lineNode, idx + 1) || lineNode;
lineNodes.push(lineNode);
lines.push(lineNode);
});
for (const transformer of transformers)
codeNode = transformer?.code?.call(context, codeNode) || codeNode;
preNode.children.push(codeNode);
for (const transformer of transformers)
preNode = transformer?.pre?.call(context, preNode) || preNode;
tree.children.push(preNode);
let result = tree;
for (const transformer of transformers)
result = transformer?.root?.call(context, result) || result;
return result;
}
function getTokenStyleObject(token) {
const styles = {};
if (token.color)
styles.color = token.color;
if (token.fontStyle) {
if (token.fontStyle & FontStyle.Italic)
styles['font-style'] = 'italic';
if (token.fontStyle & FontStyle.Bold)
styles['font-weight'] = 'bold';
if (token.fontStyle & FontStyle.Underline)
styles['text-decoration'] = 'underline';
}
return styles;
}
function stringifyTokenStyle(token) {
return Object.entries(token).map(([key, value]) => `${key}:${value}`).join(';');
}
function mergeWhitespaceTokens(tokens) {
return tokens.map((line) => {
const newLine = [];
let carryOnContent = '';
line.forEach((token, idx) => {
const isUnderline = token.fontStyle && token.fontStyle & FontStyle.Underline;
const couldMerge = !isUnderline;
if (couldMerge && token.content.match(/^\s+$/) && line[idx + 1]) {
carryOnContent += token.content;
}
else {
if (carryOnContent) {
if (couldMerge) {
newLine.push({
...token,
content: carryOnContent + token.content,
});
}
else {
newLine.push({
content: carryOnContent,
}, token);
}
carryOnContent = '';
}
else {
newLine.push(token);
}
}
});
return newLine;
});
}
function splitWhitespaceTokens(tokens) {
return tokens.map((line) => {
return line.flatMap((token) => {
if (token.content.match(/^\s+$/))
return token;
const match = token.content.match(/^(\s*)(.*?)(\s*)$/);
if (!match)
return token;
const [, leading, content, trailing] = match;
if (!leading && !trailing)
return token;
const expanded = [{
...token,
content,
}];
if (leading)
expanded.unshift({ content: leading });
if (trailing)
expanded.push({ content: trailing });
return expanded;
});
});
}
/**
* Get highlighted code in HTML.
*/
function codeToHtml(internal, code, options) {
const context = {
meta: {},
options,
codeToHast: (_code, _options) => codeToHast(internal, _code, _options),
};
let result = toHtml(codeToHast(internal, code, options, context));
for (const transformer of options.transformers || [])
result = transformer.postprocess?.call(context, result, options) || result;
return result;
}
async function main(init) {
let wasmMemory;
let buffer;
const binding = {};
function updateGlobalBufferAndViews(buf) {
buffer = buf;
binding.HEAPU8 = new Uint8Array(buf);
binding.HEAPU32 = new Uint32Array(buf);
}
function _emscripten_get_now() {
return typeof performance !== 'undefined' ? performance.now() : Date.now();
}
function _emscripten_memcpy_big(dest, src, num) {
binding.HEAPU8.copyWithin(dest, src, src + num);
}
function getHeapMax() {
return 2147483648;
}
function emscripten_realloc_buffer(size) {
try {
wasmMemory.grow((size - buffer.byteLength + 65535) >>> 16);
updateGlobalBufferAndViews(wasmMemory.buffer);
return 1;
}
catch (e) { }
}
function _emscripten_resize_heap(requestedSize) {
const oldSize = binding.HEAPU8.length;
requestedSize = requestedSize >>> 0;
const maxHeapSize = getHeapMax();
if (requestedSize > maxHeapSize)
return false;
const alignUp = (x, multiple) => x + ((multiple - (x % multiple)) % multiple);
for (let cutDown = 1; cutDown <= 4; cutDown *= 2) {
let overGrownHeapSize = oldSize * (1 + 0.2 / cutDown);
overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296);
const newSize = Math.min(maxHeapSize, alignUp(Math.max(requestedSize, overGrownHeapSize), 65536));
const replacement = emscripten_realloc_buffer(newSize);
if (replacement)
return true;
}
return false;
}
const asmLibraryArg = {
emscripten_get_now: _emscripten_get_now,
emscripten_memcpy_big: _emscripten_memcpy_big,
emscripten_resize_heap: _emscripten_resize_heap,
fd_write: () => 0,
};
async function createWasm() {
const info = {
env: asmLibraryArg,
wasi_snapshot_preview1: asmLibraryArg,
};
const exports = await init(info);
wasmMemory = exports.memory;
updateGlobalBufferAndViews(wasmMemory.buffer);
Object.assign(binding, exports);
}
await createWasm();
return binding;
}
/* ---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*-------------------------------------------------------- */
let onigBinding = null;
let defaultDebugCall = false;
function throwLastOnigError(onigBinding) {
throw new Error(onigBinding.UTF8ToString(onigBinding.getLastOnigError()));
}
class UtfString {
static _utf8ByteLength(str) {
let result = 0;
for (let i = 0, len = str.length; i < len; i++) {
const charCode = str.charCodeAt(i);
let codepoint = charCode;
let wasSurrogatePair = false;
if (charCode >= 0xD800 && charCode <= 0xDBFF) {
// Hit a high surrogate, try to look for a matching low surrogate
if (i + 1 < len) {
const nextCharCode = str.charCodeAt(i + 1);
if (nextCharCode >= 0xDC00 && nextCharCode <= 0xDFFF) {
// Found the matching low surrogate
codepoint = (((charCode - 0xD800) << 10) + 0x10000) | (nextCharCode - 0xDC00);
wasSurrogatePair = true;
}
}
}
if (codepoint <= 0x7F)
result += 1;
else if (codepoint <= 0x7FF)
result += 2;
else if (codepoint <= 0xFFFF)
result += 3;
else
result += 4;
if (wasSurrogatePair)
i++;
}
return result;
}
utf16Length;
utf8Length;
utf16Value;
utf8Value;
utf16OffsetToUtf8;
utf8OffsetToUtf16;
constructor(str) {
const utf16Length = str.length;
const utf8Length = UtfString._utf8ByteLength(str);
const computeIndicesMapping = (utf8Length !== utf16Length);
const utf16OffsetToUtf8 = computeIndicesMapping ? new Uint32Array(utf16Length + 1) : null;
if (computeIndicesMapping)
utf16OffsetToUtf8[utf16Length] = utf8Length;
const utf8OffsetToUtf16 = computeIndicesMapping ? new Uint32Array(utf8Length + 1) : null;
if (computeIndicesMapping)
utf8OffsetToUtf16[utf8Length] = utf16Length;
const utf8Value = new Uint8Array(utf8Length);
let i8 = 0;
for (let i16 = 0; i16 < utf16Length; i16++) {
const charCode = str.charCodeAt(i16);
let codePoint = charCode;
let wasSurrogatePair = false;
if (charCode >= 0xD800 && charCode <= 0xDBFF) {
// Hit a high surrogate, try to look for a matching low surrogate
if (i16 + 1 < utf16Length) {
const nextCharCode = str.charCodeAt(i16 + 1);
if (nextCharCode >= 0xDC00 && nextCharCode <= 0xDFFF) {
// Found the matching low surrogate
codePoint = (((charCode - 0xD800) << 10) + 0x10000) | (nextCharCode - 0xDC00);
wasSurrogatePair = true;
}
}
}
if (computeIndicesMapping) {
utf16OffsetToUtf8[i16] = i8;
if (wasSurrogatePair)
utf16OffsetToUtf8[i16 + 1] = i8;
if (codePoint <= 0x7F) {
utf8OffsetToUtf16[i8 + 0] = i16;
}
else if (codePoint <= 0x7FF) {
utf8OffsetToUtf16[i8 + 0] = i16;
utf8OffsetToUtf16[i8 + 1] = i16;
}
else if (codePoint <= 0xFFFF) {
utf8OffsetToUtf16[i8 + 0] = i16;
utf8OffsetToUtf16[i8 + 1] = i16;
utf8OffsetToUtf16[i8 + 2] = i16;
}
else {
utf8OffsetToUtf16[i8 + 0] = i16;
utf8OffsetToUtf16[i8 + 1] = i16;
utf8OffsetToUtf16[i8 + 2] = i16;
utf8OffsetToUtf16[i8 + 3] = i16;
}
}
if (codePoint <= 0x7F) {
utf8Value[i8++] = codePoint;
}
else if (codePoint <= 0x7FF) {
utf8Value[i8++] = 0b11000000 | ((codePoint & 0b00000000000000000000011111000000) >>> 6);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
}
else if (codePoint <= 0xFFFF) {
utf8Value[i8++] = 0b11100000 | ((codePoint & 0b00000000000000001111000000000000) >>> 12);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
}
else {
utf8Value[i8++] = 0b11110000 | ((codePoint & 0b00000000000111000000000000000000) >>> 18);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000111111000000000000) >>> 12);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000111111000000) >>> 6);
utf8Value[i8++] = 0b10000000 | ((codePoint & 0b00000000000000000000000000111111) >>> 0);
}
if (wasSurrogatePair)
i16++;
}
this.utf16Length = utf16Length;
this.utf8Length = utf8Length;
this.utf16Value = str;
this.utf8Value = utf8Value;
this.utf16OffsetToUtf8 = utf16OffsetToUtf8;
this.utf8OffsetToUtf16 = utf8OffsetToUtf16;
}
createString(onigBinding) {
const result = onigBinding.omalloc(this.utf8Length);
onigBinding.HEAPU8.set(this.utf8Value, result);
return result;
}
}
class OnigString {
static LAST_ID = 0;
static _sharedPtr = 0; // a pointer to a string of 10000 bytes
static _sharedPtrInUse = false;
id = (++OnigString.LAST_ID);
_onigBinding;
content;
utf16Length;
utf8Length;
utf16OffsetToUtf8;
utf8OffsetToUtf16;
ptr;
constructor(str) {
if (!onigBinding)
throw new Error('Must invoke loadWasm first.');
this._onigBinding = onigBinding;
this.content = str;
const utfString = new UtfString(str);
this.utf16Length = utfString.utf16Length;
this.utf8Length = utfString.utf8Length;
this.utf16OffsetToUtf8 = utfString.utf16OffsetToUtf8;
this.utf8OffsetToUtf16 = utfString.utf8OffsetToUtf16;
if (this.utf8Length < 10000 && !OnigString._sharedPtrInUse) {
if (!OnigString._sharedPtr)
OnigString._sharedPtr = onigBinding.omalloc(10000);
OnigString._sharedPtrInUse = true;
onigBinding.HEAPU8.set(utfString.utf8Value, OnigString._sharedPtr);
this.ptr = OnigString._sharedPtr;
}
else {
this.ptr = utfString.createString(onigBinding);
}
}
convertUtf8OffsetToUtf16(utf8Offset) {
if (this.utf8OffsetToUtf16) {
if (utf8Offset < 0)
return 0;
if (utf8Offset > this.utf8Length)
return this.utf16Length;
return this.utf8OffsetToUtf16[utf8Offset];
}
return utf8Offset;
}
convertUtf16OffsetToUtf8(utf16Offset) {
if (this.utf16OffsetToUtf8) {
if (utf16Offset < 0)
return 0;
if (utf16Offset > this.utf16Length)
return this.utf8Length;
return this.utf16OffsetToUtf8[utf16Offset];
}
return utf16Offset;
}
dispose() {
if (this.ptr === OnigString._sharedPtr)
OnigString._sharedPtrInUse = false;
else
this._onigBinding.ofree(this.ptr);
}
}
class OnigScanner {
_onigBinding;
_ptr;
constructor(patterns) {
if (!onigBinding)
throw new Error('Must invoke loadWasm first.');
const strPtrsArr = [];
const strLenArr = [];
for (let i = 0, len = patterns.length; i < len; i++) {
const utfString = new UtfString(patterns[i]);
strPtrsArr[i] = utfString.createString(onigBinding);
strLenArr[i] = utfString.utf8Length;
}
const strPtrsPtr = onigBinding.omalloc(4 * patterns.length);
onigBinding.HEAPU32.set(strPtrsArr, strPtrsPtr / 4);
const strLenPtr = onigBinding.omalloc(4 * patterns.length);
onigBinding.HEAPU32.set(strLenArr, strLenPtr / 4);
const scannerPtr = onigBinding.createOnigScanner(strPtrsPtr, strLenPtr, patterns.length);
for (let i = 0, len = patterns.length; i < len; i++)
onigBinding.ofree(strPtrsArr[i]);
onigBinding.ofree(strLenPtr);
onigBinding.ofree(strPtrsPtr);
if (scannerPtr === 0)
throwLastOnigError(onigBinding);
this._onigBinding = onigBinding;
this._ptr = scannerPtr;
}
dispose() {
this._onigBinding.freeOnigScanner(this._ptr);
}
findNextMatchSync(string, startPosition, arg) {
let debugCall = defaultDebugCall;
let options = 0 /* FindOption.None */;
if (typeof arg === 'number') {
if (arg & 8 /* FindOption.DebugCall */)
debugCall = true;
options = arg;
}
else if (typeof arg === 'boolean') {
debugCall = arg;
}
if (typeof string === 'string') {
string = new OnigString(string);
const result = this._findNextMatchSync(string, startPosition, debugCall, options);
string.dispose();
return result;
}
return this._findNextMatchSync(string, startPosition, debugCall, options);
}
_findNextMatchSync(string, startPosition, debugCall, options) {
const onigBinding = this._onigBinding;
let resultPtr;
if (debugCall)
resultPtr = onigBinding.findNextOnigScannerMatchDbg(this._ptr, string.id, string.ptr, string.utf8Length, string.convertUtf16OffsetToUtf8(startPosition), options);
else
resultPtr = onigBinding.findNextOnigScannerMatch(this._ptr, string.id, string.ptr, string.utf8Length, string.convertUtf16OffsetToUtf8(startPosition), options);
if (resultPtr === 0) {
// no match
return null;
}
const HEAPU32 = onigBinding.HEAPU32;
let offset = resultPtr / 4; // byte offset -> uint32 offset
const index = HEAPU32[offset++];
const count = HEAPU32[offset++];
const captureIndices = [];
for (let i = 0; i < count; i++) {
const beg = string.convertUtf8OffsetToUtf16(HEAPU32[offset++]);
const end = string.convertUtf8OffsetToUtf16(HEAPU32[offset++]);
captureIndices[i] = {
start: beg,
end,
length: end - beg,
};
}
return {
index,
captureIndices,
};
}
}
function isInstantiatorOptionsObject(dataOrOptions) {
return (typeof dataOrOptions.instantiator === 'function');
}
function isInstantiatorModule(dataOrOptions) {
return (typeof dataOrOptions.default === 'function');
}
function isDataOptionsObject(dataOrOptions) {
return (typeof dataOrOptions.data !== 'undefined');
}
function isResponse(dataOrOptions) {
return (typeof Response !== 'undefined' && dataOrOptions instanceof Response);
}
function isArrayBuffer(data) {
return (typeof ArrayBuffer !== 'undefined' && (data instanceof ArrayBuffer || ArrayBuffer.isView(data)))
// eslint-disable-next-line node/prefer-global/buffer
|| (typeof Buffer !== 'undefined' && Buffer.isBuffer(data))
|| (typeof SharedArrayBuffer !== 'undefined' && data instanceof SharedArrayBuffer)
|| (typeof Uint32Array !== 'undefined' && data instanceof Uint32Array);
}
let initPromise;
function loadWasm(options) {
if (initPromise)
return initPromise;
async function _load() {
onigBinding = await main(async (info) => {
let instance = options;
instance = await instance;
if (typeof instance === 'function')
instance = await instance(info);
if (typeof instance === 'function')
instance = await instance(info);
if (isInstantiatorOptionsObject(instance)) {
instance = await instance.instantiator(info);
}
else if (isInstantiatorModule(instance)) {
instance = await instance.default(info);
}
else {
if (isDataOptionsObject(instance))
instance = instance.data;
if (isResponse(instance)) {
if (typeof WebAssembly.instantiateStreaming === 'function')
instance = await _makeResponseStreamingLoader(instance)(info);
else
instance = await _makeResponseNonStreamingLoader(instance)(info);
}
else if (isArrayBuffer(instance)) {
instance = await _makeArrayBufferLoader(instance)(info);
}
}
if ('instance' in instance)
instance = instance.instance;
if ('exports' in instance)
instance = instance.exports;
return instance;
});
}
initPromise = _load();
return initPromise;
}
function _makeArrayBufferLoader(data) {
return importObject => WebAssembly.instantiate(data, importObject);
}
function _makeResponseStreamingLoader(data) {
return importObject => WebAssembly.instantiateStreaming(data, importObject);
}
function _makeResponseNonStreamingLoader(data) {
return async (importObject) => {
const arrayBuffer = await data.arrayBuffer();
return WebAssembly.instantiate(arrayBuffer, importObject);
};
}
function createOnigString(str) {
return new OnigString(str);
}
function createOnigScanner(patterns) {
return new OnigScanner(patterns);
}
/**
* https://github.com/microsoft/vscode/blob/f7f05dee53fb33fe023db2e06e30a89d3094488f/src/vs/platform/theme/common/colorRegistry.ts#L258-L268
*/
const VSCODE_FALLBACK_EDITOR_FG = { light: '#333333', dark: '#bbbbbb' };
const VSCODE_FALLBACK_EDITOR_BG = { light: '#fffffe', dark: '#1e1e1e' };
const RESOLVED_KEY = '__shiki_resolved';
/**
* Normalize a textmate theme to shiki theme
*/
function normalizeTheme(rawTheme) {
// @ts-expect-error private field
if (rawTheme?.[RESOLVED_KEY])
return rawTheme;
const theme = {
...rawTheme,
};
// Fallback settings
if (theme.tokenColors && !theme.settings) {
theme.settings = theme.tokenColors;
delete theme.tokenColors;
}
theme.type ||= 'dark';
theme.colorReplacements = { ...theme.colorReplacements };
theme.settings ||= [];
// Guess fg/bg colors
let { bg, fg } = theme;
if (!bg || !fg) {
/**
* First try:
* Theme might contain a global `tokenColor` without `name` or `scope`
* Used as default value for foreground/background
*/
const globalSetting = theme.settings
? theme.settings.find((s) => !s.name && !s.scope)
: undefined;
if (globalSetting?.settings?.foreground)
fg = globalSetting.settings.foreground;
if (globalSetting?.settings?.background)
bg = globalSetting.settings.background;
/**
* Second try:
* If there's no global `tokenColor` without `name` or `scope`
* Use `editor.foreground` and `editor.background`
*/
if (!fg && theme?.colors?.['editor.foreground'])
fg = theme.colors['editor.foreground'];
if (!bg && theme?.colors?.['editor.background'])
bg = theme.colors['editor.background'];
/**
* Last try:
* If there's no fg/bg color specified in theme, use default
*/
if (!fg)
fg = theme.type === 'light' ? VSCODE_FALLBACK_EDITOR_FG.light : VSCODE_FALLBACK_EDITOR_FG.dark;
if (!bg)
bg = theme.type === 'light' ? VSCODE_FALLBACK_EDITOR_BG.light : VSCODE_FALLBACK_EDITOR_BG.dark;
theme.fg = fg;
theme.bg = bg;
}
// Push a no-scope setting with fallback colors
if (!(theme.settings[0] && theme.settings[0].settings && !theme.settings[0].scope)) {
theme.settings.unshift({
settings: {
foreground: theme.fg,
background: theme.bg,
},
});
}
// Push non-hex colors to color replacements, as `vscode-textmate` doesn't support them
let replacementCount = 0;
const replacementMap = new Map();
function getReplacementColor(value) {
if (replacementMap.has(value))
return replacementMap.get(value);
replacementCount += 1;
const hex = `#${replacementCount.toString(16).padStart(8, '0').toLowerCase()}`;
if (theme.colorReplacements?.[`#${hex}`]) // already exists
return getReplacementColor(value);
replacementMap.set(value, hex);
return hex;
}
theme.settings = theme.settings.map((setting) => {
const replaceFg = setting.settings?.foreground && !setting.settings.foreground.startsWith('#');
const replaceBg = setting.settings?.background && !setting.settings.background.startsWith('#');
if (!replaceFg && !replaceBg)
return setting;
const clone = {
...setting,
settings: {
...setting.settings,
},
};
if (replaceFg) {
const replacement = getReplacementColor(setting.settings.foreground);
theme.colorReplacements[replacement] = setting.settings.foreground;
clone.settings.foreground = replacement;
}
if (replaceBg) {
const replacement = getReplacementColor(setting.settings.background);
theme.colorReplacements[replacement] = setting.settings.background;
clone.settings.background = replacement;
}
return clone;
});
for (const key of Object.keys(theme.colors || {})) {
// Only patch for known keys
if (key === 'editor.foreground' || key === 'editor.background' || key.startsWith('terminal.ansi')) {
if (!theme.colors[key]?.startsWith('#')) {
const replacement = getReplacementColor(theme.colors[key]);
theme.colorReplacements[replacement] = theme.colors[key];
theme.colors[key] = replacement;
}
}
}
Object.defineProperty(theme, RESOLVED_KEY, {
enumerable: false,
writable: false,
value: true,
});
return theme;
}
/**
* @deprecated Use `normalizeTheme` instead.
*/
const toShikiTheme = normalizeTheme;
class Registry extends Registry$1 {
_resolver;
_themes;
_langs;
_resolvedThemes = {};
_resolvedGrammars = {};
_langMap = {};
_langGraph = new Map();
alias = {};
constructor(_resolver, _themes, _langs) {
super(_resolver);
this._resolver = _resolver;
this._themes = _themes;
this._langs = _langs;
_themes.forEach(t => this.loadTheme(t));
_langs.forEach(l => this.loadLanguage(l));
}
getTheme(theme) {
if (typeof theme === 'string')
return this._resolvedThemes[theme];
else
return this.loadTheme(theme);
}
loadTheme(theme) {
const _theme = normalizeTheme(theme);
if (_theme.name)
this._resolvedThemes[_theme.name] = _theme;
return _theme;
}
getLoadedThemes() {
return Object.keys(this._resolvedThemes);
}
getGrammar(name) {
if (this.alias[name]) {
const resolved = new Set([name]);
while (this.alias[name]) {
name = this.alias[name];
if (resolved.has(name))
throw new Error(`[shikiji] Circular alias \`${Array.from(resolved).join(' -> ')} -> ${name}\``);
resolved.add(name);
}
}
return this._resolvedGrammars[name];
}
async loadLanguage(lang) {
if (this.getGrammar(lang.name))
return;
const embeddedLazilyBy = new Set(Object.values(this._langMap).filter(i => i.embeddedLangsLazy?.includes(lang.name)));
this._resolver.addLanguage(lang);
const grammarConfig = {
balancedBracketSelectors: lang.balancedBracketSelectors || ['*'],
unbalancedBracketSelectors: lang.unbalancedBracketSelectors || [],
};
// @ts-expect-error Private members, set this to override the previous grammar (that can be a stub)
this._syncRegistry._rawGrammars.set(lang.scopeName, lang);
const g = await this.loadGrammarWithConfiguration(lang.scopeName, 1, grammarConfig);
this._resolvedGrammars[lang.name] = g;
if (lang.aliases) {
lang.aliases.forEach((alias) => {
this.alias[alias] = lang.name;
});
}
// If there is a language that embeds this language lazily, we need to reload it
if (embeddedLazilyBy.size) {
for (const e of embeddedLazilyBy) {
delete this._resolvedGrammars[e.name];
// @ts-expect-error clear cache
this._syncRegistry?._injectionGrammars?.delete(e.scopeName);
// @ts-expect-error clear cache
this._syncRegistry?._grammars?.delete(e.scopeName);
await this.loadLanguage(this._langMap[e.name]);
}
}
}
async init() {
this._themes.map(t => this.loadTheme(t));
await this.loadLanguages(this._langs);
}
async loadLanguages(langs) {
for (const lang of langs)
this.resolveEmbeddedLanguages(lang);
const langsGraphArray = Array.from(this._langGraph.entries());
const missingLangs = langsGraphArray.filter(([_, lang]) => !lang);
if (missingLangs.length) {
const dependents = langsGraphArray
.filter(([_, lang]) => lang && lang.embeddedLangs?.some(l => missingLangs.map(([name]) => name).includes(l)))
.filter(lang => !missingLangs.includes(lang));
throw new Error(`[shikiji] Missing languages ${missingLangs.map(([name]) => `\`${name}\``).join(', ')}, required by ${dependents.map(([name]) => `\`${name}\``).join(', ')}`);
}
for (const [_, lang] of langsGraphArray)
this._resolver.addLanguage(lang);
for (const [_, lang] of langsGraphArray)
await this.loadLanguage(lang);
}
getLoadedLanguages() {
return Object.keys({ ...this._resolvedGrammars, ...this.alias });
}
resolveEmbeddedLanguages(lang) {
this._langMap[lang.name] = lang;
this._langGraph.set(lang.name, lang);
if (lang.embeddedLangs) {
for (const embeddedLang of lang.embeddedLangs)
this._langGraph.set(embeddedLang, this._langMap[embeddedLang]);
}
}
}
class Resolver {
_langs = new Map();
_scopeToLang = new Map();
_injections = new Map();
_onigLibPromise;
constructor(onigLibPromise, langs) {
this._onigLibPromise = onigLibPromise;
langs.forEach(i => this.addLanguage(i));
}
get onigLib() {
return this._onigLibPromise;
}
getLangRegistration(langIdOrAlias) {
return this._langs.get(langIdOrAlias);
}
async loadGrammar(scopeName) {
return this._scopeToLang.get(scopeName);
}
addLanguage(l) {
this._langs.set(l.name, l);
if (l.aliases) {
l.aliases.forEach((a) => {
this._langs.set(a, l);
});
}
this._scopeToLang.set(l.scopeName, l);
if (l.injectTo) {
l.injectTo.forEach((i) => {
if (!this._injections.get(i))
this._injections.set(i, []);
this._injections.get(i).push(l.scopeName);
});
}
}
getInjections(scopeName) {
const scopeParts = scopeName.split('.');
let injections = [];
for (let i = 1; i <= scopeParts.length; i++) {
const subScopeName = scopeParts.slice(0, i).join('.');
injections = [...injections, ...(this._injections.get(subScopeName) || [])];
}
return injections;
}
}
/**
* Get the minimal shiki context for rendering.
*/
async function getShikiInternal(options = {}) {
async function normalizeGetter(p) {
return Promise.resolve(typeof p === 'function' ? p() : p).then(r => r.default || r);
}
async function resolveLangs(langs) {
return Array.from(new Set((await Promise.all(langs.map(async (lang) => await normalizeGetter(lang).then(r => Array.isArray(r) ? r : [r])))).flat()));
}
const [themes, langs,] = await Promise.all([
Promise.all((options.themes || []).map(normalizeGetter)).then(r => r.map(normalizeTheme)),
resolveLangs(options.langs || []),
options.loadWasm ? loadWasm(options.loadWasm) : undefined,
]);
const resolver = new Resolver(Promise.resolve({
createOnigScanner(patterns) {
return createOnigScanner(patterns);
},
createOnigString(s) {
return createOnigString(s);
},
}), langs);
const _registry = new Registry(resolver, themes, langs);
Object.assign(_registry.alias, options.langAlias);
await _registry.init();
let _lastTheme;
function getLangGrammar(name) {
const _lang = _registry.getGrammar(name);
if (!_lang)
throw new Error(`[shikiji] Language \`${name}\` not found, you may need to load it first`);
return _lang;
}
function getTheme(name) {
const _theme = _registry.getTheme(name);
if (!_theme)
throw new Error(`[shikiji] Theme \`${name}\` not found, you may need to load it first`);
return _theme;
}
function setTheme(name) {
const theme = getTheme(name);
if (_lastTheme !== name) {
_registry.setTheme(theme);
_lastTheme = name;
}
const colorMap = _registry.getColorMap();
return {
theme,
colorMap,
};
}
function getLoadedThemes() {
return _registry.getLoadedThemes();
}
function getLoadedLanguages() {
return _registry.getLoadedLanguages();
}
async function loadLanguage(...langs) {
await _registry.loadLanguages(await resolveLangs(langs));
}
async function loadTheme(...themes) {
await Promise.all(themes.map(async (theme) => _registry.loadTheme(await normalizeGetter(theme))));
}
function updateAlias(alias) {
Object.assign(_registry.alias, alias);
}
function getAlias() {
return _registry.alias;
}
return {
setTheme,
getTheme,
getLangGrammar,
getLoadedThemes,
getLoadedLanguages,
getAlias,
updateAlias,
loadLanguage,
loadTheme,
};
}
/**
* @deprecated Use `getShikiInternal` instead.
*/
const getShikiContext = getShikiInternal;
/**
* Create a Shikiji core highlighter instance, with no languages or themes bundled.
* Wasm and each language and theme must be loaded manually.
*
* @see http://shikiji.netlify.app/guide/install#fine-grained-bundle
*/
async function getHighlighterCore(options = {}) {
const internal = await getShikiInternal(options);
return {
codeToThemedTokens: (code, options) => codeToThemedTokens(internal, code, options),
codeToTokensWithThemes: (code, options) => codeToTokensWithThemes(internal, code, options),
codeToHast: (code, options) => codeToHast(internal, code, options),
codeToHtml: (code, options) => codeToHtml(internal, code, options),
loadLanguage: internal.loadLanguage,
loadTheme: internal.loadTheme,
getTheme: internal.getTheme,
getLangGrammar: internal.getLangGrammar,
setTheme: internal.setTheme,
getLoadedThemes: internal.getLoadedThemes,
getLoadedLanguages: internal.getLoadedLanguages,
getInternalContext: () => internal,
};
}
/**
* Create a `getHighlighter` function with bundled themes and languages.
*
* @param bundledLanguages
* @param bundledThemes
* @param loadWasm
*/
function createdBundledHighlighter(bundledLanguages, bundledThemes, loadWasm) {
async function getHighlighter(options = {}) {
function resolveLang(lang) {
if (typeof lang === 'string') {
if (isSpecialLang(lang))
return [];
const bundle = bundledLanguages[lang];
if (!bundle)
throw new Error(`[shikiji] Language \`${lang}\` is not built-in.`);
return bundle;
}
return lang;
}
function resolveTheme(theme) {
if (typeof theme === 'string') {
const bundle = bundledThemes[theme];
if (!bundle)
throw new Error(`[shikiji] Theme \`${theme}\` is not built-in.`);
return bundle;
}
return theme;
}
const _themes = (options.themes ?? []).map(i => resolveTheme(i));
const langs = (options.langs ?? [])
.map(i => resolveLang(i));
const core = await getHighlighterCore({
...options,
themes: _themes,
langs,
loadWasm,
});
return {
...core,
loadLanguage(...langs) {
return core.loadLanguage(...langs.map(resolveLang));
},
loadTheme(...themes) {
return core.loadTheme(...themes.map(resolveTheme));
},
};
}
return getHighlighter;
}
function createSingletonShorthands(getHighlighter) {
let _shiki;
async function _getHighlighter(options = {}) {
if (!_shiki) {
_shiki = getHighlighter({
themes: toArray(options.theme || []),
langs: toArray(options.lang || []),
});
return _shiki;
}
else {
const s = await _shiki;
await Promise.all([
s.loadTheme(...toArray(options.theme || [])),
s.loadLanguage(...toArray(options.lang || [])),
]);
return s;
}
}
return {
getSingletonHighlighter: () => _getHighlighter(),
async codeToHtml(code, options) {
const shiki = await _getHighlighter({
lang: options.lang,
theme: ('theme' in options ? [options.theme] : Object.values(options.themes)),
});
return shiki.codeToHtml(code, options);
},
async codeToHast(code, options) {
const shiki = await _getHighlighter({
lang: options.lang,
theme: ('theme' in options ? [options.theme] : Object.values(options.themes)),
});
return shiki.codeToHast(code, options);
},
async codeToThemedTokens(code, options) {
const shiki = await _getHighlighter(options);
return shiki.codeToThemedTokens(code, options);
},
async codeToTokensWithThemes(code, options) {
const shiki = await _getHighlighter({
lang: options.lang,
theme: Object.values(options.themes).filter(Boolean),
});
return shiki.codeToTokensWithThemes(code, options);
},
};
}
export { FontStyle, addClassToHast, codeToHast, codeToHtml, codeToThemedTokens, codeToTokensWithThemes, createSingletonShorthands, createdBundledHighlighter, getHighlighterCore, getShikiContext, getShikiInternal, isPlaintext, isSpecialLang, loadWasm, normalizeTheme, toArray, toShikiTheme, tokenizeAnsiWithTheme };