export function parseFunctionExpressionOrMetaProperty(
	parser: JSParser,
): FunctionExpression | MetaProperty {
	const start = parser.getPosition();
	parser.next();

	// We do not do parseIdentifier here because when parseFunctionExpressionOrMetaProperty

	// is called we already know that the current token is a "name" with the value "function"

	// This will improve perf a tiny little bit as we do not do validation but more importantly

	// here is that parseIdentifier will remove an item from the expression stack

	// if "function" or "class" is parsed as identifier (in objects e.g.), which should not happen here.
	const meta = createIdentifier(parser, start, "function");

	if (parser.inScope("GENERATOR") && parser.eat(tt.dot)) {
		return parseMetaProperty(parser, start, meta, "sent");
	}

	const node = parseFunctionExpression(parser, start, false);

	if (node.type !== "FunctionExpression") {
		throw new Error("Expected parseFunction to return a FunctionExpression");
	}

	return node;
}


export function parseMetaProperty(
	parser: JSParser,
	start: Position,
	meta: Identifier,
	propertyName: string,
): MetaProperty {
	if (
		meta.name === "function" &&
		propertyName === "sent" &&
		!parser.isContextual(propertyName)
	) {
		// They didn't actually say `function.sent`, just `function.`, so a simple error would be less confusing.
		parser.unexpectedToken();
	}

	const escapePosition = parser.state.escapePosition;
	const property = parseIdentifier(parser, true);

	if (property.name === propertyName) {
		parser.banUnicodeEscape(escapePosition, propertyName);
	} else {
		parser.addDiagnostic({
			loc: property.loc,
			description: descriptions.JS_PARSER.INVALID_META_PROPERTY(
				meta.name,
				propertyName,
			),
		});
	}

	return parser.finishNode(
		start,
		{
			type: "MetaProperty",
			meta,
			property,
		},
	);
}


export function parseImportMetaProperty(parser: JSParser): MetaProperty {
	const start = parser.getPosition();
	const id = parseIdentifier(parser, true);
	parser.expect(tt.dot);
	const node = parseMetaProperty(parser, start, id, "meta");

	if (!parser.inModule) {
		parser.addDiagnostic({
			loc: node.loc,
			description: descriptions.JS_PARSER.IMPORT_META_OUTSIDE_MODULE,
		});
	}

	return node;
}


export function parseParenExpression(
	parser: JSParser,
	context: ExpressionContext,
): AnyExpression {
	const openContext = parser.expectOpening(tt.parenL, tt.parenR, context);
	const val = parseExpression(parser, context);
	parser.expectClosing(openContext);
	return val;
}


export function parseParenAndDistinguishExpression(
	parser: JSParser,
	context: ExpressionContext,
	canBeArrow: boolean,
): AnyExpression {
	if (parser.state.noArrowAt.includes(parser.state.startPos.index)) {
		canBeArrow = false;
	}

	const startPos = parser.state.startPos;

	const openContext = parser.expectOpening(
		tt.parenL,
		tt.parenR,
		"paren expression",
	);

	const oldMaybeInArrowParameters = parser.state.maybeInArrowParameters;
	const oldYieldPos = parser.state.yieldPos;
	const oldAwaitPos = parser.state.awaitPos;
	const oldYield = parser.state.yieldInPossibleArrowParameters;
	parser.state.maybeInArrowParameters = true;
	parser.state.yieldInPossibleArrowParameters = undefined;
	parser.state.yieldPos = ob1Number0;
	parser.state.awaitPos = ob1Number0;

	const innerStart = parser.getPosition();
	const exprList: Array<ToReferencedItem> = [];
	const refShorthandDefaultPos: IndexTracker = createIndexTracker();
	const refNeedsArrowPos: IndexTracker = createIndexTracker();
	let first = true;
	let spreadStart;
	let optionalCommaStart;

	while (!parser.match(tt.parenR)) {
		if (first) {
			first = false;
		} else {
			if (
				!parser.expect(
					tt.comma,
					refNeedsArrowPos.index === ob1Number0
						? undefined
						: parser.getPositionFromIndex(refNeedsArrowPos.index),
				)
			) {
				break;
			}

			if (parser.match(tt.parenR)) {
				optionalCommaStart = parser.state.startPos;
				break;
			}
		}

		if (parser.match(tt.ellipsis)) {
			const spreadNodeStartPos = parser.state.startPos;
			spreadStart = parser.state.startPos;
			exprList.push(
				parseParenItem(parser, parseSpread(parser), spreadNodeStartPos),
			);

			if (parser.match(tt.comma) && parser.lookaheadState().tokenType === tt.parenR) {
				raiseRestNotLast(parser);
				parser.eat(tt.comma);
			}
		} else {
			exprList.push(
				parseMaybeAssign<ReturnType<typeof parseParenItem>>(
					parser,
					context,
					false,
					refShorthandDefaultPos,
					parseParenItem,
					refNeedsArrowPos,
				),
			);
		}
	}

	const innerEnd = parser.getPosition();
	parser.expectClosing(openContext);

	parser.state.maybeInArrowParameters = oldMaybeInArrowParameters;

	const arrowStart = startPos;
	if (canBeArrow && shouldParseArrow(parser)) {
		const {valid, returnType} = parseArrowHead(parser);

		if (valid) {
			checkYieldAwaitInDefaultParams(parser);
			parser.state.yieldPos = oldYieldPos;
			parser.state.awaitPos = oldAwaitPos;

			for (const param of exprList) {
				if (parser.isParenthesized(param)) {
					parser.addDiagnostic({
						loc: param.loc,
						description: descriptions.JS_PARSER.PARENTHESIZED_FUNCTION_PARAMS,
					});
				}
			}

			const arrow = parseArrowExpression(
				parser,
				arrowStart,
				{
					assignmentList: exprList,
				},
			);
			parser.state.yieldInPossibleArrowParameters = oldYield;
			return {
				...arrow,
				head: {
					...arrow.head,
					returnType,
				},
			};
		}
	}

	parser.state.yieldInPossibleArrowParameters = oldYield;

	// We keep the old value if it isn't null, for cases like

	//   (x = (yield)) => {}
	parser.state.yieldPos = oldYieldPos || parser.state.yieldPos;
	parser.state.awaitPos = oldAwaitPos || parser.state.awaitPos;

	if (exprList.length === 0) {
		parser.addDiagnostic({
			start: innerStart,
			end: innerEnd,
			description: descriptions.JS_PARSER.EMPTY_PARENTHESIZED_EXPRESSION,
		});

		exprList.push(
			toReferenceIdentifier(
				parser,
				parser.createUnknownIdentifier(
					"empty parenthesized expression",
					innerStart,
					innerEnd,
				),
			),
		);
	}

	if (optionalCommaStart !== undefined) {
		parser.unexpectedToken(optionalCommaStart);
	}

	if (spreadStart !== undefined) {
		parser.unexpectedToken(spreadStart);
	}

	if (ob1Get0(refShorthandDefaultPos.index) > 0) {
		parser.unexpectedToken(
			parser.getPositionFromIndex(refShorthandDefaultPos.index),
		);
	}

	if (ob1Get0(refNeedsArrowPos.index) > 0) {
		parser.unexpectedToken(parser.getPositionFromIndex(refNeedsArrowPos.index));
	}

	const filterList = filterSpread(
		parser,
		toReferencedListDeep(parser, exprList, /* isParenthesizedExpr */ true),
	);

	let val: AnyExpression = filterList[0];
	if (filterList.length > 1) {
		val = parser.finishNodeAt(
			innerStart,
			innerEnd,
			{
				type: "SequenceExpression",
				expressions: filterList,
			},
		);
	}

	parser.addParenthesized(val);

	return val;
}


export function shouldParseArrow(parser: JSParser): boolean {
	return parser.match(tt.colon) || !parser.canInsertSemicolon();
}


export function parseArrowHead(
	parser: JSParser,
): {
	valid: boolean;
	returnType: undefined | AnyTSPrimary;
} {
	if (parser.match(tt.colon)) {
		const oldNoAnonFunctionType = parser.state.noAnonFunctionType;
		parser.state.noAnonFunctionType = true;

		const branch = parser.createBranch<undefined | AnyTSPrimary>();

		branch.add(() => {
			const res = parseTSTypeOrTypePredicateAnnotation(parser, tt.colon);

			if (parser.canInsertSemicolon()) {
				// No semicolon insertion expected
				return undefined;
			}

			if (parser.eat(tt.arrow)) {
				return res;
			}

			return undefined;
		});

		if (branch.hasBranch()) {
			const returnType = branch.pick();
			parser.state.noAnonFunctionType = oldNoAnonFunctionType;

			if (returnType === undefined) {
				throw new Error(
					"hasBranchResult call above should have refined this condition",
				);
			}

			return {
				valid: true,
				returnType,
			};
		} else {
			parser.state.noAnonFunctionType = oldNoAnonFunctionType;
			return {
				valid: false,
				returnType: undefined,
			};
		}
	} else {
		return {
			valid: parser.eat(tt.arrow),
			returnType: undefined,
		};
	}
}


// Parse a possible function param or call argument
export function parseParenItem(
	parser: JSParser,
	node: AnyExpression | SpreadElement,
	startPos: Position,
): ToReferencedItem {
	let optional: undefined | boolean = undefined;
	if (parser.eat(tt.question)) {
		optional = true;
	}

	if (parser.match(tt.colon)) {
		const typeAnnotation = parseTSTypeAnnotation(parser, true);
		return parser.finishNode(
			startPos,
			{
				type: "AmbiguousFlowTypeCastExpression",
				expression: node,
				typeAnnotation,
				optional,
			},
		);
	}

	if (optional) {
		return parser.finishNode(
			startPos,
			{
				type: "AmbiguousFlowTypeCastExpression",
				expression: node,
				typeAnnotation: undefined,
				optional,
			},
		);
	}

	return node;
}


export function parseNew(parser: JSParser): NewExpression | MetaProperty {
	const start = parser.getPosition();
	const meta = parseIdentifier(parser, true);

	if (parser.eat(tt.dot)) {
		const metaProp = parseMetaProperty(parser, start, meta, "target");

		if (!parser.inScope("NON_ARROW_FUNCTION") && !parser.inScope("CLASS_PROPERTY")) {
			parser.addDiagnostic({
				loc: metaProp.loc,
				description: descriptions.JS_PARSER.NEW_TARGET_OUTSIDE_CLASS,
			});
		}

		return metaProp;
	}

	const callee = parseNoCallExpr(parser, "new callee");

	if (callee.type === "ImportCall") {
		parser.addDiagnostic({
			loc: callee.loc,
			description: descriptions.JS_PARSER.SUPER_OUTSIDE_METHOD,
		});
	}

	const optionalMember = getFirstOptionalChainMember(callee);
	if (optionalMember !== undefined) {
		const memberLoc = parser.getLoc(optionalMember);

		parser.addDiagnostic({
			description: descriptions.JS_PARSER.NEW_IN_OPTIONAL_CHAIN(memberLoc),
		});
	}

	if (parser.eat(tt.questionDot)) {
		parser.addDiagnostic({
			description: descriptions.JS_PARSER.NEW_IN_OPTIONAL_CHAIN(),
		});
	}

	let optional = undefined;
	if (parser.eat(tt.questionDot)) {
		optional = true;
	}

	let typeArguments = undefined;
	if (parser.isSyntaxEnabled("ts") && parser.isRelational("<")) {
		typeArguments = parser.tryBranch(parseTSTypeArguments);
	}

	let args: Array<AnyExpression | SpreadElement> = [];
	if (parser.match(tt.parenL)) {
		const openContext = parser.expectOpening(tt.parenL, tt.parenR, "new argument");
		args = parseExpressionListNonEmpty(
			parser,
			"new expression argument",
			openContext,
		);
		args = toReferencedList(parser, args);
	} else if (parser.isSyntaxEnabled("ts") && typeArguments !== undefined) {
		parser.addDiagnostic({
			description: descriptions.JS_PARSER.NEW_WITH_TYPESCRIPT_TYPE_ARGUMENTS_NO_PARENS,
		});
	}

	return parser.finishNode(
		start,
		{
			type: "NewExpression",
			callee,
			typeArguments,
			arguments: args,
			optional,
		},
	);
}


function getFirstOptionalChainMember(
	node: AnyNode,
): undefined | OptionalCallExpression | MemberExpression {
	if (node.type === "OptionalCallExpression") {
		return node;
	}

	if (node.type === "MemberExpression") {
		if (node.property.optional) {
			return node;
		}

		if (node.property.type === "StaticMemberProperty") {
			return getFirstOptionalChainMember(node.object);
		}
	}

	return undefined;
}


