0 directories, 46 files

util

Home / tinai / util
/**
 * YAML grammar for Highlight.js, adapted from the official 11.12.0 source
 * (https://github.com/highlightjs/highlight.js/blob/11.12.0/src/languages/yaml.js)
 * with these fixes over the stock grammar:
 * - merge keys (<<:) are highlighted as keys instead of strings
 * - anchors/aliases (&x, *x) are highlighted mid-line, not only at line end
 * - dotted numeric sequences (IP addresses, versions) stay one token
 */

/**
 * Returns the YAML grammar definition for hljs.registerLanguage.
 * @param {Object} hljs - The Highlight.js instance.
 * @returns {Object} The grammar definition.
 */
export default function(hljs) {
	const LITERALS = 'true false yes no null';

	// YAML spec allows non-reserved URI characters in tags.
	const URI_CHARACTERS = '[\\w#;/?:@&=+$,.~*\'()[\\]]+';

	// Define keys as starting with a word character
	// ...containing word chars, spaces, colons, forward-slashes, hyphens and periods
	// ...and ending with a colon followed immediately by a space, tab or newline.
	// The YAML spec allows for much more than this, but this covers most use-cases.
	const KEY = {
		className: 'attr',
		variants: [
			// added brackets support and special char support
			{ begin: /[\w*@][\w*@ :()\./-]*:(?=[ \t]|$)/ },
			// merge keys
			{ begin: /<<:(?=[ \t]|$)/ },
			{ // double quoted keys - with brackets and special char support
				begin: /"[\w*@][\w*@ :()\./-]*":(?=[ \t]|$)/ },
			{ // single quoted keys - with brackets and special char support
				begin: /'[\w*@][\w*@ :()\./-]*':(?=[ \t]|$)/ },
		]
	};

	const TEMPLATE_VARIABLES = {
		className: 'template-variable',
		variants: [
			{ // jinja templates Ansible
				begin: /\{\{/,
				end: /\}\}/
			},
			{ // Ruby i18n
				begin: /%\{/,
				end: /\}/
			}
		]
	};

	const SINGLE_QUOTE_STRING = {
		className: 'string',
		relevance: 0,
		begin: /'/,
		end: /'/,
		contains: [
			{
				match: /''/,
				scope: 'char.escape',
				relevance: 0
			}
		]
	};

	const STRING = {
		className: 'string',
		relevance: 0,
		variants: [
			{
				begin: /"/,
				end: /"/
			},
			{ begin: /\S+/ }
		],
		contains: [
			hljs.BACKSLASH_ESCAPE,
			TEMPLATE_VARIABLES
		]
	};

	// Strings inside of value containers (objects) can't contain braces,
	// brackets, or commas
	const CONTAINER_STRING = hljs.inherit(STRING, { variants: [
		{
			begin: /'/,
			end: /'/,
			contains: [
				{
					begin: /''/,
					relevance: 0
				}
			]
		},
		{
			begin: /"/,
			end: /"/
		},
		{ begin: /[^\s,{}[\]]+/ }
	] });

	const DATE_RE = '[0-9]{4}(-[0-9][0-9]){0,2}';
	const TIME_RE = '([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?';
	const FRACTION_RE = '(\\.[0-9]*)?';
	const ZONE_RE = '([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?';
	const TIMESTAMP = {
		className: 'number',
		begin: '\\b' + DATE_RE + TIME_RE + FRACTION_RE + ZONE_RE + '\\b'
	};

	const VALUE_CONTAINER = {
		end: ',',
		endsWithParent: true,
		excludeEnd: true,
		keywords: LITERALS,
		relevance: 0
	};
	const OBJECT = {
		begin: /\{/,
		end: /\}/,
		contains: [ VALUE_CONTAINER ],
		illegal: '\\n',
		relevance: 0
	};
	const ARRAY = {
		begin: '\\[',
		end: '\\]',
		contains: [ VALUE_CONTAINER ],
		illegal: '\\n',
		relevance: 0
	};

	const MODES = [
		KEY,
		{
			className: 'meta',
			begin: '^---\\s*$',
			relevance: 10
		},
		{ // multi line string
			// Blocks start with a | or > followed by a newline
			//
			// Indentation of subsequent lines must be the same to
			// be considered part of the block
			className: 'string',
			begin: '[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*'
		},
		{ // Ruby/Rails erb
			begin: '<%[%=-]?',
			end: '[%-]?%>',
			subLanguage: 'ruby',
			excludeBegin: true,
			excludeEnd: true,
			relevance: 0
		},
		{ // named tags
			className: 'type',
			begin: '!\\w+!' + URI_CHARACTERS
		},
		// https://yaml.org/spec/1.2/spec.html#id2784064
		{ // verbatim tags
			className: 'type',
			begin: '!<' + URI_CHARACTERS + ">"
		},
		{ // primary tags
			className: 'type',
			begin: '!' + URI_CHARACTERS
		},
		{ // secondary tags
			className: 'type',
			begin: '!!' + URI_CHARACTERS
		},
		{ // fragment id &ref, also mid-line (e.g. "key: &anchor value")
			className: 'meta',
			begin: '&' + hljs.UNDERSCORE_IDENT_RE
		},
		{ // fragment reference *ref, also mid-line
			className: 'meta',
			begin: '\\*' + hljs.UNDERSCORE_IDENT_RE
		},
		{ // dotted numeric sequences like IPv4 addresses or version numbers
			className: 'string',
			begin: /(\b\d{1,3}\.){2}\d{1,3}(\.\d+)*/
		},
		{ // array listing
			className: 'bullet',
			// TODO: remove |$ hack when we have proper look-ahead support
			begin: '-(?=[ ]|$)',
			relevance: 0
		},
		hljs.HASH_COMMENT_MODE,
		{
			beginKeywords: LITERALS,
			keywords: { literal: LITERALS }
		},
		TIMESTAMP,
		// numbers are any valid C-style number that
		// sit isolated from other words
		{
			className: 'number',
			begin: hljs.C_NUMBER_RE + '\\b',
			relevance: 0
		},
		OBJECT,
		ARRAY,
		SINGLE_QUOTE_STRING,
		STRING
	];

	const VALUE_MODES = [ ...MODES ];
	VALUE_MODES.pop();
	VALUE_MODES.push(CONTAINER_STRING);
	VALUE_CONTAINER.contains = VALUE_MODES;

	return {
		name: 'YAML',
		case_insensitive: true,
		aliases: [ 'yml' ],
		contains: MODES
	};
}
🌐
hljs-yaml.js ×
Type: Web, text/plain
5.07 Kilobytes
Last Modified 2026-11-04 09:48:28
⬇ Download File