Files
languageservices/actions-workflow-parser/src/templates/tokens/template-token.ts
T

226 lines
6.5 KiB
TypeScript

import {BooleanToken, MappingToken, NullToken, NumberToken, ScalarToken, SequenceToken, StringToken} from ".";
import {Definition} from "../schema/definition";
import {DefinitionInfo} from "../schema/definition-info";
import {PropertyDefinition} from "../schema/property-definition";
import {SerializedToken} from "./serialization";
import {TokenRange} from "./token-range";
import {TraversalState} from "./traversal-state";
import {TokenType, tokenTypeName} from "./types";
export class TemplateTokenError extends Error {
constructor(message: string, public readonly token?: TemplateToken) {
super(message);
}
}
export abstract class TemplateToken {
// Fields for serialization
private readonly type: TokenType;
private _description: string | undefined;
public readonly file: number | undefined;
public readonly range: TokenRange | undefined;
public definitionInfo: DefinitionInfo | undefined;
public propertyDefinition: PropertyDefinition | undefined;
/**
* Base class for all template tokens
*/
public constructor(
type: TokenType,
file: number | undefined,
range: TokenRange | undefined,
definitionInfo: DefinitionInfo | undefined
) {
this.type = type;
this.file = file;
this.range = range;
this.definitionInfo = definitionInfo;
}
public get templateTokenType(): number {
return this.type;
}
public get line(): number | undefined {
return this.range?.start.line;
}
public get col(): number | undefined {
return this.range?.start.column;
}
public get definition(): Definition | undefined {
return this.definitionInfo?.definition;
}
get description(): string | undefined {
return this._description || this.propertyDefinition?.description || this.definition?.description;
}
set description(description: string | undefined) {
this._description = description;
}
public abstract get isScalar(): boolean;
public abstract get isLiteral(): boolean;
public abstract get isExpression(): boolean;
public abstract clone(omitSource?: boolean): TemplateToken;
public typeName(): string {
return tokenTypeName(this.type);
}
/**
* Asserts expected type and throws a good debug message if unexpected
*/
public assertNull(objectDescription: string): NullToken {
if (this.type === TokenType.Null) {
return this as unknown as NullToken;
}
throw new TemplateTokenError(
`Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName(
TokenType.Null
)}' was expected.`,
this
);
}
/**
* Asserts expected type and throws a good debug message if unexpected
*/
public assertBoolean(objectDescription: string): BooleanToken {
if (this.type === TokenType.Boolean) {
return this as unknown as BooleanToken;
}
throw new TemplateTokenError(
`Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName(
TokenType.Boolean
)}' was expected.`,
this
);
}
/**
* Asserts expected type and throws a good debug message if unexpected
*/
public assertNumber(objectDescription: string): NumberToken {
if (this.type === TokenType.Number) {
return this as unknown as NumberToken;
}
throw new TemplateTokenError(
`Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName(
TokenType.Number
)}' was expected.`,
this
);
}
/**
* Asserts expected type and throws a good debug message if unexpected
*/
public assertString(objectDescription: string): StringToken {
if (this.type === TokenType.String) {
return this as unknown as StringToken;
}
throw new TemplateTokenError(
`Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName(
TokenType.String
)}' was expected.`,
this
);
}
/**
* Asserts expected type and throws a good debug message if unexpected
*/
public assertScalar(objectDescription: string): ScalarToken {
if ((this as unknown as ScalarToken | undefined)?.isScalar === true) {
return this as unknown as ScalarToken;
}
throw new TemplateTokenError(
`Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type 'ScalarToken' was expected.`,
this
);
}
/**
* Asserts expected type and throws a good debug message if unexpected
*/
public assertSequence(objectDescription: string): SequenceToken {
if (this.type === TokenType.Sequence) {
return this as unknown as SequenceToken;
}
throw new TemplateTokenError(
`Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName(
TokenType.Sequence
)}' was expected.`,
this
);
}
/**
* Asserts expected type and throws a good debug message if unexpected
*/
public assertMapping(objectDescription: string): MappingToken {
if (this.type === TokenType.Mapping) {
return this as unknown as MappingToken;
}
throw new TemplateTokenError(
`Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName(
TokenType.Mapping
)}' was expected.`,
this
);
}
/**
* Returns all tokens (depth first)
* @param value The object to travese
* @param omitKeys Whether to omit mapping keys
*/
public static *traverse(
value: TemplateToken,
omitKeys?: boolean
): Generator<[parent: TemplateToken | undefined, token: TemplateToken, keyToken: TemplateToken | undefined], void> {
yield [undefined, value, undefined];
switch (value.templateTokenType) {
case TokenType.Sequence:
case TokenType.Mapping: {
let state: TraversalState | undefined = new TraversalState(undefined, value);
state = new TraversalState(state, value);
while (state.parent) {
if (state.moveNext(omitKeys ?? false)) {
value = state.current as TemplateToken;
yield [state.parent?.current, value, state.currentKey];
switch (value.type) {
case TokenType.Sequence:
case TokenType.Mapping:
state = new TraversalState(state, value);
break;
}
} else {
state = state.parent;
}
}
break;
}
}
}
public toJSON(): SerializedToken {
return undefined;
}
}