Practical Example
/**
* The interface for elements.
*/
export interface Elements {
/**
* A root element.
*/
root: HTMLElement;
/**
* A container element that contains code.
*/
code: HTMLElement;
}
/**
* The class for highlighting code via provided tokens.
*
* @since 0.0.1
*/
export class Highlighter {
/**
* Holds elements.
*/
protected elements: Elements;
/**
* Holds options.
*/
protected readonly options: Options;
/**
* Holds the EventBus instance.
*/
protected readonly event: EventBus = new EventBus();
/**
* The Highlighter constructor.
*
* @param elm - An element to highlight.
* @param options - Optional. Options.
*/
constructor( elm: HTMLElement, options: Options ) {
this.elements = {
root: elm,
code: create( 'div', { class: `${ PROJECT_CODE }__code` } ),
}
this.options = options;
this.init();
}
/**
* Initializes the instance.
*/
protected init(): void {
forOwn( Components, Component => {
Component( this.elements, this.event, this.options );
} );
this.event.emit( 'mounted', this );
}
/**
* Creates a div element for each line.
*
* @return A created div element.
*/
protected createLine(): HTMLDivElement {
return create( 'div', { class: `${ PROJECT_CODE }__line` } );
}
/**
* Creates a span element for each token.
*
* @param category - A category name.
* @param value - A text value.
*
* @return A created span element.
*/
protected createSpan( category: string, value: string ): HTMLSpanElement {
const className = `${ PROJECT_CODE_SHORT }__token ${ PROJECT_CODE_SHORT }__${ category }`;
const span = create( 'span', { class: className } );
text( span, value );
return span;
}
/**
* Append tokens to the provided element with creating div elements for each line.
*
* @param elm - An element to append tokens to.
* @param tokens - An array with tokens.
*/
protected append( elm: HTMLElement, tokens: Token[] ): void {
let line = this.createLine();
const lines = [ line ];
tokens.forEach( ( token ) => {
if ( token[ 0 ] === 'eol' ) {
line = this.createLine();
lines.push( line );
} else {
append( line, this.createSpan( token[ 0 ], token[ 1 ] ) );
}
} );
append( elm, ...lines );
}
/**
* Render tokens as HTML elements.
*
* @param tokens - Tokens to render.
*/
render( tokens: Token[] ): void {
const { root, code } = this.elements;
this.append( code, tokens );
text( root, '' );
append( root, code );
root.classList.add( PROJECT_CODE );
this.event.emit( 'rendered', this );
}
/**
* Destroys the instance.
*/
destroy(): void {
this.event.emit( 'destroy' );
this.elements = null;
}
}
Comments/Strings
/**
* Multiline comment
* 'Should not be a string'
* "Should not be a string"
*/
/* Multiline comment in a single line */
// Single line comment
// 'Should not be a string'
// "Should not be a string"
'Single quote'
'Single \'quote\' with escape'
'Single 'quote' with single quote'
'Single "quote" with double quote'
'Single `quote` with back quote'
"Double quote"
"Double \"quote\" with escape"
"Double "quote" with double quote"
"Double 'quote' with single quote"
"Double `quote` with back quote"
`Back quote`
'Back \`quote\` with escape'
'Back `quote` with back quote'
'Back 'quote' with single quote'
'Back "quote" with double quote'
'/* Should not be a comment */'
'// Should not be a comment'
"/* Should not be a comment */"
"// Should not be a comment"
`/* Should not be a comment */`
`// Should not be a comment`
RegExp
/^.*?[\n\s]/gmsi
Template Literal
`Multiline
template
literal`
`The result will be ${ ( a + b ) * 3 }`
// Nested template literal
`container ${
isMobile()
// ${ comment }
// `
? 'is-mobile'
: `container--${ page.isFront() ? 'front' : 'page' }`
}`;
Functions/Generics
// Function
function apply<T extends object>( value: T ) {}
// Anonymous function
const a = function <T extends object>( value: T ) {}
// Arrow function
<T extends object> ( value: T ) => {}
// Method
{
apply<T extends object>( value: T ) {}
}
type Assign<T, U> = Omit<T, keyof U> & U;
export function assign<T extends object, U extends object[]>( object: T, ...sources: U ): Assign<T, U> {
const keys: Array<string | symbol> = getKeys( source );
if ( a < 1 && b > d ) {
console.log( a );
}
for ( let i = 0; i < keys.length; i++ ) {
}
}
return object;
}
Typing
declare var process: any;
type Token = [ string, number, ...RegExp[] ];
interface CustomDivElement extends HTMLDivElement {
selectionStart: number,
selectionEnd: number,
setSelection( number, number ): void;
}
namespace Lexer {
export interface Grammar {
main: Tokenizers[];
[ key: string ]: Tokenizers[];
}
}
function isArray<T>( subject: T[] ): subject is T[] {
return Array.isArray( subject );
}
Keywords
declare, keyof, namespace, readonly, type, string,
number, boolean, bigint, symbol, any, never, unknown
Classes
Object.keys( object );
class Component {
constructor() {
}
}
const component = new Component();
Booleans
true, false
Numbers
0 1 1.23 .23 +1.23 -1.23
1e10 1e+10 1e-10 1E10 1E+10 1E-10
1.2e10 1.2e+10 1.2e-10 1.2E10 1.2E+10 1.2E-10
Operators
+ - ~ ! / * % ** < > <= >= == != === !==
<< >> >>> & | ^ && || ?? ?
= *= **= /= %= += -= <<= >>= >>>= &= ^= |= &&= ||= ??= :
Brackets
{} () []
Delimiters
; . ,
;;;; .... ,,,,