// Parse template expression.
export function parseTemplateElement(
	parser: JSParser,
	isTagged: boolean,
): TemplateElement {
	const start = parser.getPosition();
	const tokenValue = parser.state.tokenValue;

	if (tokenValue === undefined) {
		if (isTagged) {
			parser.state.invalidTemplateEscapePosition = undefined;
		} else {
			parser.addDiagnostic({
				index: parser.state.invalidTemplateEscapePosition,
				description: descriptions.JS_PARSER.INVALID_TEMPLATE_ESCAPE,
			});
		}
	}

	const raw = parser.getRawInput(
		parser.state.startPos.index,
		parser.state.endPos.index,
	).replace(/\r\n?/g, "\n");
	const cooked = tokenValue === undefined ? raw : String(tokenValue);

	parser.next();
	const tail = parser.match(tt.backQuote);
	return parser.finishNode(
		start,
		{
			type: "TemplateElement",
			raw,
			cooked,
			tail,
		},
	);
}


export function parseTemplate(
	parser: JSParser,
	isTagged: boolean,
): TemplateLiteral {
	const start = parser.getPosition();
	const openContext = parser.expectOpening(
		tt.backQuote,
		tt.backQuote,
		"template literal",
	);
	const expressions = [];
	let curElt = parseTemplateElement(parser, isTagged);
	const quasis = [curElt];

	while (true) {
		if (parser.match(tt.eof) || curElt.tail === true) {
			break;
		}

		const exprPpenContext = parser.expectOpening(
			tt.dollarBraceL,
			tt.braceR,
			"template expression value",
		);
		expressions.push(parseExpression(parser, "template expression value"));
		parser.expectClosing(exprPpenContext);

		curElt = parseTemplateElement(parser, isTagged);
		quasis.push(curElt);
	}

	parser.expectClosing(openContext);

	return parser.finishNode(
		start,
		{
			type: "TemplateLiteral",
			expressions,
			quasis,
		},
	);
}


export function parseObjectExpression(
	parser: JSParser,
	refShorthandDefaultPos?: IndexTracker,
): ObjectExpression {
	const propHash: Set<string> = new Set();
	let first = true;

	const start = parser.getPosition();
	const properties = [];

	const openContext = parser.expectOpening(tt.braceL, tt.braceR, "object");

	while (true) {
		if (parser.match(tt.braceR) || parser.match(tt.eof)) {
			parser.expectClosing(openContext);
			break;
		}

		if (first) {
			first = false;
		} else {
			if (!parser.expect(tt.comma)) {
				break;
			}

			if (parser.eat(tt.braceR)) {
				break;
			}
		}

		if (parser.match(tt.ellipsis)) {
			const prop: SpreadProperty = {
				...parseSpread(parser),
				type: "SpreadProperty",
			};
			properties.push(prop);
			continue;
		}

		const start = parser.getPosition();
		let isGenerator = parser.eat(tt.star);
		let isAsync = false;

		let key: StaticPropertyKey | ComputedPropertyKey;
		let escapePosition;

		if (parser.isContextual("async")) {
			if (isGenerator) {
				parser.unexpectedToken();
			}

			const asyncId = parseIdentifier(parser);
			if (
				parser.match(tt.colon) ||
				parser.match(tt.parenL) ||
				parser.match(tt.braceR) ||
				parser.match(tt.eq) ||
				parser.match(tt.comma)
			) {
				key = {
					type: "StaticPropertyKey",
					loc: asyncId.loc,
					value: asyncId,
				};
			} else {
				if (parser.hasPrecedingLineBreak()) {
					parser.addDiagnostic({
						description: descriptions.JS_PARSER.ASYNC_OBJECT_METHOD_LINE_BREAK,
					});
				}

				isAsync = true;
				if (parser.match(tt.star)) {
					parser.next();
					isGenerator = true;
				}
				escapePosition = parser.state.escapePosition;
				key = parseObjectPropertyKey(parser);
			}
		} else {
			escapePosition = parser.state.escapePosition;
			key = parseObjectPropertyKey(parser);
		}

		const prop = parseObjectPropertyValue(
			parser,
			{
				key,
				start,
				isGenerator,
				isAsync,
				isPattern: false,
				refShorthandDefaultPos,
				escapePosition,
			},
		);
		if (prop === undefined) {
			continue;
		}
		if (prop.type === "BindingObjectPatternProperty") {
			throw new Error("Impossible");
		}

		checkPropClash(parser, prop, propHash);
		properties.push(prop);
	}

	return parser.finishNode(
		start,
		{
			type: "ObjectExpression",
			properties,
		},
	);
}


export function parseObjectPattern(
	parser: JSParser,
	refShorthandDefaultPos?: IndexTracker,
): BindingObjectPattern {
	const propHash: Set<string> = new Set();
	let first = true;

	const start = parser.getPosition();
	const properties: Array<BindingObjectPatternProperty> = [];
	let rest: undefined | BindingIdentifier;

	const openContext = parser.expectOpening(
		tt.braceL,
		tt.braceR,
		"object pattern",
	);

	let firstRestLocation = undefined;

	while (true) {
		if (parser.match(tt.eof) || parser.match(tt.braceR)) {
			break;
		}

		if (first) {
			first = false;
		} else {
			parser.expect(tt.comma);

			if (parser.match(tt.eof) || parser.match(tt.braceR)) {
				break;
			}
		}

		let isGenerator = false;
		let isAsync = false;
		let start = parser.getPosition();

		if (parser.eat(tt.ellipsis)) {
			const argument = parseBindingIdentifier(parser);
			rest = argument;

			if (firstRestLocation !== undefined) {
				parser.addDiagnostic({
					loc: argument.loc,
					description: descriptions.JS_PARSER.MULTIPLE_DESTRUCTURING_RESTS,
				});
			}

			if (parser.match(tt.braceR) || parser.match(tt.eof)) {
				break;
			}

			if (parser.match(tt.comma) && parser.lookaheadState().tokenType === tt.braceR) {
				parser.addDiagnostic({
					description: descriptions.JS_PARSER.TRAILING_COMMA_AFTER_REST,
				});
				parser.eat(tt.comma);
				break;
			} else {
				firstRestLocation = argument.loc;
				continue;
			}
		}

		start = parser.getPosition();

		const key = parseObjectPropertyKey(parser);
		const prop = parseObjectPropertyValue(
			parser,
			{
				key,
				start,
				isGenerator,
				isAsync,
				isPattern: true,
				refShorthandDefaultPos,
				escapePosition: undefined,
			},
		);

		if (prop === undefined) {
			continue;
		}

		checkPropClash(parser, prop, propHash);

		if (prop.type !== "BindingObjectPatternProperty") {
			parser.addDiagnostic({
				description: descriptions.JS_PARSER.INVALID_OBJECT_PATTERN_PROP,
				loc: prop.loc,
			});
			continue;
		}

		properties.push(prop);
	}

	parser.expectClosing(openContext);

	if (firstRestLocation !== undefined) {
		raiseRestNotLast(parser, firstRestLocation);
	}

	return parser.finishNode(
		start,
		{
			type: "BindingObjectPattern",
			properties,
			rest,
		},
	);
}


export function isGetterOrSetterMethod(
	parser: JSParser,
	key: StaticPropertyKey | ComputedPropertyKey,
	// `key` is always from `name.key`, we just need it here to refine
	keyVal: Identifier | AnyExpression | PrivateName,
	isPattern: boolean,
): keyVal is Identifier {
	return (
		!isPattern &&
		key.type === "StaticPropertyKey" &&
		keyVal.type === "Identifier" &&
		(keyVal.name === "get" || keyVal.name === "set") &&
		(parser.match(tt.string) ||
		// get "string"() {}
		parser.match(tt.num) ||
		// get 1() {}
		parser.match(tt.bracketL) ||
		// get ["string"]() {}
		parser.match(tt.name) ||
		// get foo() {}
		!!parser.state.tokenType.keyword) // get debugger() {}
	);
}


// get methods aren't allowed to have any parameters
// set methods must have exactly 1 parameter
export function checkGetterSetterParamCount(
	parser: JSParser,
	method:
		 | ObjectMethod
		| ClassMethod
		| ClassPrivateMethod
		| TSDeclareFunction
		| TSDeclareMethod,
	kind: string,
): void {
	const head = method.head;

	if (kind === "get") {
		if (head.rest !== undefined || head.params.length !== 0) {
			parser.addDiagnostic({
				loc: method.loc,
				description: descriptions.JS_PARSER.GETTER_WITH_PARAMS,
			});
		}
	} else if (kind === "set") {
		if (head.rest !== undefined) {
			parser.addDiagnostic({
				loc: head.rest.loc,
				description: descriptions.JS_PARSER.SETTER_WITH_REST,
			});
		} else if (head.params.length !== 1) {
			parser.addDiagnostic({
				loc: method.loc,
				description: descriptions.JS_PARSER.SETTER_NOT_ONE_PARAM,
			});
		}
	}
}


export function parseObjectMethod(
	parser: JSParser,
	{
		key,
		start,
		isGenerator,
		isAsync,
		isPattern,
		escapePosition,
	}: ParseObjectMethodOpts,
): undefined | ObjectMethod {
	if (isAsync || isGenerator || parser.match(tt.parenL)) {
		if (isPattern) {
			parser.addDiagnostic({
				description: descriptions.JS_PARSER.OBJECT_METHOD_IN_PATTERN,
			});
		}

		const partial = parseMethod(
			parser,
			{
				kind: "method",
				isClass: false,
				isGenerator,
				isAsync,
				isConstructor: false,
			},
		);

		const {body} = partial;
		if (body === undefined || body.type !== "BlockStatement") {
			throw new Error("Expected body");
		}

		return parser.finishNode(
			start,
			{
				...partial,
				body,
				key,
				type: "ObjectMethod",
				kind: "method",
			},
		);
	}

	if (isGetterOrSetterMethod(parser, key, key.value, isPattern)) {
		if (isAsync) {
			parser.addDiagnostic({
				description: descriptions.JS_PARSER.ASYNC_GETTER_SETTER,
			});
		}

		if (isGenerator) {
			parser.addDiagnostic({
				description: descriptions.JS_PARSER.GENERATOR_GETTER_SETTER,
			});
		}

		const kind = key.value.name;
		if (kind !== "get" && kind !== "set") {
			throw new Error(
				"Name should be get or set as we already validated it as such",
			);
		}
		parser.banUnicodeEscape(escapePosition, kind);

		const newKey = parseObjectPropertyKey(parser);

		const partial = parseMethod(
			parser,
			{
				kind,
				isClass: false,
				isGenerator: false,
				isAsync: false,
				isConstructor: false,
			},
		);

		const {body, head} = partial;
		if (body === undefined || body.type !== "BlockStatement") {
			throw new Error("Expected body");
		}

		const method: ObjectMethod = parser.finishNode(
			start,
			{
				head,
				body,
				key: newKey,
				type: "ObjectMethod",
				kind,
			},
		);
		checkGetterSetterParamCount(parser, method, method.kind);
		return method;
	}

	return undefined;
}


export function parseObjectProperty(
	parser: JSParser,
	key: AnyObjectPropertyKey,
	start: Position,
	isPattern: boolean,
	refShorthandDefaultPos: undefined | IndexTracker,
): undefined | ObjectProperty | BindingObjectPatternProperty {
	if (parser.eat(tt.colon)) {
		if (isPattern) {
			const value = parseMaybeDefault(parser);
			return parser.finishNode(
				start,
				{
					key,
					type: "BindingObjectPatternProperty",
					value,
				},
			);
		} else {
			const value = parseMaybeAssign(
				parser,
				"object property value",
				false,
				refShorthandDefaultPos,
			);
			return parser.finishNode(
				start,
				{
					key,
					type: "ObjectProperty",
					value,
				},
			);
		}
	}

	if (key.type === "StaticPropertyKey" && key.value.type === "Identifier") {
		checkReservedWord(
			parser,
			key.value.name,
			parser.getLoc(key.value),
			true,
			true,
		);

		if (isPattern) {
			let value: AnyBindingPattern = toBindingIdentifier(
				parser,
				parser.cloneNode(key.value),
			);

			if (parser.match(tt.eq) && refShorthandDefaultPos) {
				if (refShorthandDefaultPos.index === ob1Number0) {
					refShorthandDefaultPos.index = parser.state.startPos.index;
				}

				value = parseMaybeDefault(parser, start, value);
			}

			return parser.finishNode(
				start,
				{
					type: "BindingObjectPatternProperty",
					key,
					value,
				},
			);
		}

		return parser.finishNode(
			start,
			{
				type: "ObjectProperty",
				key,
				value: toReferenceIdentifier(parser, parser.cloneNode(key.value)),
			},
		);
	}

	return undefined;
}


export function parseObjectPropertyValue(
	parser: JSParser,
	{
		key,
		start,
		isGenerator,
		isAsync,
		isPattern,
		refShorthandDefaultPos,
		escapePosition,
	}: ParseObjectPropValueOpts,
): undefined | ObjectMethod | ObjectProperty | BindingObjectPatternProperty {
	// parse type parameters for object method shorthand
	let typeParameters = maybeParseTSTypeParameters(parser);
	if (typeParameters !== undefined && !parser.match(tt.parenL)) {
		parser.unexpectedToken();
	}

	let node:
		 | undefined
		| ObjectMethod
		| ObjectProperty
		| BindingObjectPatternProperty =
		parseObjectMethod(
			parser,
			{
				key,
				start,
				isGenerator,
				isAsync,
				isPattern,
				escapePosition,
			},
		) ||
		parseObjectProperty(parser, key, start, isPattern, refShorthandDefaultPos);

	if (node === undefined) {
		parser.unexpectedToken();
		return undefined;
	}

	if (typeParameters === undefined) {
		return node;
	} else {
		if (
			node.type === "ObjectProperty" ||
			node.type === "BindingObjectPatternProperty"
		) {
			parser.addDiagnostic({
				loc: typeParameters.loc,
				description: descriptions.JS_PARSER.OBJECT_PROPERTY_WITH_TYPE_PARAMETERS,
			});
			return node;
		}

		return {
			...node,
			head: {
				...node.head,
				typeParameters,
			},
		};
	}
}


export function parseObjectPropertyKey(
	parser: JSParser,
): StaticPropertyKey | ComputedPropertyKey {
	const start = parser.getPosition();

	if (parser.match(tt.bracketL)) {
		const openContext = parser.expectOpening(
			tt.bracketL,
			tt.bracketR,
			"property name",
		);

		const value = parseMaybeAssign(parser, "property name");
		parser.expectClosing(openContext);
		return parser.finishNode(
			start,
			{
				type: "ComputedPropertyKey",
				value,
			},
		);
	} else {
		parser.pushScope("PROPERTY_NAME", true);

		// We check if it's valid for it to be a private name when we push it.
		let value;
		if (parser.match(tt.num)) {
			value = parseNumericLiteral(parser);
		} else if (parser.match(tt.string)) {
			value = parseStringLiteral(parser);
		} else {
			value = parseMaybePrivateName(parser);
		}

		parser.popScope("PROPERTY_NAME");

		return parser.finishNode(
			start,
			{
				type: "StaticPropertyKey",
				value,
			},
		);
	}
}


// Parse object or class method.
export function parseMethod(
	parser: JSParser,
	opts: {
		kind: ClassMethodKind | ObjectMethodKind;
		isGenerator: boolean;
		isAsync: boolean;
		isConstructor: boolean;
		isClass: boolean;
	},
): {
	head: FunctionHead;
	body: undefined | ParseFunctionBodyReturn["body"];
} {
	const {kind, isClass, isGenerator, isAsync, isConstructor} = opts;

	const oldYieldPos = parser.state.yieldPos;
	const oldAwaitPos = parser.state.awaitPos;
	parser.pushScope("FUNCTION", true);
	parser.pushScope("NON_ARROW_FUNCTION");
	parser.pushScope("METHOD", kind);
	parser.pushScope("GENERATOR", isGenerator);
	parser.state.yieldPos = ob1Number0;
	parser.state.awaitPos = ob1Number0;

	const allowTSModifiers = isConstructor;
	const headStart = parser.getPosition();
	const {typeParameters, rest, params} = parseFunctionParams(
		parser,
		kind,
		allowTSModifiers,
	);
	const start = parser.getPosition();
	const {body, head} = parseFunctionBodyAndFinish(
		parser,
		{
			headStart,
			rest,
			params,
			id: undefined,
			allowBodiless: isClass,
			isArrowFunction: false,
			isAsync,
			isGenerator,
			isMethod: true,
			start,
		},
	);

	parser.popScope("METHOD");
	parser.popScope("GENERATOR");
	parser.popScope("FUNCTION");
	parser.popScope("NON_ARROW_FUNCTION");
	parser.state.yieldPos = oldYieldPos;
	parser.state.awaitPos = oldAwaitPos;

	return {
		head: {
			...head,
			typeParameters,
		},
		body,
	};
}


function createFunctionHead(
	parser: JSParser,
	params: Array<AnyBindingPattern>,
	rest: undefined | AnyTargetBindingPattern,
	opts: Partial<FunctionHead>,
): FunctionHead {
	const nonRestParams: FunctionHead["params"] = [];

	for (const param of params) {
		switch (param.type) {
			case "BindingIdentifier":
			case "BindingAssignmentPattern":
			case "BindingObjectPattern":
			case "BindingArrayPattern": {
				nonRestParams.push(param);
				break;
			}

			default:
				throw new Error("TODO");
		}
	}

	return {
		type: "FunctionHead",
		rest,
		...splitFunctionParams(nonRestParams),
		...opts,
	};
}


export function parseArrowExpression(
	parser: JSParser,
	start: Position,
	opts: {
		bindingList?: Array<AnyBindingPattern>;
		assignmentList?: Array<ArrayHole | ToReferencedItem>;
		rest?: AnyTargetBindingPattern;
	},
	isAsync: boolean = false,
): ArrowFunctionExpression {
	// if we got there, it's no more "yield in possible arrow parameters";
	// it's just "yield in arrow parameters"
	if (parser.state.yieldInPossibleArrowParameters) {
		parser.addDiagnostic({
			start: parser.state.yieldInPossibleArrowParameters,
			description: descriptions.JS_PARSER.YIELD_NAME_IN_GENERATOR,
		});
	}

	parser.pushScope("FUNCTION", true);

	const oldYieldPos = parser.state.yieldPos;
	const oldAwaitPos = parser.state.awaitPos;
	const oldMaybeInArrowParameters = parser.state.maybeInArrowParameters;
	parser.pushScope("GENERATOR", false);
	parser.state.maybeInArrowParameters = false;
	parser.state.yieldPos = ob1Number0;
	parser.state.awaitPos = ob1Number0;

	const headEnd = parser.getLastEndPosition();

	let params: Array<AnyBindingPattern> = [];
	let rest: undefined | AnyTargetBindingPattern = opts.rest;

	if (opts.bindingList !== undefined) {
		params = opts.bindingList;
	}

	if (opts.assignmentList !== undefined) {
		({params, rest} = toFunctionParamsBindingList(
			parser,
			opts.assignmentList,
			"arrow function parameters",
		));
	}

	let head = parser.finishNodeAt(
		start,
		headEnd,
		createFunctionHead(
			parser,
			params,
			rest,
			{
				hasHoistedVars: false,
				async: isAsync,
			},
		),
	);

	const {body, hasHoistedVars} = parseFunctionBody(
		parser,
		{
			id: undefined,
			allowBodiless: false,
			isArrowFunction: true,
			isMethod: false,
			isAsync,
			isGenerator: false,
			start,
		},
	);

	head = {
		...head,
		hasHoistedVars,
	};

	checkFunctionNameAndParams(
		parser,
		{
			isArrowFunction: true,
			isMethod: false,
			id: undefined,
			params,
			rest,
			start,
		},
		body,
	);

	parser.popScope("GENERATOR");
	parser.popScope("FUNCTION");
	parser.state.maybeInArrowParameters = oldMaybeInArrowParameters;
	parser.state.yieldPos = oldYieldPos;
	parser.state.awaitPos = oldAwaitPos;

	return parser.finishNode(
		start,
		{
			type: "ArrowFunctionExpression",
			body,
			head,
		},
	);
}


export function isStrictBody(parser: JSParser, body: AnyNode): boolean {
	if (body.type === "BlockStatement" && body.directives !== undefined) {
		for (const directive of body.directives) {
			if (directive.value === "use strict") {
				return true;
			}
		}
	}

	return false;
}


export function parseFunctionBodyAndFinish(
	parser: JSParser,
	opts: CheckFunctionNameParamsOpts &
		FunctionBodyParseOpts & {
			headStart: Position;
		},
): {
	head: FunctionHead;
	body: undefined | ParseFunctionBodyReturn["body"];
} {
	let returnType = undefined;

	// For arrow functions, `parseArrow` handles the return type itself.
	if (!opts.isArrowFunction && parser.match(tt.colon)) {
		returnType = parseTSTypeOrTypePredicateAnnotation(parser, tt.colon);
	}

	const headEnd = parser.getLastEndPosition();
	const head = parser.finishNodeAt(
		opts.headStart,
		headEnd,
		createFunctionHead(
			parser,
			opts.params,
			opts.rest,
			{
				generator: opts.isGenerator,
				async: opts.isAsync,
				hasHoistedVars: false,
				returnType,
			},
		),
	);

	if (opts.allowBodiless && !parser.match(tt.braceL) && parser.isLineTerminator()) {
		return {
			head,
			body: undefined,
		};
	}

	const {body, hasHoistedVars} = parseFunctionBody(parser, opts);

	checkFunctionNameAndParams(
		parser,
		{
			isArrowFunction: opts.isArrowFunction,
			isMethod: opts.isMethod,
			id: opts.id,
			start: opts.start,
			params: opts.params,
			rest: opts.rest,
		},
		body,
	);

	head.hasHoistedVars = hasHoistedVars;

	return {
		head,
		body,
	};
}


export function parseFunctionBody(
	parser: JSParser,
	opts: FunctionBodyParseOpts,
): ParseFunctionBodyReturn {
	if (opts.isArrowFunction) {
		return forwardNoArrowParamsConversionAt(
			parser,
			opts.start,
			() => _parseFunctionBody(parser, opts),
		);
	} else {
		return _parseFunctionBody(parser, opts);
	}
}


// Parse function body and check parameters.
function _parseFunctionBody(
	parser: JSParser,
	opts: FunctionBodyParseOpts,
): ParseFunctionBodyReturn {
	const {isArrowFunction, isAsync, isGenerator} = opts;

	const isExpression = isArrowFunction && !parser.match(tt.braceL);

	parser.pushScope("PARAMETERS", false);
	parser.pushScope("ASYNC", isAsync);

	let hasHoistedVars = false;
	let body: AnyExpression | BlockStatement;
	if (isExpression) {
		body = parseMaybeAssign(parser, "function body");
	} else {
		// Start a new scope with regard to labels and the `inGenerator`
		// flag (restore them to their old value afterwards).
		const oldLabels = parser.state.labels;
		parser.pushScope("GENERATOR", isGenerator);
		parser.state.labels = [];

		const oldhasHoistedVars = parser.state.hasHoistedVars;
		parser.state.hasHoistedVars = false;

		body = parseBlock(parser, true);
		hasHoistedVars = parser.state.hasHoistedVars;

		parser.popScope("GENERATOR");

		parser.state.hasHoistedVars = oldhasHoistedVars;
		parser.state.labels = oldLabels;
	}

	parser.popScope("ASYNC");
	parser.popScope("PARAMETERS");

	return {body, hasHoistedVars};
}


export function checkFunctionNameAndParams(
	parser: JSParser,
	opts: CheckFunctionNameParamsOpts,
	body: AnyExpression | BlockStatement,
	force?: boolean,
): void {
	const {isArrowFunction, isMethod, id, rest, params, start} = opts;

	if (
		!isSimpleParamList(params, rest) &&
		body.type === "BlockStatement" &&
		body.directives !== undefined
	) {
		const firstDirective = body.directives[0];
		if (firstDirective !== undefined && firstDirective.value === "use strict") {
			parser.addDiagnostic({
				loc: firstDirective.loc,
				description: descriptions.JS_PARSER.STRICT_DIRECTIVE_IN_NON_SIMPLE_PARAMS,
			});
		}
	}

	if (
		isArrowFunction &&
		force !== true &&
		parser.state.noArrowParamsConversionAt.includes(start.index)
	) {
		return undefined;
	}

	// If this is a strict mode function, verify that argument names

	// are not repeated, and it does not try to bind the words `eval`
	const _isStrictBody = isStrictBody(parser, body);
	const isStrict = parser.inScope("STRICT") || _isStrictBody;

	const isSimpleParams = isSimpleParamList(params, rest);
	const shouldCheckLVal: boolean =
		isStrict || isArrowFunction || isMethod || !isSimpleParams;

	parser.pushScope("STRICT", isStrict);

	if (shouldCheckLVal) {
		const clashes: Map<string, AnyNode> = new Map();

		if (id !== undefined) {
			checkLVal(parser, id, true, undefined, "function name");
		}

		for (const param of params) {
			if (_isStrictBody && param.type !== "BindingIdentifier") {
				parser.addDiagnostic({
					loc: param.loc,
					description: descriptions.JS_PARSER.NON_SIMPLE_PARAM_IN_EXPLICIT_STRICT_FUNCTION,
				});
			}
			checkLVal(parser, param, true, clashes, "function parameter list");
		}
	}

	parser.popScope("STRICT");
}


function isSimpleParamList(
	params: Array<AnyBindingPattern>,
	rest: undefined | AnyTargetBindingPattern,
): boolean {
	if (rest !== undefined) {
		return false;
	}

	for (const param of params) {
		if (param.type !== "BindingIdentifier") {
			return false;
		}
	}

	return true;
}


export function parseExpressionList(
	parser: JSParser,
	context: ExpressionContext,
	openContext: OpeningContext,
	allowEmpty?: boolean,
	refShorthandDefaultPos?: IndexTracker,
): Array<ReturnType<typeof parseCallArgument>> {
	const elts = [];
	let first = true;

	while (true) {
		if (parser.match(openContext.close) || parser.match(tt.eof)) {
			break;
		}

		if (first) {
			first = false;
		} else {
			parser.expect(tt.comma);

			if (parser.match(openContext.close)) {
				break;
			}
		}

		elts.push(
			parseCallArgument(parser, context, allowEmpty, refShorthandDefaultPos),
		);
	}

	parser.expectClosing(openContext);

	return elts;
}


export function parseExpressionListNonEmpty(
	parser: JSParser,
	context: ExpressionContext,
	openContext: OpeningContext,
	refShorthandDefaultPos?: IndexTracker,
): Array<AnyExpression> {
	const val = parseExpressionList(
		parser,
		context,
		openContext,
		false,
		refShorthandDefaultPos,
	);
	// @ts-ignore: Passed allowEmpty: false above
	return val;
}


export function parseCallArgument(
	parser: JSParser,
	context: ExpressionContext,
	allowHoles: boolean = false,
	refShorthandDefaultPos?: IndexTracker,
	refNeedsArrowPos?: IndexTracker,
	refTrailingCommaPos?: IndexTracker,
): ArrayHole | AnyExpression | SpreadElement | AmbiguousFlowTypeCastExpression {
	if (allowHoles && parser.match(tt.comma)) {
		return parseArrayHole(parser);
	} else if (parser.match(tt.ellipsis)) {
		const spreadNodeStart = parser.state.startPos;

		const elt = parseParenItem(
			parser,
			parseSpread(parser, refShorthandDefaultPos, refNeedsArrowPos),
			spreadNodeStart,
		);

		if (refTrailingCommaPos && parser.match(tt.comma)) {
			refTrailingCommaPos.index = parser.state.startPos.index;
		}

		return elt;
	} else {
		return parseMaybeAssign<ReturnType<typeof parseParenItem>>(
			parser,
			context,
			false,
			refShorthandDefaultPos,
			parseParenItem,
			refNeedsArrowPos,
		);
	}
}


// Parse the next token as an identifier. If `liberal` is true (used
// when parsing properties), it will also convert keywords into
// identifiers.
export function parseIdentifier(parser: JSParser, liberal?: boolean): Identifier {
	const start = parser.getPosition();
	const name = parseIdentifierName(parser, liberal);
	return createIdentifier(parser, start, name);
}


export function parseBindingIdentifier(
	parser: JSParser,
	liberal?: boolean,
): BindingIdentifier {
	return toBindingIdentifier(parser, parseIdentifier(parser, liberal));
}


export function parseReferenceIdentifier(
	parser: JSParser,
	liberal?: boolean,
): ReferenceIdentifier {
	return toReferenceIdentifier(parser, parseIdentifier(parser, liberal));
}


export function toBindingIdentifier(
	parser: JSParser,
	node: ReferenceIdentifier | Identifier | AssignmentIdentifier,
): BindingIdentifier {
	return parser.finalizeNode({
		...node,
		type: "BindingIdentifier",
	});
}


export function toAssignmentIdentifier(
	parser: JSParser,
	node: ReferenceIdentifier | Identifier | BindingIdentifier,
): AssignmentIdentifier {
	return parser.finalizeNode({
		...node,
		type: "AssignmentIdentifier",
	});
}


export function toReferenceIdentifier(
	parser: JSParser,
	node: BindingIdentifier | Identifier | AssignmentIdentifier,
): ReferenceIdentifier {
	return parser.finalizeNode({
		...node,
		type: "ReferenceIdentifier",
	});
}


export function toIdentifier(
	parser: JSParser,
	node: BindingIdentifier | ReferenceIdentifier | AssignmentIdentifier,
): Identifier {
	return {
		...node,
		type: "Identifier",
	};
}


export function createIdentifier(
	parser: JSParser,
	start: Position,
	name: string,
): Identifier {
	const node: Identifier = parser.finishNode(
		start,
		{
			type: "Identifier",
			name,
		},
	);
	parser.getLoc(node).identifierName = name;
	return node;
}


export function parseIdentifierName(
	parser: JSParser,
	liberal: boolean = false,
): string {
	const loc = parser.finishLocAt(parser.state.startPos, parser.state.endPos);

	if (!liberal) {
		checkReservedWord(
			parser,
			String(parser.state.tokenValue),
			loc,
			!!parser.state.tokenType.keyword,
			false,
		);
	}

	let name: string;

	if (parser.match(tt.name)) {
		name = String(parser.state.tokenValue);
	} else if (parser.state.tokenType.keyword !== undefined) {
		name = parser.state.tokenType.keyword;

		// `class` and `function` keywords push new context into this.context.

		// But there is no chance to pop the context if the keyword is consumed

		// as an identifier such as a property name.

		// If the previous token is a dot, this does not apply because the

		// context-managing code already ignored the keyword
		if (
			(name === "class" || name === "function") &&
			(parser.state.lastEndPos.index !== ob1Inc(parser.state.lastStartPos.index) ||
			parser.input.charCodeAt(ob1Get0(parser.state.lastStartPos.index)) !==
			charCodes.dot)
		) {
			parser.state.context.pop();
		}
	} else {
		parser.addDiagnostic({
			description: descriptions.JS_PARSER.EXPECTED_IDENTIFIER,
		});
		name = "";
	}

	if (!liberal) {
		checkReservedWord(
			parser,
			name,
			loc,
			parser.state.tokenType.keyword !== undefined,
			false,
		);
	}

	parser.next();
	return name;
}


export function checkReservedWord(
	parser: JSParser,
	word: string,
	loc: SourceLocation,
	checkKeywords: boolean,
	isBinding: boolean,
): void {
	if (parser.isSyntaxEnabled("ts")) {
		// TypeScript support in Babel disables reserved word checking...
		// This is mostly because TS allows reserved words in certain scenarios
		// TODO we should just allow those rather than relying on this hack
		return undefined;
	}

	if (parser.inScope("GENERATOR") && word === "yield") {
		parser.addDiagnostic({
			loc,
			description: descriptions.JS_PARSER.YIELD_NAME_IN_GENERATOR,
		});
	}

	if (parser.inScope("ASYNC") && word === "await") {
		parser.addDiagnostic({
			loc,
			description: descriptions.JS_PARSER.AWAIT_NAME_IN_ASYNC,
		});
	}

	if (parser.inScope("CLASS_PROPERTY") && word === "arguments") {
		parser.addDiagnostic({
			loc,
			description: descriptions.JS_PARSER.ARGUMENTS_IN_CLASS_FIELD,
		});
	}

	if (checkKeywords && isKeyword(word)) {
		parser.addDiagnostic({
			loc,
			description: descriptions.JS_PARSER.UNEXPECTED_KEYWORD(word),
		});
	}

	let isReserved = false;
	if (parser.inScope("STRICT")) {
		if (isBinding) {
			isReserved = isStrictBindReservedWord(word, parser.inModule);
		} else {
			isReserved = isStrictReservedWord(word, parser.inModule);
		}
	} else {
		isReserved = isReservedWord(word, parser.inModule);
	}

	if (isReserved) {
		if (!parser.inScope("ASYNC") && word === "await") {
			parser.addDiagnostic({
				loc,
				description: descriptions.JS_PARSER.AWAIT_OUTSIDE_ASYNC,
			});
		} else {
			parser.addDiagnostic({
				loc,
				description: descriptions.JS_PARSER.RESERVED_WORD(word),
			});
		}
	}
}


// Parses await expression inside async function.
export function parseAwait(parser: JSParser): AwaitExpression {
	if (!parser.state.awaitPos) {
		parser.state.awaitPos = parser.state.index;
	}

	if (!parser.inScope("ASYNC")) {
		parser.addDiagnostic({
			description: descriptions.JS_PARSER.AWAIT_OUTSIDE_ASYNC,
		});
	}

	const start = parser.getPosition();
	parser.next();

	if (parser.inScope("PARAMETERS")) {
		parser.addDiagnostic({
			description: descriptions.JS_PARSER.AWAIT_IN_ASYNC_PARAMS,
		});
	}

	if (parser.eat(tt.star)) {
		parser.addDiagnostic({
			start,
			description: descriptions.JS_PARSER.AWAIT_STAR,
		});
	}

	const argument = parseMaybeUnary(parser, "await argument");
	return parser.finishNode(start, {type: "AwaitExpression", argument});
}


// Parses yield expression inside generator.
export function parseYield(parser: JSParser, noIn?: boolean): YieldExpression {
	if (!parser.state.yieldPos) {
		parser.state.yieldPos = parser.state.index;
	}

	const start = parser.getPosition();

	if (parser.inScope("PARAMETERS")) {
		parser.addDiagnostic({
			start,
			description: descriptions.JS_PARSER.YIELD_IN_GENERATOR_PARAMS,
		});
	}

	if (
		parser.state.maybeInArrowParameters &&
		// We only set yieldInPossibleArrowParameters if we haven't already
		// found a possible invalid YieldExpression.
		parser.state.yieldInPossibleArrowParameters === undefined
	) {
		parser.state.yieldInPossibleArrowParameters = start;
	}

	parser.next();

	let delegate: undefined | boolean;
	let argument: undefined | AnyExpression;
	if (
		parser.match(tt.semi) ||
		(!parser.match(tt.star) && !parser.state.tokenType.startsExpr) ||
		parser.canInsertSemicolon()
	) {
		delegate = false;
	} else {
		delegate = parser.eat(tt.star);
		argument = parseMaybeAssign<AnyExpression>(parser, "yield argument", noIn);
	}

	return parser.finishNode(
		start,
		{
			type: "YieldExpression",
			delegate,
			argument,
		},
	);
}


function parseNullLiteral(parser: JSParser): NullLiteral {
	const start = parser.getPosition();
	parser.next();
	return parser.finishNode(start, {type: "NullLiteral"});
}


export function parseStringLiteral(parser: JSParser): StringLiteral {
	const start = parser.getPosition();
	const value = String(parser.state.tokenValue);
	parser.next();
	return parser.finishNode(
		start,
		{
			type: "StringLiteral",
			value,
		},
	);
}


function parseBigIntLiteral(parser: JSParser): BigIntLiteral {
	const start = parser.getPosition();
	const value = String(parser.state.tokenValue);
	parser.next();
	return parser.finishNode(
		start,
		{
			type: "BigIntLiteral",
			value,
		},
	);
}


export function parseNumericLiteral(parser: JSParser): NumericLiteral {
	const start = parser.getPosition();
	const {tokenValue} = parser.state;
	if (!(tokenValue instanceof NumberTokenValue)) {
		throw new Error("Expected NumberTokenValue");
	}

	const {value, format} = tokenValue;
	parser.next();
	return parser.finishNode(
		start,
		{
			type: "NumericLiteral",
			format,
			value,
		},
	);
}


function parseRegExpLiteral(parser: JSParser): RegExpLiteral {
	const start = parser.getPosition();
	const value = parser.state.tokenValue;
	if (!(value instanceof RegExpTokenValue)) {
		throw new Error("Expected regex token value");
	}
	parser.next();

	const {flags, pattern} = value;

	const regexParser = createRegExpParser({
		offsetPosition: {
			// Advance passed first slash
			...start,
			column: ob1Inc(start.column),
			index: ob1Inc(start.index),
		},
		path: parser.filename,
		input: pattern,
		unicode: flags.has("u"),
	});

	const {diagnostics, expression} = regexParser.parse();

	for (const diagnostic of diagnostics) {
		parser.addDiagnostic(diagnostic);
	}

	return parser.finishNode(
		start,
		{
			type: "RegExpLiteral",
			expression,
			global: flags.has("g"),
			multiline: flags.has("m"),
			sticky: flags.has("y"),
			insensitive: flags.has("i"),
			noDotNewline: flags.has("s"),
			unicode: flags.has("u"),
		},
	);
}


function parseImportOrMetaProperty(parser: JSParser): ImportCall | MetaProperty {
	if (parser.lookaheadState().tokenType === tt.dot) {
		return parseImportMetaProperty(parser);
	} else {
		return parseImportCall(parser);
	}
}


function parseImportCall(parser: JSParser): ImportCall {
	parser.expect(tt._import);

	const start = parser.getPosition();
	const openContext = parser.expectOpening(tt.parenL, tt.parenR, "array");

	let argument: ReturnType<typeof parseCallArgument>;

	if (parser.match(tt.parenR)) {
		parser.addDiagnostic({
			description: descriptions.JS_PARSER.IMPORT_EXACT_ARGUMENTS,
		});

		argument = toReferenceIdentifier(
			parser,
			parser.createUnknownIdentifier("import call argument"),
		);
	} else {
		const callArg = parseCallArgument(parser, "call expression argument", false);
		if (callArg.type === "ArrayHole") {
			throw new Error(
				"Expected argument, parseExpressionListItem was passed maybeAllowEmpty: false",
			);
		} else {
			argument = callArg;
		}
	}

	// TODO warn on multiple arguments
	if (parser.eat(tt.comma)) {
		parser.addDiagnostic({
			start: parser.state.lastStartPos,
			end: parser.state.lastEndPos,
			description: descriptions.JS_PARSER.IMPORT_TRAILING_COMMA,
		});
	}

	if (argument.type === "SpreadElement") {
		parser.addDiagnostic({
			loc: argument.loc,
			description: descriptions.JS_PARSER.IMPORT_SPREAD,
		});
	}

	parser.expectClosing(openContext);

	const spreadOrExpression: AnyExpression | SpreadElement =
		argument.type === "AmbiguousFlowTypeCastExpression"
			? argument.expression
			: argument;

	const expression: AnyExpression =
		spreadOrExpression.type === "SpreadElement"
			? spreadOrExpression.argument
			: spreadOrExpression;

	return parser.finishNode(start, {type: "ImportCall", argument: expression});
}


function parseSuper(parser: JSParser): Super {
	if (
		!parser.inScope("METHOD") &&
		!parser.inScope("CLASS_PROPERTY") &&
		parser.sourceType !== "template"
	) {
		parser.addDiagnostic({
			description: descriptions.JS_PARSER.SUPER_OUTSIDE_METHOD,
		});
	}

	const start = parser.getPosition();
	parser.next();

	if (
		!parser.match(tt.parenL) &&
		!parser.match(tt.bracketL) &&
		!parser.match(tt.dot)
	) {
		parser.addDiagnostic({
			description: descriptions.JS_PARSER.INVALID_SUPER_SUFFIX,
		});
	}

	const loc = parser.finishLoc(start);

	if (
		parser.match(tt.parenL) &&
		(parser.getLastScope("METHOD") !== "constructor" ||
		parser.getLastScope("CLASS") !== "derived") &&
		parser.sourceType !== "template"
	) {
		parser.addDiagnostic({
			loc,
			description: descriptions.JS_PARSER.SUPER_CALL_OUTSIDE_CONSTRUCTOR,
		});
	}

	return parser.finalizeNode({
		type: "Super",
		loc,
	});
}


function parseDoExpression(parser: JSParser): DoExpression {
	const start = parser.getPosition();
	parser.next();
	const oldLabels = parser.state.labels;
	parser.state.labels = [];
	parser.pushScope("FUNCTION", false);
	const body = parseBlock(parser, false);
	parser.popScope("FUNCTION");
	parser.state.labels = oldLabels;
	return parser.finishNode(
		start,
		{
			type: "DoExpression",
			body,
		},
	);
}


export function parseArrayHole(parser: JSParser): ArrayHole {
	return parser.finishNode(
		parser.getPosition(),
		{
			type: "ArrayHole",
		},
	);
}


function parseArrayExpression(
	parser: JSParser,
	refShorthandDefaultPos?: IndexTracker,
): ArrayExpression {
	const start = parser.getPosition();
	const openContext = parser.expectOpening(tt.bracketL, tt.bracketR, "array");

	const elements = toReferencedListOptional(
		parser,
		parseExpressionList(
			parser,
			"array element",
			openContext,
			true,
			refShorthandDefaultPos,
		),
	);

	return parser.finishNode(
		start,
		{
			type: "ArrayExpression",
			elements,
		},
	);
}


() => {
				return _parseMaybeAssign(
					parser,
					context,
					noIn,
					refShorthandDefaultPos,
					afterLeftParse,
					refNeedsArrowPos,
				);
			}


() => {
			const start = parser.getPosition();
			const typeParameters = parseTSTypeParameters(parser);
			const arrowExpression = forwardNoArrowParamsConversionAt(
				parser,
				start,
				() =>
					_parseMaybeAssign<T>(
						parser,
						context,
						noIn,
						refShorthandDefaultPos,
						afterLeftParse,
						refNeedsArrowPos,
					)
				,
			);
			parser.resetStartLocationFromNode(arrowExpression, typeParameters);

			if (arrowExpression.type === "ArrowFunctionExpression") {
				return {
					...arrowExpression,
					typeParameters,
				};
			} else {
				parser.addDiagnostic({
					loc: typeParameters.loc,
					description: descriptions.JS_PARSER.EXPECTED_ARROW_AFTER_TYPE_PARAMS,
				});
				return toReferenceIdentifier(
					parser,
					parser.createUnknownIdentifier("type params without arrow function"),
				);
			}
		}


() =>
					_parseMaybeAssign<T>(
						parser,
						context,
						noIn,
						refShorthandDefaultPos,
						afterLeftParse,
						refNeedsArrowPos,
					)


() => {
		return _parseMaybeAssign<T>(
			parser,
			context,
			noIn,
			refShorthandDefaultPos,
			afterLeftParse,
			refNeedsArrowPos,
		);
	}


() => {
		parser.state.noArrowParamsConversionAt.push(parser.state.startPos.index);
		const consequent = parseMaybeAssign(parser, "conditional consequent");
		parser.state.noArrowParamsConversionAt.pop();
		return {
			consequent,
			failed: !parser.match(tt.colon),
		};
	}


() => _parseConditional(parser, expr, noIn, startPos)


() =>
			parseMaybeAssign(
				parser,
				"conditional alternate",
				noIn,
				undefined,
				undefined,
				undefined,
			)


() => parseAsyncArrowWithTypeParameters(parser, startPos)


() =>
			parseExpressionSubscriptsRecursively(
				parser,
				base,
				startPos,
				noCalls,
				maybeAsyncArrow,
			)


() => {
			const typeArguments = parseTSTypeArguments(parser);

			if (!noCalls && parser.match(tt.parenL)) {
				const argsStart = parser.getPosition();
				const openContext = parser.expectOpening(
					tt.parenL,
					tt.parenR,
					"call arguments",
				);
				const {args} = parseCallExpressionArguments(parser, openContext, false);
				const node: CallExpression = parser.finishNodeWithCommentStarts(
					[argsStart, startPos],
					{
						type: "CallExpression",
						arguments: args,
						callee: base,
						typeArguments,
					},
				);
				return node;
			}

			if (parser.match(tt.backQuote)) {
				return parseTaggedTemplateExpression(
					parser,
					startPos,
					base,
					state,
					typeArguments,
				);
			}

			return undefined;
		}


() => {
			const res = parseTSTypeOrTypePredicateAnnotation(parser, tt.colon);

			if (parser.canInsertSemicolon()) {
				// No semicolon insertion expected
				return undefined;
			}

			if (parser.eat(tt.arrow)) {
				return res;
			}

			return undefined;
		}


() => _parseFunctionBody(parser, opts)


type MaybeAssignAfterParse<T> = (
	parser: JSParser,
	left: AnyExpression,
	startPos: Position,
) => T;


type ParseSubscriptState = {
	stop: boolean;
	optionalChainMember: boolean;
};


type ExpressionContext =
	 | "await argument"
	| "export default declaration"
	| "export from"
	| "import source"
	| "return argument"
	| "switch discriminant"
	| "case test"
	| "throw argument"
	| "flow object property key"
	| "flow declare module id"
	| "flow declared predicate"
	| "class private property"
	| "class property value"
	| "assignment right"
	| "class heritage"
	| "new callee"
	| "var init"
	| "for right"
	| "for update"
	| "for test"
	| "for init"
	| "with object"
	| "while test"
	| "do test"
	| "if test"
	| "conditional consequent"
	| "conditional alternate"
	| "class private property value"
	| "statement expression"
	| "class private property value"
	| "optional member expression property"
	| "member expression computed property"
	| "call expression argument"
	| "new expression argument"
	| "template expression value"
	| "object property value"
	| "property name"
	| "function body"
	| "yield argument"
	| "array element"
	| "spread argument"
	| "assignment pattern right"
	| "ts export assignment"
	| "ts external module reference expression"
	| "ts enum member initializer"
	| "ts enum member id"
	| "ts type assertion"
	| "ts literal type"
	| "ts import argument"
	| "jsx inner expression container"
	| "jsx attribute value"
	| "jsx spread child expression"
	| "jsx attribute spread"
	| "jsx text";


type ParseObjectMethodOpts = {
	key: AnyObjectPropertyKey;
	start: Position;
	isGenerator: boolean;
	isAsync: boolean;
	isPattern: boolean;
	escapePosition: undefined | Number0;
};


type ParseObjectPropValueOpts = {
	key: ComputedPropertyKey | StaticPropertyKey;
	start: Position;
	isGenerator: boolean;
	isAsync: boolean;
	isPattern: boolean;
	refShorthandDefaultPos: undefined | IndexTracker;
	escapePosition: undefined | Number0;
};


type FunctionBodyParseOpts = {
	allowBodiless: boolean;
	isArrowFunction: boolean;
	isAsync: boolean;
	isGenerator: boolean;
	isMethod: boolean;
	start: Position;
	id: BindingIdentifier | undefined;
};


type ParseFunctionBodyReturn = {
	body: BlockStatement | AnyExpression;
	hasHoistedVars: boolean;
};


type CheckFunctionNameParamsOpts = {
	isArrowFunction: boolean;
	isMethod: boolean;
	id: undefined | BindingIdentifier;
	params: Array<AnyBindingPattern>;
	rest: undefined | AnyTargetBindingPattern;
	start: Position;
};


@Component({
  selector: "hb-footer",
  templateUrl: "./footer.component.html",
  styleUrls: ["./footer.component.scss"]
})
export class FooterComponent implements OnInit {
  constructor() {}

  ngOnInit(): void {}

  getYear() {
    const date = new Date();
    return date.getFullYear();
  }
}


getYear() {
    const date = new Date();
    return date.getFullYear();
  }


/**
 * Copyright 2021 Solomon Shalom Lijo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
export default function TicketMono() {
  return (
    <svg
      width="100%"
      height="100%"
      viewBox="0 0 650 330"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M20 0C8.95431 0 0 8.95431 0 20V138C14.9117 138 27 150.088 27 165C27 179.912 14.9117 192 0 192V310C0 321.046 8.9543 330 20 330H630C641.046 330 650 321.046 650 310V192C635.088 192 623 179.912 623 165C623 150.088 635.088 138 650 138V20C650 8.95431 641.046 0 630 0H20Z"
        fill="#252729"
      />
      <path
        fillRule="evenodd"
        clipRule="evenodd"
        d="M21 5C12.1634 5 5 12.1634 5 21V133.388C20.2981 135.789 32 149.028 32 165C32 180.972 20.2981 194.211 5 196.612V309C5 317.837 12.1634 325 21 325H629C637.837 325 645 317.837 645 309V196.612C629.702 194.211 618 180.972 618 165C618 149.028 629.702 135.789 645 133.388V21C645 12.1634 637.837 5 629 5H21Z"
        fill="black"
      />
      <path d="M512 5V326" stroke="#444444" strokeDasharray="6 6" />
    </svg>
  );
}


export interface IExtensionDialogProps {
    props?: any,
    onSubmit: (props: any | null) => any,
    open: boolean
}


export function register(config?: Config) {
  if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
    // The URL constructor is available in all browsers that support SW.
    const publicUrl = new URL(
      (process as { readonly env: { readonly [key: string]: string } }).env.PUBLIC_URL,
      window.location.href,
    )
    if (publicUrl.origin !== window.location.origin) {
      // Our service worker won't work if PUBLIC_URL is on a different origin
      // from what our page is served on. This might happen if a CDN is used to
      // serve assets; see https://github.com/facebook/create-react-app/issues/2374
      return
    }

    window.addEventListener("load", () => {
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`

      if (isLocalhost) {
        // This is running on localhost. Let's check if a service worker still exists or not.
        checkValidServiceWorker(swUrl, config)

        // Add some additional logging to localhost, pointing developers to the
        // service worker/PWA documentation.
        navigator.serviceWorker.ready.then(() => {
          // tslint:disable-next-line
          console.log(
            "This web app is being served cache-first by a service " +
              "worker. To learn more, visit https://bit.ly/CRA-PWA",
          )
        })
      } else {
        // Is not localhost. Just register service worker
        registerValidSW(swUrl, config)
      }
    })
  }
}


function registerValidSW(swUrl: string, config?: Config) {
  navigator.serviceWorker
    .register(swUrl)
    .then(registration => {
      registration.onupdatefound = () => {
        const installingWorker = registration.installing
        if (installingWorker == null) {
          return
        }
        installingWorker.onstatechange = () => {
          if (installingWorker.state === "installed") {
            if (navigator.serviceWorker.controller) {
              // At this point, the updated precached content has been fetched,
              // but the previous service worker will still serve the older
              // content until all client tabs are closed.
              // tslint:disable-next-line
              console.log(
                "New content is available and will be used when all " +
                  "tabs for this page are closed. See https://bit.ly/CRA-PWA.",
              )

              // Execute callback
              if (config && config.onUpdate) {
                config.onUpdate(registration)
              }
            } else {
              // At this point, everything has been precached.
              // It's the perfect time to display a
              // "Content is cached for offline use." message.
              // tslint:disable-next-line
              console.log("Content is cached for offline use.")

              // Execute callback
              if (config && config.onSuccess) {
                config.onSuccess(registration)
              }
            }
          }
        }
      }
    })
    .catch(error => {
      // tslint:disable-next-line
      console.error("Error during service worker registration:", error)
    })
}


function checkValidServiceWorker(swUrl: string, config?: Config) {
  // Check if the service worker can be found. If it can't reload the page.
  fetch(swUrl)
    .then(response => {
      // Ensure service worker exists, and that we really are getting a JS file.
      const contentType = response.headers.get("content-type")
      if (
        response.status === 404 ||
        (contentType != null && contentType.indexOf("javascript") === -1)
      ) {
        // No service worker found. Probably a different app. Reload the page.
        navigator.serviceWorker.ready.then(registration => {
          registration.unregister().then(() => {
            window.location.reload()
          })
        })
      } else {
        // Service worker found. Proceed as normal.
        registerValidSW(swUrl, config)
      }
    })
    .catch(() => {
      // tslint:disable-next-line
      console.log(
        "No internet connection found. App is running in offline mode.",
      )
    })
}


export function unregister() {
  if ("serviceWorker" in navigator) {
    navigator.serviceWorker.ready.then(registration => {
      registration.unregister()
    })
  }
}


() => {
      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`

      if (isLocalhost) {
        // This is running on localhost. Let's check if a service worker still exists or not.
        checkValidServiceWorker(swUrl, config)

        // Add some additional logging to localhost, pointing developers to the
        // service worker/PWA documentation.
        navigator.serviceWorker.ready.then(() => {
          // tslint:disable-next-line
          console.log(
            "This web app is being served cache-first by a service " +
              "worker. To learn more, visit https://bit.ly/CRA-PWA",
          )
        })
      } else {
        // Is not localhost. Just register service worker
        registerValidSW(swUrl, config)
      }
    }


() => {
          // tslint:disable-next-line
          console.log(
            "This web app is being served cache-first by a service " +
              "worker. To learn more, visit https://bit.ly/CRA-PWA",
          )
        }


registration => {
      registration.onupdatefound = () => {
        const installingWorker = registration.installing
        if (installingWorker == null) {
          return
        }
        installingWorker.onstatechange = () => {
          if (installingWorker.state === "installed") {
            if (navigator.serviceWorker.controller) {
              // At this point, the updated precached content has been fetched,
              // but the previous service worker will still serve the older
              // content until all client tabs are closed.
              // tslint:disable-next-line
              console.log(
                "New content is available and will be used when all " +
                  "tabs for this page are closed. See https://bit.ly/CRA-PWA.",
              )

              // Execute callback
              if (config && config.onUpdate) {
                config.onUpdate(registration)
              }
            } else {
              // At this point, everything has been precached.
              // It's the perfect time to display a
              // "Content is cached for offline use." message.
              // tslint:disable-next-line
              console.log("Content is cached for offline use.")

              // Execute callback
              if (config && config.onSuccess) {
                config.onSuccess(registration)
              }
            }
          }
        }
      }
    }


() => {
        const installingWorker = registration.installing
        if (installingWorker == null) {
          return
        }
        installingWorker.onstatechange = () => {
          if (installingWorker.state === "installed") {
            if (navigator.serviceWorker.controller) {
              // At this point, the updated precached content has been fetched,
              // but the previous service worker will still serve the older
              // content until all client tabs are closed.
              // tslint:disable-next-line
              console.log(
                "New content is available and will be used when all " +
                  "tabs for this page are closed. See https://bit.ly/CRA-PWA.",
              )

              // Execute callback
              if (config && config.onUpdate) {
                config.onUpdate(registration)
              }
            } else {
              // At this point, everything has been precached.
              // It's the perfect time to display a
              // "Content is cached for offline use." message.
              // tslint:disable-next-line
              console.log("Content is cached for offline use.")

              // Execute callback
              if (config && config.onSuccess) {
                config.onSuccess(registration)
              }
            }
          }
        }
      }


() => {
          if (installingWorker.state === "installed") {
            if (navigator.serviceWorker.controller) {
              // At this point, the updated precached content has been fetched,
              // but the previous service worker will still serve the older
              // content until all client tabs are closed.
              // tslint:disable-next-line
              console.log(
                "New content is available and will be used when all " +
                  "tabs for this page are closed. See https://bit.ly/CRA-PWA.",
              )

              // Execute callback
              if (config && config.onUpdate) {
                config.onUpdate(registration)
              }
            } else {
              // At this point, everything has been precached.
              // It's the perfect time to display a
              // "Content is cached for offline use." message.
              // tslint:disable-next-line
              console.log("Content is cached for offline use.")

              // Execute callback
              if (config && config.onSuccess) {
                config.onSuccess(registration)
              }
            }
          }
        }


error => {
      // tslint:disable-next-line
      console.error("Error during service worker registration:", error)
    }


response => {
      // Ensure service worker exists, and that we really are getting a JS file.
      const contentType = response.headers.get("content-type")
      if (
        response.status === 404 ||
        (contentType != null && contentType.indexOf("javascript") === -1)
      ) {
        // No service worker found. Probably a different app. Reload the page.
        navigator.serviceWorker.ready.then(registration => {
          registration.unregister().then(() => {
            window.location.reload()
          })
        })
      } else {
        // Service worker found. Proceed as normal.
        registerValidSW(swUrl, config)
      }
    }


registration => {
          registration.unregister().then(() => {
            window.location.reload()
          })
        }


() => {
            window.location.reload()
          }


() => {
      // tslint:disable-next-line
      console.log(
        "No internet connection found. App is running in offline mode.",
      )
    }


registration => {
      registration.unregister()
    }


interface Config {
  readonly onSuccess?: (registration: ServiceWorkerRegistration) => void
  readonly onUpdate?: (registration: ServiceWorkerRegistration) => void
}


() => RequiredValidator


() => CheckboxRequiredValidator


() => EmailValidator


() => MinLengthValidator


() => MaxLengthValidator


() => PatternValidator


/**
 * A Directive that adds the `required` validator to any controls marked with the
 * `required` attribute, via the `NG_VALIDATORS` binding.
 *
 * ### Example
 *
 * ```
 * <input name="fullName" ngModel required>
 * ```
 *
 *
 */
@Directive({
  selector:
      ':not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]',
  providers: [REQUIRED_VALIDATOR],
  host: {'[attr.required]': 'required ? "" : null'}
})
export class RequiredValidator implements Validator {
  private _required: boolean;
  private _onChange: () => void;

  @Input()
  get required(): boolean|string { return this._required; }

  set required(value: boolean|string) {
    this._required = value != null && value !== false && `${value}` !== 'false';
    if (this._onChange) this._onChange();
  }

  validate(c: AbstractControl): ValidationErrors|null {
    return this.required ? Validators.required(c) : null;
  }

  registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
}


/**
 * A Directive that adds the `required` validator to checkbox controls marked with the
 * `required` attribute, via the `NG_VALIDATORS` binding.
 *
 * ### Example
 *
 * ```
 * <input type="checkbox" name="active" ngModel required>
 * ```
 *
 * @experimental
 */
@Directive({
  selector:
      'input[type=checkbox][required][formControlName],input[type=checkbox][required][formControl],input[type=checkbox][required][ngModel]',
  providers: [CHECKBOX_REQUIRED_VALIDATOR],
  host: {'[attr.required]': 'required ? "" : null'}
})
export class CheckboxRequiredValidator extends RequiredValidator {
  validate(c: AbstractControl): ValidationErrors|null {
    return this.required ? Validators.requiredTrue(c) : null;
  }
}


/**
 * A Directive that adds the `email` validator to controls marked with the
 * `email` attribute, via the `NG_VALIDATORS` binding.
 *
 * ### Example
 *
 * ```
 * <input type="email" name="email" ngModel email>
 * <input type="email" name="email" ngModel email="true">
 * <input type="email" name="email" ngModel [email]="true">
 * ```
 *
 * @experimental
 */
@Directive({
  selector: '[email][formControlName],[email][formControl],[email][ngModel]',
  providers: [EMAIL_VALIDATOR]
})
export class EmailValidator implements Validator {
  private _enabled: boolean;
  private _onChange: () => void;

  @Input()
  set email(value: boolean|string) {
    this._enabled = value === '' || value === true || value === 'true';
    if (this._onChange) this._onChange();
  }

  validate(c: AbstractControl): ValidationErrors|null {
    return this._enabled ? Validators.email(c) : null;
  }

  registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }
}


/**
 * A directive which installs the `MinLengthValidator` for any `formControlName`,
 * `formControl`, or control with `ngModel` that also has a `minlength` attribute.
 *
 *
 */
@Directive({
  selector: '[minlength][formControlName],[minlength][formControl],[minlength][ngModel]',
  providers: [MIN_LENGTH_VALIDATOR],
  host: {'[attr.minlength]': 'minlength ? minlength : null'}
})
export class MinLengthValidator implements Validator,
    OnChanges {
  private _validator: ValidatorFn;
  private _onChange: () => void;

  @Input() minlength: string;

  ngOnChanges(changes: SimpleChanges): void {
    if ('minlength' in changes) {
      this._createValidator();
      if (this._onChange) this._onChange();
    }
  }

  validate(c: AbstractControl): ValidationErrors|null {
    return this.minlength == null ? null : this._validator(c);
  }

  registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }

  private _createValidator(): void {
    this._validator = Validators.minLength(parseInt(this.minlength, 10));
  }
}


/**
 * A directive which installs the `MaxLengthValidator` for any `formControlName,
 * `formControl`,
 * or control with `ngModel` that also has a `maxlength` attribute.
 *
 *
 */
@Directive({
  selector: '[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]',
  providers: [MAX_LENGTH_VALIDATOR],
  host: {'[attr.maxlength]': 'maxlength ? maxlength : null'}
})
export class MaxLengthValidator implements Validator,
    OnChanges {
  private _validator: ValidatorFn;
  private _onChange: () => void;

  @Input() maxlength: string;

  ngOnChanges(changes: SimpleChanges): void {
    if ('maxlength' in changes) {
      this._createValidator();
      if (this._onChange) this._onChange();
    }
  }

  validate(c: AbstractControl): ValidationErrors|null {
    return this.maxlength != null ? this._validator(c) : null;
  }

  registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }

  private _createValidator(): void {
    this._validator = Validators.maxLength(parseInt(this.maxlength, 10));
  }
}


/**
 * A Directive that adds the `pattern` validator to any controls marked with the
 * `pattern` attribute, via the `NG_VALIDATORS` binding. Uses attribute value
 * as the regex to validate Control value against.  Follows pattern attribute
 * semantics; i.e. regex must match entire Control value.
 *
 * ### Example
 *
 * ```
 * <input [name]="fullName" pattern="[a-zA-Z ]*" ngModel>
 * ```
 *
 */
@Directive({
  selector: '[pattern][formControlName],[pattern][formControl],[pattern][ngModel]',
  providers: [PATTERN_VALIDATOR],
  host: {'[attr.pattern]': 'pattern ? pattern : null'}
})
export class PatternValidator implements Validator,
    OnChanges {
  private _validator: ValidatorFn;
  private _onChange: () => void;

  @Input() pattern: string|RegExp;

  ngOnChanges(changes: SimpleChanges): void {
    if ('pattern' in changes) {
      this._createValidator();
      if (this._onChange) this._onChange();
    }
  }

  validate(c: AbstractControl): ValidationErrors|null { return this._validator(c); }

  registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }

  private _createValidator(): void { this._validator = Validators.pattern(this.pattern); }
}


/**
 * An interface that can be implemented by classes that can act as validators.
 *
 * ## Usage
 *
 * ```typescript
 * @Directive({
 *   selector: '[custom-validator]',
 *   providers: [{provide: NG_VALIDATORS, useExisting: CustomValidatorDirective, multi: true}]
 * })
 * class CustomValidatorDirective implements Validator {
 *   validate(c: Control): {[key: string]: any} {
 *     return {"custom": true};
 *   }
 * }
 * ```
 *
 *
 */
export interface Validator {
  validate(c: AbstractControl): ValidationErrors|null;
  registerOnValidatorChange?(fn: () => void): void;
}


/** @experimental */
export interface AsyncValidator extends Validator {
  validate(c: AbstractControl): Promise<ValidationErrors|null>|Observable<ValidationErrors|null>;
}


export interface ValidatorFn { (c: AbstractControl): ValidationErrors|null; }


export interface AsyncValidatorFn {
  (c: AbstractControl): Promise<ValidationErrors|null>|Observable<ValidationErrors|null>;
}


/** @experimental */
export type ValidationErrors = {
  [key: string]: any
};


validate(c: AbstractControl): ValidationErrors|null {
    return this.required ? Validators.required(c) : null;
  }


registerOnValidatorChange(fn: () => void): void { this._onChange = fn; }


validate(c: AbstractControl): ValidationErrors|null {
    return this.required ? Validators.requiredTrue(c) : null;
  }


validate(c: AbstractControl): ValidationErrors|null {
    return this._enabled ? Validators.email(c) : null;
  }


ngOnChanges(changes: SimpleChanges): void {
    if ('minlength' in changes) {
      this._createValidator();
      if (this._onChange) this._onChange();
    }
  }


validate(c: AbstractControl): ValidationErrors|null {
    return this.minlength == null ? null : this._validator(c);
  }


private _createValidator(): void {
    this._validator = Validators.minLength(parseInt(this.minlength, 10));
  }


ngOnChanges(changes: SimpleChanges): void {
    if ('maxlength' in changes) {
      this._createValidator();
      if (this._onChange) this._onChange();
    }
  }


validate(c: AbstractControl): ValidationErrors|null {
    return this.maxlength != null ? this._validator(c) : null;
  }


private _createValidator(): void {
    this._validator = Validators.maxLength(parseInt(this.maxlength, 10));
  }


ngOnChanges(changes: SimpleChanges): void {
    if ('pattern' in changes) {
      this._createValidator();
      if (this._onChange) this._onChange();
    }
  }


validate(c: AbstractControl): ValidationErrors|null { return this._validator(c); }


private _createValidator(): void { this._validator = Validators.pattern(this.pattern); }


() => {
  test('Check the INDEX clause of the query builder', async () => {
    const expr_where: LogicalWhereExpr = { 'travel-sample.callsign': { $like: '%57-59%' } };

    const on: IIndexOnParams[] = [{ name: 'travel-sample.callsing' }];

    const withExpr: IIndexWithParams = {
      nodes: ['192.168.1.1:8078', '192.168.1.1:8079'],
      defer_build: true,
      num_replica: 2,
    };

    const query = new Query({}, 'travel-sample')
      .index('CREATE', 'travel_sample_id_test')
      .on(on)
      .where(expr_where)
      .usingGSI()
      .with(withExpr)
      .build();

    expect(query).toStrictEqual(
      'CREATE INDEX `travel_sample_id_test` ON `travel-sample`(`travel-sample`.callsing) WHERE `travel-sample`.callsign LIKE "%57-59%" USING GSI WITH {"nodes": ["192.168.1.1:8078","192.168.1.1:8079"],"defer_build": true,"num_replica": 2}',
    );
  });

  test('Check Multiple Query Exceptions with select', () => {
    const run = () => new Query({}, 'travel-sample').index('CREATE', 'travel_index').select('*');
    expect(run).toThrow(MultipleQueryTypesException);
  });

  test('Check the DROP INDEX clause of the query builder', async () => {
    const query = new Query({}, 'travel-sample').index('DROP', 'travel_sample_id_test').usingGSI().build();

    expect(query).toStrictEqual('DROP INDEX `travel-sample`.`travel_sample_id_test` USING GSI');
  });

  test('Check the exception Index Params On Exception', async () => {
    const run = () => new Query({}, 'travel-sample').select().on([{ name: 'test', sort: 'DESC' }]);
    expect(run).toThrow(IndexParamsOnExceptions);
  });

  test('Check the exception Invalid index name', async () => {
    const run = () => new Query({}, 'travel-sample').index('CREATE', 'index-*&');
    expect(run).toThrow(
      'Valid GSI index names can contain any of the following characters: A-Z a-z 0-9 # _, and must start with a letter, [A-Z a-z]',
    );
  });
  test('Check Multiple Query Exceptions with index', () => {
    const run = () => new Query({}, 'travel-sample').select('*').index('CREATE', 'travel_index');
    expect(run).toThrow(MultipleQueryTypesException);
  });

  test('Check the exception IndexParamsUsingGSIExceptions', () => {
    const run = () => new Query({}, 'travel-sample').select('*').usingGSI();
    expect(run).toThrow(IndexParamsUsingGSIExceptions);
  });
}


async () => {
    const expr_where: LogicalWhereExpr = { 'travel-sample.callsign': { $like: '%57-59%' } };

    const on: IIndexOnParams[] = [{ name: 'travel-sample.callsing' }];

    const withExpr: IIndexWithParams = {
      nodes: ['192.168.1.1:8078', '192.168.1.1:8079'],
      defer_build: true,
      num_replica: 2,
    };

    const query = new Query({}, 'travel-sample')
      .index('CREATE', 'travel_sample_id_test')
      .on(on)
      .where(expr_where)
      .usingGSI()
      .with(withExpr)
      .build();

    expect(query).toStrictEqual(
      'CREATE INDEX `travel_sample_id_test` ON `travel-sample`(`travel-sample`.callsing) WHERE `travel-sample`.callsign LIKE "%57-59%" USING GSI WITH {"nodes": ["192.168.1.1:8078","192.168.1.1:8079"],"defer_build": true,"num_replica": 2}',
    );
  }


() => {
    const run = () => new Query({}, 'travel-sample').index('CREATE', 'travel_index').select('*');
    expect(run).toThrow(MultipleQueryTypesException);
  }


() => new Query({}, 'travel-sample').index('CREATE', 'travel_index').select('*')


async () => {
    const query = new Query({}, 'travel-sample').index('DROP', 'travel_sample_id_test').usingGSI().build();

    expect(query).toStrictEqual('DROP INDEX `travel-sample`.`travel_sample_id_test` USING GSI');
  }


async () => {
    const run = () => new Query({}, 'travel-sample').select().on([{ name: 'test', sort: 'DESC' }]);
    expect(run).toThrow(IndexParamsOnExceptions);
  }


() => new Query({}, 'travel-sample').select().on([{ name: 'test', sort: 'DESC' }])


async () => {
    const run = () => new Query({}, 'travel-sample').index('CREATE', 'index-*&');
    expect(run).toThrow(
      'Valid GSI index names can contain any of the following characters: A-Z a-z 0-9 # _, and must start with a letter, [A-Z a-z]',
    );
  }


() => new Query({}, 'travel-sample').index('CREATE', 'index-*&')


() => {
    const run = () => new Query({}, 'travel-sample').select('*').index('CREATE', 'travel_index');
    expect(run).toThrow(MultipleQueryTypesException);
  }


() => new Query({}, 'travel-sample').select('*').index('CREATE', 'travel_index')


() => {
    const run = () => new Query({}, 'travel-sample').select('*').usingGSI();
    expect(run).toThrow(IndexParamsUsingGSIExceptions);
  }


() => new Query({}, 'travel-sample').select('*').usingGSI()


/**
 * Create a tagged union from a record mapping tags to value types, along with associated
 * variant constructors, type predicates and `match` function.
 *
 * @param record A record mapping tags to value types. The actual values of the record don't
 * matter; they're just used in the types of the resulting tagged union. See `ofType`.
 * @param config An optional config object. By default tag='tag' and value is merged into object itself
 * @param config.tag An optional custom name for the tag property of the union.
 * @param config.value An optional custom name for the value property of the union. If not specified,
 * the value must be a dictionary type.
 */
export function unionize<
  Record extends SingleValueRec,
  ValProp extends string,
  TagProp extends string = 'tag'
>(
  record: Record,
  config: { value: ValProp; tag?: TagProp },
): Unionized<Record, SingleValueVariants<Record, TagProp, ValProp>, TagProp>;


export function unionize<Record extends MultiValueRec<TagProp>, TagProp extends string = 'tag'>(
  record: Record,
  config?: { tag: TagProp },
): Unionized<Record, MultiValueVariants<Record, TagProp>, TagProp>;


export function unionize<Record>(record: Record, config?: { value?: string; tag?: string }) {
  const { value: valProp = undefined, tag: tagProp = 'tag' } = config || {};

  const creators = {} as Creators<Record, any, any>;
  for (const tag in record) {
    creators[tag] = ((value: any = {}) =>
      valProp ? { [tagProp]: tag, [valProp]: value } : { ...value, [tagProp]: tag }) as any;
  }

  const is = {} as Predicates<any>;
  for (const tag in record) {
    is[tag] = ((variant: any) => variant[tagProp] === tag) as any;
  }

  function evalMatch(variant: any, cases: any, defaultCase = cases.default): any {
    const handler = cases[variant[tagProp]];
    return handler ? handler(valProp ? variant[valProp] : variant) : defaultCase(variant);
  }

  function match<A>(first: any, second?: any): A {
    return second ? evalMatch(first, second) : (variant: any) => evalMatch(variant, first);
  }

  const identity = <A>(x: A) => x;
  const transform = (first: any, second?: any) =>
    second
      ? evalMatch(first, second, identity)
      : (variant: any) => evalMatch(variant, first, identity);

  const as = {} as Casts<Record, any>;
  for (const expectedTag in record) {
    as[expectedTag] = match({
      [expectedTag]: (x: any) => x,
      default: (val: any) => {
        throw new Error(`Attempted to cast ${val[tagProp]} as ${expectedTag}`);
      },
    });
  }

  return Object.assign(
    {
      is,
      as,
      match,
      transform,
      _Record: record,
    },
    creators,
  );
}


function evalMatch(variant: any, cases: any, defaultCase = cases.default): any {
    const handler = cases[variant[tagProp]];
    return handler ? handler(valProp ? variant[valProp] : variant) : defaultCase(variant);
  }


function match<A>(first: any, second?: any): A {
    return second ? evalMatch(first, second) : (variant: any) => evalMatch(variant, first);
  }


(value: any = {}) =>
      valProp ? { [tagProp]: tag, [valProp]: value } : { ...value, [tagProp]: tag }


(variant: any) => variant[tagProp] === tag


(variant: any) => evalMatch(variant, first)


<A>(x: A) => x


(first: any, second?: any) =>
    second
      ? evalMatch(first, second, identity)
      : (variant: any) => evalMatch(variant, first, identity)


(variant: any) => evalMatch(variant, first, identity)


(x: any) => x


(val: any) => {
        throw new Error(`Attempted to cast ${val[tagProp]} as ${expectedTag}`);
      }


<T>() => (undefined as any) as T


export interface UnionTypes<Record, TaggedRecord> {
  _Tags: keyof TaggedRecord;
  _Record: Record;
  _Union: TaggedRecord[keyof TaggedRecord];
}


export interface UnionExtensions<Record, TaggedRecord> {
  is: Predicates<TaggedRecord>;
  as: Casts<Record, TaggedRecord[keyof TaggedRecord]>;
  match: Match<Record, TaggedRecord[keyof TaggedRecord]>;
  transform: Transform<Record, TaggedRecord[keyof TaggedRecord]>;
}


export interface Match<Record, Union> {
  <A>(cases: MatchCases<Record, Union, A>): (variant: Union) => A;
  <A>(variant: Union, cases: MatchCases<Record, Union, A>): A;
}


export interface Transform<Record, Union> {
  (cases: TransformCases<Record, Union>): (variant: Union) => Union;
  (variant: Union, cases: TransformCases<Record, Union>): Union;
}


// Forbid usage of default property; reserved for pattern matching.
export interface NoDefaultProp {
  default?: never;
}


export type Unionized<Record, TaggedRecord, TagProp extends string> = UnionTypes<
  Record,
  TaggedRecord
> &
  Creators<Record, TaggedRecord, TagProp> &
  UnionExtensions<Record, TaggedRecord>;


export type TagsOf<U extends UnionTypes<any, any>> = U['_Tags'];


export type RecordOf<U extends UnionTypes<any, any>> = U['_Record'];


export type UnionOf<U extends UnionTypes<any, any>> = U['_Union'];


export type Creators<Record, TaggedRecord, TagProp extends string> = {
  [T in keyof Record]: {} extends Required<UnTagged<Record[T], TagProp>>
    ? ((value?: {}) => TaggedRecord[keyof TaggedRecord])
    : ((value: UnTagged<Record[T], TagProp>) => TaggedRecord[keyof TaggedRecord])
};


export type Predicates<TaggedRecord> = {
  [T in keyof TaggedRecord]: (
    variant: TaggedRecord[keyof TaggedRecord],
  ) => variant is TaggedRecord[T]
};


export type Casts<Record, Union> = { [T in keyof Record]: (variant: Union) => Record[T] };


export type Cases<Record, A> = { [T in keyof Record]: (value: Record[T]) => A };


export type MatchCases<Record, Union, A> =
  | Cases<Record, A> & NoDefaultProp
  | Partial<Cases<Record, A>> & { default: (variant: Union) => A };


export type TransformCases<Record, Union> = Partial<
  { [T in keyof Record]: (value: Record[T]) => Union }
>;


export type MultiValueVariants<Record extends MultiValueRec<TagProp>, TagProp extends string> = {
  [T in keyof Record]: Record[T] extends { [_ in TagProp]: T } // does record already has tag with correct value?
    ? Record[T] // yes: return as is
    : { [_ in TagProp]: T } & Record[T] // no: decorate with tag
};


export type UnTagged<Record, TagProp extends string = 'tag'> = Record extends any
  ? Pick<Record, Exclude<keyof Record, TagProp>>
  : never;


export type SingleValueVariants<
  Record extends SingleValueRec,
  TagProp extends string,
  ValProp extends string
> = { [T in keyof Record]: { [_ in TagProp]: T } & { [_ in ValProp]: Record[T] } };


export type SingleValueRec = NoDefaultRec<{} | null>;


export type MultiValueRec<TagProp extends string> = NoDefaultRec<
  { [tag: string]: any } & { [tag in TagProp]?: never }
>;


export type NoDefaultRec<Val> = {
  [k: string]: Val;
} & NoDefaultProp;


/**
 * Ellipse constructor
 * @constructor
 * @memberof Konva
 * @augments Konva.Shape
 * @param {Object} config
 * @param {Object} config.radius defines x and y radius
 * @@shapeParams
 * @@nodeParams
 * @example
 * var ellipse = new Konva.Ellipse({
 *   radius : {
 *     x : 50,
 *     y : 50
 *   },
 *   fill: 'red'
 * });
 */
export class Ellipse extends Shape<EllipseConfig> {
  _sceneFunc(context) {
    var rx = this.radiusX(),
      ry = this.radiusY();

    context.beginPath();
    context.ellipse(0, 0, rx, ry, 0, 0, Math.PI * 2, false);
    context.closePath();
    context.fillStrokeShape(this);
  }
  getWidth() {
    return this.radiusX() * 2;
  }
  getHeight() {
    return this.radiusY() * 2;
  }
  setWidth(width) {
    this.radiusX(width / 2);
  }
  setHeight(height) {
    this.radiusY(height / 2);
  }

  radius: GetSet<Vector2d, this>;
  radiusX: GetSet<number, this>;
  radiusY: GetSet<number, this>;
}


export interface EllipseConfig extends ShapeConfig {
  radiusX: number;
  radiusY: number;
}


_sceneFunc(context) {
    var rx = this.radiusX(),
      ry = this.radiusY();

    context.beginPath();
    context.ellipse(0, 0, rx, ry, 0, 0, Math.PI * 2, false);
    context.closePath();
    context.fillStrokeShape(this);
  }


getWidth() {
    return this.radiusX() * 2;
  }


getHeight() {
    return this.radiusY() * 2;
  }


setWidth(width) {
    this.radiusX(width / 2);
  }


setHeight(height) {
    this.radiusY(height / 2);
  }


option => option.type === type


command => defaultCommandOptions.find(option => option.command === command).label


option => option.command === command


@Component({
  templateUrl: './task.detail.component.html',
  styleUrls: ['./task.detail.component.scss'],
})
export class TaskDetailComponent {
  task$: Observable<TaskDefinition>;

  constructor(private store: Store<fromCustomers.State>) {
    this.task$ = this.store.select(fromCustomers.getSelectedTask);
  }

  formatType(type: TaskDefinitionType): string {
    return defaultTypeOptions.find(option => option.type === type).label;
  }

  formatCommands(commands: TaskDefinitionCommand[]): string {
    const options = commands.map(command => defaultCommandOptions.find(option => option.command === command).label);

    return options.join(', ');
  }
}


formatType(type: TaskDefinitionType): string {
    return defaultTypeOptions.find(option => option.type === type).label;
  }


formatCommands(commands: TaskDefinitionCommand[]): string {
    const options = commands.map(command => defaultCommandOptions.find(option => option.command === command).label);

    return options.join(', ');
  }


@Directive({
  selector: '[ngsGridItem]'
})
export class GridItemDirective implements OnChanges {
  @Input() x = 0;
  @Input() y = 0;

  @HostBinding('style.transform')
  hostTransform: string;

  get translate(): string {
    return `translate(${this.x}px,${this.y}px)`;
  }

  get transform(): string {
    return this.translate;
  }

  ngOnChanges() {
    this.hostTransform = this.transform;
  }
}


ngOnChanges() {
    this.hostTransform = this.transform;
  }


({
  match
}) => {
  const qs = parseQs(location.search.substr(1));
  const params: AppDetailsUrlQueryParams = qs;

  return (
    <AppDetailsView id={decodeURIComponent(match.params.id)} params={params} />


({
  match
}) => <AppDetailsSettingsView id


props => {
  const qs = parseQs(location.search.substr(1));
  const params: AppInstallUrlQueryParams = qs;

  return <AppInstallView params={params} {...props} />;
}


({
  match,
  token,
  onTokenClose
}) => {
  const qs = parseQs(location.search.substr(1));
  const params: CustomAppUrlQueryParams = qs;

  return (
    <CustomAppDetailsView
      id={decodeURIComponent(match.params.id)}
      params={params}
      token={token}
      onTokenClose={onTokenClose}
    />


() => {
  const qs = parseQs(location.search.substr(1));
  const params: AppListUrlQueryParams = qs;

  return <AppsListView params={params} />;
}


() => {
  const intl = useIntl();
  const [token, setToken] = React.useState<string>(null);

  return (
    <>
      <WindowTitle title={intl.formatMessage(sectionNames.apps)} />
      <Switch>
        <Route exact path={appsListPath} component={AppsList} />
        <Route
          exact
          path={customAppAddPath}
          render={()


interface CustomAppDetailsProps extends RouteComponentProps<{ id: string }> {
  token: string;
  onTokenClose: () => void;
}


decodeURIComponent(match


appPath(":id")


appSettingsPath(":id")


customAppPath(":id")


/**
 * Auth middleware is meant to restrict un-authenticated access to a given route
 * or a group of routes.
 *
 * You must register this middleware inside `start/kernel.ts` file under the list
 * of named middleware.
 */
export default class AuthMiddleware {
  /**
  * The URL to redirect to when request is Unauthorized
  */
  protected redirectTo = '/'

  /**
   * Authenticates the current HTTP request against a custom set of defined
   * guards.
   *
   * The authentication loop stops as soon as the user is authenticated using any
   * of the mentioned guards and that guard will be used by the rest of the code
   * during the current request.
   */
  protected async authenticate ({ request, auth, session }: HttpContextContract, guards: any[]): Promise<boolean | never> {
    /**
     * Hold reference to the guard last attempted within the for loop. We pass
     * the reference of the guard to the "AuthenticationException", so that
     * it can decide the correct response behavior based upon the guard
     * driver
     */
    let guardLastAttempted: string | undefined

    for (const guard of guards) {
      guardLastAttempted = guard

      // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
      if (await auth.use(guard).check()) {
        /**
         * Instruct auth to use the given guard as the default guard for
         * the rest of the request, since the user authenticated
         * succeeded here
         */
        auth.defaultGuard = guard
        return true
      }
    }

    /**
     * Unable to authenticate using any guard
     */
    if (session.get('message') === undefined) {
      session.flash('message', { error: 'Error: Please login to perform that action.' })
    }

    session.put('redirectURL', request.parsedUrl.href)
    throw new AuthenticationException(
      'Unauthorized access',
      'E_UNAUTHORIZED_ACCESS',
      guardLastAttempted,
      this.redirectTo
    )
  }

  /**
   * Handle request
   */
  public async handle (ctx: HttpContextContract, next: () => Promise<void>, customGuards: string[]): Promise<void> {
    /**
     * Uses the user defined guards or the default guard mentioned in
     * the config file
     */
    const guards = customGuards.length > 0 ? customGuards : [ctx.auth.name]
    await this.authenticate(ctx, guards)
    await next()
  }
}


/**
   * Authenticates the current HTTP request against a custom set of defined
   * guards.
   *
   * The authentication loop stops as soon as the user is authenticated using any
   * of the mentioned guards and that guard will be used by the rest of the code
   * during the current request.
   */
protected async authenticate ({ request, auth, session }: HttpContextContract, guards: any[]): Promise<boolean | never> {
    /**
     * Hold reference to the guard last attempted within the for loop. We pass
     * the reference of the guard to the "AuthenticationException", so that
     * it can decide the correct response behavior based upon the guard
     * driver
     */
    let guardLastAttempted: string | undefined

    for (const guard of guards) {
      guardLastAttempted = guard

      // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
      if (await auth.use(guard).check()) {
        /**
         * Instruct auth to use the given guard as the default guard for
         * the rest of the request, since the user authenticated
         * succeeded here
         */
        auth.defaultGuard = guard
        return true
      }
    }

    /**
     * Unable to authenticate using any guard
     */
    if (session.get('message') === undefined) {
      session.flash('message', { error: 'Error: Please login to perform that action.' })
    }

    session.put('redirectURL', request.parsedUrl.href)
    throw new AuthenticationException(
      'Unauthorized access',
      'E_UNAUTHORIZED_ACCESS',
      guardLastAttempted,
      this.redirectTo
    )
  }


/**
   * Handle request
   */
public async handle (ctx: HttpContextContract, next: () => Promise<void>, customGuards: string[]): Promise<void> {
    /**
     * Uses the user defined guards or the default guard mentioned in
     * the config file
     */
    const guards = customGuards.length > 0 ? customGuards : [ctx.auth.name]
    await this.authenticate(ctx, guards)
    await next()
  }


async function withTimeout(promise: Promise<Api>, timeoutMs: number = DEFAULT_TIMEOUT): Promise<Api> {
  if (timeoutMs === 0) {
    return promise;
  }

  return Promise.race<Api>([
    promise,
    new Promise<Api>((_, reject) => {
      setTimeout(() => {
        reject(new Error(`Timed out in ${timeoutMs} ms.`));
      }, timeoutMs);
    }),
  ]);
}


(resolve, reject) => {
        const rejectError = err => {
          // Disconnect provider if API initialization fails
          api.disconnect();

          reject(new Error('Connection fail'));
        };

        api.isReady.then(res => {
          //  Remove error listener if API initialization success.
          (api as any)._eventemitter.removeListener('error', rejectError);
          resolve((res as unknown) as Api);
        }, reject);

        api.once('error', rejectError);
      }


err => {
          // Disconnect provider if API initialization fails
          api.disconnect();

          reject(new Error('Connection fail'));
        }


res => {
          //  Remove error listener if API initialization success.
          (api as any)._eventemitter.removeListener('error', rejectError);
          resolve((res as unknown) as Api);
        }


(_, reject) => {
      setTimeout(() => {
        reject(new Error(`Timed out in ${timeoutMs} ms.`));
      }, timeoutMs);
    }


() => {
        reject(new Error(`Timed out in ${timeoutMs} ms.`));
      }