[geany/geany] 16a8ba: Merge pull request #3165 from techee/typescript_parser

Jiří Techet git-noreply at geany.org
Fri May 13 00:01:18 UTC 2022


Branch:      refs/heads/master
Author:      Jiří Techet <techet at gmail.com>
Committer:   GitHub <noreply at github.com>
Date:        Fri, 13 May 2022 00:01:18 UTC
Commit:      16a8ba30ffd5ca999297492908ed214c98edb02b
             https://github.com/geany/geany/commit/16a8ba30ffd5ca999297492908ed214c98edb02b

Log Message:
-----------
Merge pull request #3165 from techee/typescript_parser

Add typescript ctags parser


Modified Paths:
--------------
    ctags/Makefile.am
    ctags/parsers/typescript.c
    data/filedefs/filetypes.TypeScript.conf
    meson.build
    src/tagmanager/tm_parser.c
    src/tagmanager/tm_parser.h
    src/tagmanager/tm_parsers.h
    tests/ctags/Makefile.am
    tests/ctags/simple.ts
    tests/ctags/simple.ts.tags
    tests/meson.build

Modified: ctags/Makefile.am
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -98,6 +98,7 @@ parsers = \
 	parsers/tex.c \
 	parsers/tex.h \
 	parsers/txt2tags.c \
+	parsers/typescript.c \
 	parsers/verilog.c \
 	parsers/vhdl.c
 


Modified: ctags/parsers/typescript.c
2139 lines changed, 2139 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,2139 @@
+/*
+ *	 Copyright (c) 2019, Karol Samborski
+ *
+ *	 This source code is released for free distribution under the terms of the
+ *	 GNU General Public License version 2 or (at your option) any later version.
+ *
+ *	 This module contains functions for generating tags for TypeScript language
+ *	 files.
+ *
+ *	 Reference: https://github.com/Microsoft/TypeScript/blob/master/doc/TypeScript%20Language%20Specification.pdf
+ *
+ */
+
+/*
+ *	 INCLUDE FILES
+ */
+#include "general.h"    /* must always come first */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "parse.h"
+#include "objpool.h"
+#include "keyword.h"
+#include "read.h"
+#include "numarray.h"
+#include "routines.h"
+#include "entry.h"
+#include "inline.h"
+#include "unwindi.h"
+
+#define isType(token,t)     (bool) ((token)->type == (t))
+#define isKeyword(token,k)  (bool) ((token)->keyword == (k))
+#define newToken() (objPoolGet (TokenPool))
+#define deleteToken(t) (objPoolPut (TokenPool, (t)))
+#define isIdentChar(c) \
+	(isalpha (c) || isdigit (c) || (c) == '$' || \
+	 (c) == '@' || (c) == '_' || (c) == '#' || \
+	 (c) >= 0x80)
+
+#define PARSER_DEF(fname, pfun, word, stateField) \
+	CTAGS_INLINE void parse ## fname (const int c, tokenInfo * const token, parserState *state, parserResult * const result) \
+	{ \
+		pfun (c, token, word, &state->stateField, result); \
+	}
+
+#define SINGLE_CHAR_PARSER_DEF(fname, ch, ttype) \
+	CTAGS_INLINE void parse ## fname (const int c, tokenInfo * const token, parserState *state, parserResult * const result) \
+	{ \
+		parseOneChar (c, token, ch, ttype, result); \
+	}
+
+#define MULTI_CHAR_PARSER_DEF(fname, chs, ...) \
+	CTAGS_INLINE void parse ## fname (const int c, tokenInfo * const token, parserState *state, parserResult * const result) \
+	{ \
+		tokenType types[] = { __VA_ARGS__ }; \
+		parseChar (c, token, state, result, chs, types); \
+	}
+
+#define WORD_TOKEN_PARSER_DEF(fname, w, ttype) \
+	CTAGS_INLINE void parse ## fname (const int c, tokenInfo * const token, parserState *state, parserResult * const result) \
+	{ \
+		parseWordToken (c, token, w, ttype, &state->num, result); \
+	}
+
+#define BLOCK_PARSER_DEF(fname, start, end, ttype) \
+	CTAGS_INLINE void parse ## fname (const int c, tokenInfo * const token, parserState *state, parserResult * const result) \
+	{ \
+		parseBlock (c, token, ttype, start, end, &state->block, result); \
+	}
+
+/*
+ *	DATA DEFINITIONS
+ */
+static langType Lang_ts;
+static objPool *TokenPool = NULL;
+
+/*	Used to specify type of keyword.
+ */
+enum eKeywordId {
+	KEYWORD_as,
+	KEYWORD_async,
+	KEYWORD_await,
+	KEYWORD_class,
+	KEYWORD_constructor,
+	KEYWORD_const,
+	KEYWORD_enum,
+	KEYWORD_extends,
+	KEYWORD_for,
+	KEYWORD_function,
+	KEYWORD_instanceof,
+	KEYWORD_in,
+	KEYWORD_interface,
+	KEYWORD_implements,
+	KEYWORD_let,
+	KEYWORD_namespace,
+	KEYWORD_new,
+	KEYWORD_of,
+	KEYWORD_private,
+	KEYWORD_protected,
+	KEYWORD_public,
+	KEYWORD_return,
+	KEYWORD_readonly,
+	KEYWORD_static,
+	KEYWORD_this,
+	KEYWORD_type,
+	KEYWORD_typeof,
+	KEYWORD_var,
+	KEYWORD_while
+};
+typedef int keywordId; /* to allow KEYWORD_NONE */
+
+typedef enum eTokenType {
+	TOKEN_UNDEFINED,
+	TOKEN_EOF,
+	TOKEN_CHARACTER,
+	TOKEN_SEMICOLON,
+	TOKEN_COLON,
+	TOKEN_QUESTION_MARK,
+	TOKEN_COMMA,
+	TOKEN_KEYWORD,
+	TOKEN_IDENTIFIER,
+	TOKEN_STRING,
+	TOKEN_TEMPLATE,
+	TOKEN_PERIOD,
+	TOKEN_OPEN_CURLY,
+	TOKEN_CLOSE_CURLY,
+	TOKEN_OPEN_PAREN,
+	TOKEN_CLOSE_PAREN,
+	TOKEN_OPEN_SQUARE,
+	TOKEN_CLOSE_SQUARE,
+	TOKEN_EQUAL_SIGN,
+	TOKEN_STAR,
+	TOKEN_NL,
+	TOKEN_COMMENT_BLOCK,
+	TOKEN_PARENS,
+	TOKEN_SQUARES,
+	TOKEN_CURLIES,
+	TOKEN_PIPE,
+	TOKEN_AMPERSAND,
+	TOKEN_ARROW,
+	TOKEN_NUMBER,
+	TOKEN_AT,
+	TOKEN_MINUS,
+	TOKEN_PLUS,
+	TOKEN_DIV,
+	TOKEN_POWER,
+	TOKEN_GREATER,
+	TOKEN_LOWER
+} tokenType;
+
+typedef enum {
+	TSTAG_FUNCTION,
+	TSTAG_CLASS,
+	TSTAG_INTERFACE,
+	TSTAG_ENUM,
+	TSTAG_ENUMERATOR,
+	TSTAG_METHOD,
+	TSTAG_NAMESPACE,
+	TSTAG_PARAMETER,
+	TSTAG_PROPERTY,
+	TSTAG_VARIABLE,
+	TSTAG_LOCAL,
+	TSTAG_CONSTANT,
+	TSTAG_GENERATOR,
+	TSTAG_ALIAS
+} tsKind;
+
+typedef struct sTokenInfo {
+	tokenType type;
+	keywordId keyword;
+	vString *string;
+	int scope;
+	unsigned long lineNumber;
+	MIOPos filePosition;
+	keywordId accessKeyword;
+} tokenInfo;
+
+typedef struct sCommentState {
+	int parsed;
+	int blockParsed;
+	bool isBlock;
+} commentState;
+
+typedef struct sBlockState {
+	int parsed;
+	int nestLevel;
+	int curlyLevel;
+} blockState;
+
+static const keywordTable TsKeywordTable [] = {
+	/* keyword		  keyword ID */
+	{ "as"	        , KEYWORD_as          },
+	{ "async"       , KEYWORD_async       },
+	{ "await"       , KEYWORD_await       },
+	{ "class"       , KEYWORD_class       },
+	{ "const"       , KEYWORD_const       },
+	{ "constructor" , KEYWORD_constructor },
+	{ "enum"        , KEYWORD_enum        },
+	{ "extends"     , KEYWORD_extends     },
+	{ "for"         , KEYWORD_for         },
+	{ "function"    , KEYWORD_function    },
+	{ "in"          , KEYWORD_in          },
+	{ "interface"   , KEYWORD_interface   },
+	{ "implements"  , KEYWORD_implements  },
+	{ "let"         , KEYWORD_let         },
+	{ "namespace"   , KEYWORD_namespace   },
+	{ "new"			, KEYWORD_new		  },
+	{ "of"          , KEYWORD_of          },
+	{ "private"     , KEYWORD_private     },
+	{ "protected"   , KEYWORD_protected   },
+	{ "public"      , KEYWORD_public      },
+	{ "static"      , KEYWORD_static      },
+	{ "this"        , KEYWORD_this        },
+	{ "type"        , KEYWORD_type        },
+	{ "typeof"      , KEYWORD_typeof      },
+	{ "var"         , KEYWORD_var         },
+	{ "while"       , KEYWORD_while       }
+};
+
+static kindDefinition TsKinds [] = {
+	{ true,  'f', "function",     "functions"                                       },
+	{ true,  'c', "class",        "classes"                                         },
+	{ true,  'i', "interface",    "interfaces"                                      },
+	{ true,  'g', "enum",         "enums"                                           },
+	{ true,  'e', "enumerator",   "enumerators (values inside an enumeration)"      },
+	{ true,  'm', "method",       "methods"                                         },
+	{ true,  'n', "namespace",    "namespaces"                                      },
+	{ false, 'z', "parameter",    "function parameters inside function definitions" },
+	{ true,  'p', "property",     "properties"                                      },
+	{ true,  'v', "variable",     "variables"                                       },
+	{ false, 'l', "local",        "local variables"                                 },
+	{ true,  'C', "constant",     "constants"                                       },
+	{ true,  'G', "generator",    "generators"                                      },
+	{ true,  'a', "alias",        "aliases",                                        }
+};
+
+typedef enum eParserResult {
+	PARSER_FINISHED,
+	PARSER_NEEDS_MORE_INPUT,
+	PARSER_FAILED
+} parserResultStatus;
+
+typedef struct sParserResult {
+	parserResultStatus status;
+	unsigned int unusedChars;
+} parserResult;
+
+typedef union uParserState {
+	int num;
+	blockState block;
+	commentState comment;
+	char ch;
+} parserState;
+
+static struct sUwiStats tsUwiStats;
+
+typedef void (*Parser)(const int c, tokenInfo *const, parserState *state, parserResult *const);
+
+static bool tryParser(Parser parser, tokenInfo *const token, bool skipWhite);
+CTAGS_INLINE void parseStringRegex(const int c, tokenInfo * const token, parserState *state, parserResult * const result);
+CTAGS_INLINE void parseStringSQuote(const int c, tokenInfo * const token, parserState *state, parserResult * const result);
+CTAGS_INLINE void parseStringDQuote(const int c, tokenInfo * const token, parserState *state, parserResult * const result);
+CTAGS_INLINE void parseStringTemplate(const int c, tokenInfo * const token, parserState *state, parserResult * const result);
+
+static int emitTag(const tokenInfo *const token, const tsKind kind)
+{
+	if (! TsKinds [kind].enabled) return CORK_NIL;
+
+	static const char *const access [3] = {
+		"private",
+		"protected",
+		"public"
+	};
+
+	const char *name = vStringValue (token->string);
+	tagEntryInfo e;
+
+	initTagEntry (&e, name, kind);
+	e.lineNumber   = token->lineNumber;
+	e.filePosition = token->filePosition;
+	e.extensionFields.scopeIndex = token->scope;
+
+	switch (token->accessKeyword)
+	{
+		case KEYWORD_public:
+			e.extensionFields.access = access [2];
+			break;
+		case KEYWORD_protected:
+			e.extensionFields.access = access [1];
+			break;
+		case KEYWORD_private:
+			e.extensionFields.access = access [0];
+			break;
+	}
+
+	return makeTagEntry (&e);
+}
+
+static void *newPoolToken (void *createArg CTAGS_ATTR_UNUSED)
+{
+	tokenInfo *token = xMalloc (1, tokenInfo);
+	token->scope = CORK_NIL;
+	token->string = NULL;
+
+	return token;
+}
+
+static void clearPoolToken (void *data)
+{
+	tokenInfo *token = data;
+
+	token->type            = TOKEN_UNDEFINED;
+	token->keyword         = KEYWORD_NONE;
+	token->lineNumber      = uwiGetLineNumber ();
+	token->filePosition    = uwiGetFilePosition ();
+
+	token->scope = CORK_NIL;
+
+	token->accessKeyword = KEYWORD_NONE;
+
+	token->string = vStringNewOrClear (token->string);
+}
+
+static void deletePoolToken (void *data)
+{
+	tokenInfo *token = data;
+	vStringDelete (token->string); /* NULL is acceptable */
+	eFree (token);
+}
+
+static void copyToken (tokenInfo *const dest, const tokenInfo *const src,
+					   bool scope)
+{
+	dest->lineNumber = src->lineNumber;
+	dest->filePosition = src->filePosition;
+	dest->type = src->type;
+	dest->keyword = src->keyword;
+	vStringCopy (dest->string, src->string);
+	if (scope) dest->scope = src->scope;
+}
+
+static void initToken (tokenInfo *const token, tokenType type)
+{
+	token->type = type;
+	token->keyword = KEYWORD_NONE;
+	token->lineNumber   = uwiGetLineNumber ();
+	token->filePosition = uwiGetFilePosition ();
+}
+
+CTAGS_INLINE bool whiteChar(const int c)
+{
+	return c == ' ' || c == '\r' || c == '\t';
+}
+
+CTAGS_INLINE void parseWhiteChars(const int c, tokenInfo *const token, parserState *state, parserResult * const result)
+{
+	if (whiteChar (c))
+	{
+		result->status = PARSER_NEEDS_MORE_INPUT;
+		state->num += 1;
+		return;
+	}
+
+	if (state->num == 0)
+	{
+		result->status = PARSER_FAILED;
+		return;
+	}
+
+	result->status = PARSER_FINISHED;
+	result->unusedChars = 1;
+}
+
+CTAGS_INLINE void parseOneChar(const int c, tokenInfo *const token, const char expected, const tokenType type, parserResult *const result)
+{
+	if (c != expected)
+	{
+		result->status = PARSER_FAILED;
+		return;
+	}
+
+	initToken (token, type);
+	result->status = PARSER_FINISHED;
+}
+
+CTAGS_INLINE void parseChar(const int c, tokenInfo *const token, void *state, parserResult *const result, const char *chars, const tokenType *types)
+{
+	const char *pos = strchr (chars, c);
+
+	if (pos)
+	{
+		result->status = PARSER_FINISHED;
+		initToken (token, types[pos - chars]);
+		return;
+	}
+
+	result->status = PARSER_FAILED;
+}
+
+CTAGS_INLINE void parseWord(const int c, tokenInfo *const token, const char *word, int *parsed, parserResult *const result)
+{
+	if (word [*parsed] == '\0')
+	{
+		if (isIdentChar (c))
+		{
+			result->status = PARSER_FAILED;
+			return;
+		}
+
+		vStringCatS (token->string, word);
+		initToken (token, TOKEN_KEYWORD);
+		token->keyword = lookupKeyword (vStringValue (token->string), Lang_ts);
+
+		result->unusedChars = 1;
+		result->status = PARSER_FINISHED;
+		return;
+	}
+
+	if (c == word [*parsed])
+	{
+		*parsed += 1;
+
+		result->status = PARSER_NEEDS_MORE_INPUT;
+		return;
+	}
+
+	result->status = PARSER_FAILED;
+}
+
+CTAGS_INLINE void parseNumber(const int c, tokenInfo *const token, int *parsed, parserResult *const result)
+{
+	if (*parsed == 0)
+	{
+		if (c == '-')
+		{
+			result->status = PARSER_NEEDS_MORE_INPUT;
+			*parsed += 1;
+			return;
+		}
+	}
+
+	if (isdigit (c))
+	{
+		result->status = PARSER_NEEDS_MORE_INPUT;
+		*parsed += 1;
+		return;
+	}
+	else if (*parsed == 0)
+	{
+		result->status = PARSER_FAILED;
+		return;
+	}
+
+	initToken (token, TOKEN_NUMBER);
+
+	result->unusedChars = 1;
+	result->status = PARSER_FINISHED;
+}
+
+CTAGS_INLINE void parseWordToken(const int c, tokenInfo *const token, const char *word, const tokenType type, int *parsed, parserResult *const result)
+{
+	if (c == word [*parsed])
+	{
+		*parsed += 1;
+
+		if (word [*parsed] == '\0')
+		{
+			initToken (token, type);
+			result->status = PARSER_FINISHED;
+			return;
+		}
+
+		result->status = PARSER_NEEDS_MORE_INPUT;
+		return;
+	}
+
+	result->status = PARSER_FAILED;
+}
+
+CTAGS_INLINE void parseComment(const int c, tokenInfo *const token, parserState *state, parserResult *const result)
+{
+	if (state->comment.parsed < 2)
+	{
+		parseWordToken (c, token, "//", TOKEN_COMMENT_BLOCK, &state->comment.parsed, result);
+
+		if (result->status == PARSER_FAILED)
+		{
+			parseWordToken (c, token, "/*", TOKEN_COMMENT_BLOCK, &state->comment.parsed, result);
+			if (result->status == PARSER_FINISHED)
+			{
+				result->status = PARSER_NEEDS_MORE_INPUT;
+				state->comment.isBlock = true;
+			}
+		}
+		else if (result->status == PARSER_FINISHED)
+		{
+			result->status = PARSER_NEEDS_MORE_INPUT;
+			state->comment.isBlock = false;
+		}
+
+		return;
+	}
+
+	state->comment.parsed += 1;
+
+	if (c == EOF) result->status = PARSER_FINISHED;
+	else if (state->comment.isBlock)
+	{
+		parseWordToken (c, token, "*/", TOKEN_COMMENT_BLOCK, &state->comment.blockParsed, result);
+
+		if (result->status == PARSER_FAILED)
+		{
+			state->comment.blockParsed = c == '*' ? 1 : 0;
+			result->status = PARSER_NEEDS_MORE_INPUT;
+		}
+	}
+	else if (c == '\n')
+	{
+		result->status = PARSER_FINISHED;
+		result->unusedChars = 1;
+	}
+
+	if (result->status == PARSER_FINISHED)
+	{
+		initToken (token, TOKEN_COMMENT_BLOCK);
+		return;
+	}
+
+	result->status = PARSER_NEEDS_MORE_INPUT;
+}
+
+CTAGS_INLINE void parseString(const int c, tokenInfo *const token, const char quote, char *prev, parserResult *const result)
+{
+	if (*prev == '\0')
+	{
+		if (c == quote)
+		{
+			*prev = c;
+			result->status = PARSER_NEEDS_MORE_INPUT;
+		}
+		else result->status = PARSER_FAILED;
+
+		return;
+	}
+	else if (c == EOF)
+	{
+		result->status = PARSER_FAILED;
+
+		return;
+	}
+
+	if (c == '\\' && *prev == '\\')
+	{
+		*prev = '\1';
+		result->status = PARSER_NEEDS_MORE_INPUT;
+
+		return;
+	}
+	else if (c == quote && *prev != '\\')
+	{
+		result->status = PARSER_FINISHED;
+		initToken (token, TOKEN_STRING);
+
+		return;
+	}
+	else if (quote == '/' && c == '\n') // regex cannot contain new lines
+	{
+		result->status = PARSER_FAILED;
+
+		return;
+	}
+
+	*prev = c;
+	result->status = PARSER_NEEDS_MORE_INPUT;
+}
+
+
+CTAGS_INLINE void parseBlock(const int c, tokenInfo *const token, tokenType const ttype, const char start, const char end, blockState *state, parserResult *const result)
+{
+	if (state->parsed == 0)
+	{
+		if (c != start)
+		{
+			result->status = PARSER_FAILED;
+			return;
+		}
+
+		state->parsed = 1;
+	}
+
+	if (c == '{') state->curlyLevel += 1;
+	else if (c == '}') state->curlyLevel -= 1;
+
+	if (c == start) state->nestLevel += 1;
+	else if (c == end) state->nestLevel -= 1;
+	else if ((state->curlyLevel < 1 && c == ';') || c == EOF)
+	{
+		result->status = PARSER_FAILED;
+		return;
+	}
+
+	if (state->nestLevel <= 0)
+	{
+		initToken (token, ttype);
+		result->status = PARSER_FINISHED;
+
+		return;
+	}
+
+	//skip comments:
+	tryParser ((Parser) parseComment, token, false);
+	//skip strings:
+	tryParser ((Parser) parseStringRegex, token, false);
+	tryParser ((Parser) parseStringSQuote, token, false);
+	tryParser ((Parser) parseStringDQuote, token, false);
+	tryParser ((Parser) parseStringTemplate, token, false);
+
+	result->status = PARSER_NEEDS_MORE_INPUT;
+}
+
+
+CTAGS_INLINE void parseIdentifierCommon(const int c, tokenInfo *const token, int *parsed, parserResult *const result,
+										   bool acceptFqName)
+{
+	if (isIdentChar (c) || (acceptFqName && c == '.'))
+	{
+		vStringPut (token->string, c);
+		*parsed = *parsed + 1;
+		result->status = PARSER_NEEDS_MORE_INPUT;
+
+		return;
+	}
+
+	if (*parsed > 0)
+	{
+		initToken (token, TOKEN_IDENTIFIER);
+		result->status = PARSER_FINISHED;
+		result->unusedChars = 1;
+		return;
+	}
+
+	result->status = PARSER_FAILED;
+}
+
+CTAGS_INLINE void parseIdentifier(const int c, tokenInfo *const token, int *parsed, parserResult *const result)
+{
+	parseIdentifierCommon (c, token, parsed, result, false);
+}
+
+CTAGS_INLINE void parseFQIdentifier(const int c, tokenInfo *const token, int *parsed, parserResult *const result)
+{
+	parseIdentifierCommon (c, token, parsed, result, true);
+}
+
+PARSER_DEF (AsKeyword, parseWord, "as", num)
+PARSER_DEF (AsyncKeyword, parseWord, "async", num)
+PARSER_DEF (AwaitKeyword, parseWord, "await", num)
+PARSER_DEF (ClassKeyword, parseWord, "class", num)
+PARSER_DEF (ConstKeyword, parseWord, "const", num)
+PARSER_DEF (ConstructorKeyword, parseWord, "constructor", num)
+PARSER_DEF (EnumKeyword, parseWord, "enum", num)
+PARSER_DEF (ExtendsKeyword, parseWord, "extends", num)
+PARSER_DEF (ForKeyword, parseWord, "for", num)
+PARSER_DEF (FunctionKeyword, parseWord, "function", num)
+PARSER_DEF (InKeyword, parseWord, "in", num)
+PARSER_DEF (InterfaceKeyword, parseWord, "interface", num)
+PARSER_DEF (ImplementsKeyword, parseWord, "implements", num)
+PARSER_DEF (LetKeyword, parseWord, "let", num)
+PARSER_DEF (NamespaceKeyword, parseWord, "namespace", num)
+PARSER_DEF (NewKeyword, parseWord, "new", num)
+PARSER_DEF (OfKeyword, parseWord, "of", num)
+PARSER_DEF (PrivateKeyword, parseWord, "private", num)
+PARSER_DEF (ProtectedKeyword, parseWord, "protected", num)
+PARSER_DEF (PublicKeyword, parseWord, "public", num)
+PARSER_DEF (ReadonlyKeyword, parseWord, "readonly", num)
+PARSER_DEF (StaticKeyword, parseWord, "static", num)
+PARSER_DEF (ThisKeyword, parseWord, "this", num)
+PARSER_DEF (TypeKeyword, parseWord, "type", num)
+PARSER_DEF (TypeofKeyword, parseWord, "typeof", num)
+PARSER_DEF (VarKeyword, parseWord, "var", num)
+PARSER_DEF (WhileKeyword, parseWord, "while", num)
+
+SINGLE_CHAR_PARSER_DEF (Colon, ':', TOKEN_COLON)
+SINGLE_CHAR_PARSER_DEF (Period, '.', TOKEN_PERIOD)
+SINGLE_CHAR_PARSER_DEF (OpenCurly, '{', TOKEN_OPEN_CURLY)
+SINGLE_CHAR_PARSER_DEF (Star, '*', TOKEN_STAR)
+SINGLE_CHAR_PARSER_DEF (At, '@', TOKEN_AT)
+SINGLE_CHAR_PARSER_DEF (NewLine, '\n', TOKEN_NL)
+
+WORD_TOKEN_PARSER_DEF (Arrow, "=>", TOKEN_ARROW)
+
+PARSER_DEF (StringSQuote, parseString, '\'', ch)
+PARSER_DEF (StringDQuote, parseString, '"', ch)
+PARSER_DEF (StringTemplate, parseString, '`', ch)
+PARSER_DEF (StringRegex, parseString, '/', ch)
+
+BLOCK_PARSER_DEF (Parens, '(', ')', TOKEN_PARENS)
+BLOCK_PARSER_DEF (Squares, '[', ']', TOKEN_SQUARES)
+BLOCK_PARSER_DEF (Template, '<', '>', TOKEN_TEMPLATE)
+BLOCK_PARSER_DEF (Curlies, '{', '}', TOKEN_CURLIES)
+
+CTAGS_INLINE bool tryParser(Parser parser, tokenInfo *const token, bool skipWhite)
+{
+	parserState currentState;
+	parserResult result;
+	int c;
+
+	result.status = PARSER_NEEDS_MORE_INPUT;
+	result.unusedChars = 0;
+	memset(&currentState, 0, sizeof (currentState));
+
+	uwiPushMarker();
+
+	c = uwiGetC ();
+	if (skipWhite && whiteChar (c))
+	{
+		do {
+			c = uwiGetC ();
+		} while (whiteChar (c));
+	}
+	parser (c, token, &currentState, &result);
+
+	while (result.status == PARSER_NEEDS_MORE_INPUT)
+	{
+		c = uwiGetC ();
+		parser (c, token, &currentState, &result);
+	}
+
+	if (result.status == PARSER_FAILED) uwiPopMarker (-1, true);
+	else if (result.unusedChars > 0) uwiPopMarker (result.unusedChars, true);
+	else uwiDropMaker ();
+
+	return result.status == PARSER_FINISHED;
+}
+
+static bool tryInSequence(tokenInfo *const token, bool skipUnparsed, Parser parser, ...)
+{
+	Parser currentParser = NULL;
+	bool result = false;
+
+	tryParser (parseWhiteChars, token, false);
+
+	va_list args;
+	va_start (args, parser);
+
+	currentParser = parser;
+	while (! result && currentParser)
+	{
+		result = tryParser (currentParser, token, false);
+		currentParser = va_arg (args, Parser);
+	}
+
+	va_end (args);
+
+	if (skipUnparsed && ! result)
+	{
+		bool skippedNextWord = false;
+		int c = uwiGetC ();
+
+		while (c != EOF && isIdentChar(c))
+		{
+			c = uwiGetC ();
+			skippedNextWord = true;
+		}
+
+		if (c != EOF && skippedNextWord) uwiUngetC (c);
+
+		return c != EOF;
+	}
+
+	return result;
+}
+
+MULTI_CHAR_PARSER_DEF (DecoratorChars, "@\n", TOKEN_AT, TOKEN_NL)
+static void parseDecorator (tokenInfo *const token)
+{
+	bool parsed = false;
+
+	do
+	{
+		clearPoolToken (token);
+		parsed = tryInSequence (token, false, parseDecoratorChars, parseComment, NULL);
+	} while (parsed && token->type != TOKEN_PARENS);
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, false, parseNewLine, parseComment, parseIdentifier, NULL);
+	} while (parsed && (token->type != TOKEN_IDENTIFIER || tryParser ((Parser) parsePeriod, token, true)));
+
+	//parse optional parens block
+	tryParser ((Parser) parseParens, token, true);
+}
+
+MULTI_CHAR_PARSER_DEF (InterfaceBodyChars, "}:;.,|&",
+		TOKEN_CLOSE_CURLY, TOKEN_COLON,
+		TOKEN_SEMICOLON, TOKEN_PERIOD, TOKEN_COMMA,
+		TOKEN_PIPE, TOKEN_AMPERSAND)
+static void parseInterfaceBody (const int scope, tokenInfo *const token)
+{
+	bool parsed;
+
+	do
+	{
+		parsed = tryInSequence (token, true,
+								parseComment,
+								parseTemplate,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseParens,
+								parseSquares,
+								parseOpenCurly,
+								NULL);
+	} while (parsed && ! isType (token, TOKEN_OPEN_CURLY));
+
+	if (! parsed) return;
+
+	tokenInfo *member = NULL;
+	bool parsingType = false;
+	int visibility = 0;
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, true,
+								parseTemplate,
+								parseComment,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseParens,
+								parseSquares,
+								parseCurlies,
+								parseNumber,
+								parsePrivateKeyword,
+								parseProtectedKeyword,
+								parsePublicKeyword,
+								parseReadonlyKeyword,
+								parseStaticKeyword,
+								parseArrow,
+								parseTypeofKeyword,
+								parseInterfaceBodyChars,
+								parseIdentifier,
+								NULL);
+
+		if (parsed)
+		{
+			switch (token->type)
+			{
+				case TOKEN_KEYWORD:
+					switch (token->keyword)
+					{
+						case KEYWORD_private:
+						case KEYWORD_public:
+						case KEYWORD_protected:
+							if (member)
+							{
+								emitTag (member, TSTAG_PROPERTY);
+								deleteToken (member);
+								member = NULL;
+							}
+							visibility = token->keyword;
+							parsingType = false;
+							break;
+						case KEYWORD_typeof:
+							parsingType = true;
+							break;
+					}
+					break;
+				case TOKEN_COLON:
+				case TOKEN_PERIOD:
+				case TOKEN_PIPE:
+				case TOKEN_AMPERSAND:
+				case TOKEN_ARROW:
+					parsingType = true;
+					break;
+				case TOKEN_IDENTIFIER:
+					if (member)
+					{
+						emitTag (member, TSTAG_PROPERTY);
+						deleteToken (member);
+						member = NULL;
+						visibility = 0;
+					}
+
+					if (!parsingType)
+					{
+						member = newToken ();
+						copyToken (member, token, false);
+						member->scope = scope;
+						if (visibility) member->accessKeyword = visibility;
+						else member->accessKeyword = KEYWORD_public;
+					}
+					parsingType = false;
+					break;
+				case TOKEN_PARENS:
+					if (!parsingType && member)
+					{
+						emitTag (member, TSTAG_METHOD);
+						deleteToken (member);
+						member = NULL;
+						visibility = 0;
+					}
+				case TOKEN_SQUARES:
+				case TOKEN_CURLIES:
+				case TOKEN_TEMPLATE:
+				case TOKEN_SEMICOLON:
+				case TOKEN_COMMA:
+				case TOKEN_STRING:
+				case TOKEN_NUMBER:
+					parsingType = false;
+					break;
+				default:
+					break;
+			}
+		}
+	} while (parsed && ! isType (token, TOKEN_CLOSE_CURLY));
+
+	if (member)
+	{
+		emitTag (member, TSTAG_PROPERTY);
+		deleteToken (member);
+	}
+}
+
+static void parseInterface (const int scope, tokenInfo *const token)
+{
+	bool parsed;
+
+	do
+	{
+		clearPoolToken (token);
+		parsed = tryInSequence (token, false,
+								parseNewLine,
+								parseComment,
+								parseIdentifier,
+								NULL);
+	} while (parsed && token->type != TOKEN_IDENTIFIER);
+
+	if (! parsed) return;
+
+	token->scope = scope;
+
+	const int nscope = emitTag (token, TSTAG_INTERFACE);
+
+	parseInterfaceBody (nscope, token);
+}
+
+MULTI_CHAR_PARSER_DEF (TypeChars, ":;,=|&",
+		TOKEN_COLON, TOKEN_SEMICOLON, TOKEN_COMMA,
+		TOKEN_EQUAL_SIGN, TOKEN_PIPE, TOKEN_AMPERSAND)
+static void parseType (const int scope, tokenInfo *const token)
+{
+	bool parsed;
+	bool parsingType = false;
+	bool shouldEnd = false;
+
+	do
+	{
+		clearPoolToken (token);
+		parsed = tryInSequence (token, false,
+								parseComment,
+								parseTemplate,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseParens,
+								parseSquares,
+								parseCurlies,
+								parseArrow,
+								parseNumber,
+								parseTypeofKeyword,
+								parseTypeChars,
+								parseIdentifier,
+								NULL);
+		if (parsed)
+		{
+			switch (token->type)
+			{
+				case TOKEN_KEYWORD:
+					switch (token->keyword)
+					{
+						case KEYWORD_typeof:
+							shouldEnd = true;
+							parsingType = true;
+							break;
+						default:
+							break;
+					}
+					break;
+				case TOKEN_COLON:
+				case TOKEN_TEMPLATE:
+				case TOKEN_STRING:
+				case TOKEN_PARENS:
+				case TOKEN_SQUARES:
+				case TOKEN_CURLIES:
+				case TOKEN_NUMBER:
+					shouldEnd = true;
+					parsingType = false;
+					break;
+				case TOKEN_EQUAL_SIGN:
+				case TOKEN_PIPE:
+				case TOKEN_AMPERSAND:
+				case TOKEN_ARROW:
+					parsingType = true;
+					break;
+				case TOKEN_IDENTIFIER:
+					if (! parsingType && ! shouldEnd)
+					{
+						token->scope = scope;
+						emitTag (token, TSTAG_ALIAS);
+					}
+					else if (! parsingType && shouldEnd)
+					{
+						for (int i = vStringLength(token->string); i >= 0; i--)
+							uwiUngetC (vStringChar(token->string, i));
+
+						parsed = false;
+					}
+					parsingType = false;
+					break;
+				default:
+					break;
+			}
+		}
+	} while (parsed && ! isType (token, TOKEN_COMMA) && ! isType (token, TOKEN_SEMICOLON));
+
+	clearPoolToken(token);
+}
+
+MULTI_CHAR_PARSER_DEF (EnumBodyChars, "},=|&.",
+		TOKEN_CLOSE_CURLY, TOKEN_COMMA, TOKEN_EQUAL_SIGN,
+		TOKEN_PIPE, TOKEN_AMPERSAND, TOKEN_PERIOD)
+static void parseEnumBody (const int scope, tokenInfo *const token)
+{
+	bool parsed;
+
+	do
+	{
+		parsed = tryInSequence (token, true,
+								parseTemplate,
+								parseComment,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseParens,
+								parseSquares,
+								parseOpenCurly,
+								NULL);
+	} while (parsed && token->type != TOKEN_OPEN_CURLY);
+
+	if (! parsed) return;
+
+	tokenInfo *member = NULL;
+	bool parsingValue = false;
+	do
+	{
+		clearPoolToken (token);
+		parsed = tryInSequence (token, true,
+								parseTemplate,
+								parseComment,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseParens,
+								parseSquares,
+								parseCurlies,
+								parseNumber,
+								parseEnumBodyChars,
+								parseNewKeyword,
+								parseIdentifier,
+								NULL);
+
+		if (parsed)
+		{
+			switch (token->type)
+			{
+				case TOKEN_KEYWORD:
+					switch (token->keyword)
+					{
+						case KEYWORD_new:
+							parsingValue = true;
+							break;
+					}
+					break;
+				case TOKEN_EQUAL_SIGN:
+				case TOKEN_PIPE:
+				case TOKEN_AMPERSAND:
+				case TOKEN_PERIOD:
+					parsingValue = true;
+					break;
+				case TOKEN_COMMA:
+				case TOKEN_SEMICOLON:
+				case TOKEN_STRING:
+				case TOKEN_PARENS:
+				case TOKEN_SQUARES:
+				case TOKEN_CURLIES:
+				case TOKEN_NUMBER:
+					parsingValue = false;
+					break;
+				case TOKEN_IDENTIFIER:
+					if (!parsingValue)
+					{
+						member = newToken ();
+						copyToken (member, token, false);
+						member->scope = scope;
+						emitTag (member, TSTAG_ENUMERATOR);
+						deleteToken (member);
+						member = NULL;
+					}
+					parsingValue = false;
+					break;
+				default:
+					break;
+			}
+		}
+	} while (parsed && ! isType (token, TOKEN_CLOSE_CURLY));
+
+	if (member)
+	{
+		deleteToken (member);
+	}
+}
+
+static void parseEnum (const int scope, tokenInfo *const token)
+{
+	bool parsed;
+
+	do
+	{
+		clearPoolToken (token);
+		parsed = tryInSequence (token, false,
+								parseComment,
+								parseIdentifier,
+								NULL);
+	} while (parsed && token->type != TOKEN_IDENTIFIER);
+
+	if (! parsed) return;
+
+	token->scope = scope;
+	const int nscope = emitTag (token, TSTAG_ENUM);
+
+	parseEnumBody (nscope, token);
+}
+
+MULTI_CHAR_PARSER_DEF (VariableChars, "|&=?[]{}()\n:;,.-+/^<>*",
+		TOKEN_PIPE, TOKEN_AMPERSAND, TOKEN_EQUAL_SIGN, TOKEN_QUESTION_MARK,
+		TOKEN_OPEN_SQUARE, TOKEN_CLOSE_SQUARE, TOKEN_OPEN_CURLY, TOKEN_CLOSE_CURLY,
+		TOKEN_OPEN_PAREN, TOKEN_CLOSE_PAREN, TOKEN_NL, TOKEN_COLON, TOKEN_SEMICOLON,
+		TOKEN_COMMA, TOKEN_PERIOD, TOKEN_MINUS, TOKEN_PLUS,
+		TOKEN_DIV, TOKEN_POWER, TOKEN_LOWER, TOKEN_GREATER, TOKEN_STAR)
+static void parseVariable (bool constVar, bool localVar, const int scope, tokenInfo *const token)
+{
+	tokenInfo *member = NULL;
+	bool parsed = false;
+	bool parsingType = false;
+	bool expectingVariable = false;
+	bool mayBeFun = false;
+	bool isFunction = false;
+	int nestLevel = 0, parenLevel = 0;
+	tsKind varKind = constVar ? TSTAG_CONSTANT : (localVar ? TSTAG_LOCAL : TSTAG_VARIABLE);
+
+	do
+	{
+		clearPoolToken (token);
+		parsed = tryInSequence (token, false,
+								parseTemplate,
+								parseComment,
+								parseStringRegex,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseNumber,
+								parseArrow,
+								parseAsKeyword,
+								parseAwaitKeyword,
+								parseForKeyword,
+								parseFunctionKeyword,
+								parseWhileKeyword,
+								parseThisKeyword,
+								parseEnumKeyword,
+								parseOfKeyword,
+								parseInKeyword,
+								parseNewKeyword,
+								parseTypeofKeyword,
+								parseVariableChars,
+								parseIdentifier,
+								NULL);
+
+		if (parsed)
+		{
+			switch (token->type)
+			{
+				case TOKEN_OPEN_SQUARE:
+				case TOKEN_OPEN_CURLY:
+					nestLevel += 1;
+					break;
+				case TOKEN_OPEN_PAREN:
+					parenLevel += 1;
+					nestLevel += 1;
+					if (mayBeFun) isFunction = true;
+					break;
+				case TOKEN_CLOSE_SQUARE:
+					nestLevel -= 1;
+					break;
+				case TOKEN_CLOSE_PAREN:
+					parenLevel -= 1;
+					nestLevel -= 1;
+					break;
+				case TOKEN_CLOSE_CURLY:
+					nestLevel -= 1;
+					if (nestLevel <= 0) isFunction = false;
+					break;
+				case TOKEN_COMMA:
+					expectingVariable = true;
+				case TOKEN_SEMICOLON:
+				case TOKEN_NUMBER:
+				case TOKEN_STRING:
+					if (nestLevel <= 0)
+					{
+						parsingType = false;
+						nestLevel = 0;
+					}
+					break;
+				case TOKEN_STAR:
+				case TOKEN_MINUS:
+				case TOKEN_PLUS:
+				case TOKEN_DIV:
+				case TOKEN_POWER:
+				case TOKEN_GREATER:
+				case TOKEN_LOWER:
+				case TOKEN_PERIOD:
+				case TOKEN_PIPE:
+				case TOKEN_AMPERSAND:
+				case TOKEN_COLON:
+				case TOKEN_EQUAL_SIGN:
+					parsingType = true;
+					break;
+				case TOKEN_ARROW:
+					isFunction = true;
+					break;
+				case TOKEN_IDENTIFIER:
+					if (parenLevel <= 0 && ! isFunction && ! parsingType)
+					{
+						member = newToken ();
+						copyToken (member, token, false);
+						member->scope = scope;
+						emitTag (member, varKind);
+						deleteToken (member);
+					}
+					expectingVariable = false;
+					parsingType = false;
+					break;
+				case TOKEN_KEYWORD:
+					switch (token->keyword)
+					{
+						case KEYWORD_enum:
+							parseEnum (scope, token);
+							break;
+						case KEYWORD_as:
+						case KEYWORD_of:
+						case KEYWORD_in:
+						case KEYWORD_await:
+						case KEYWORD_new:
+						case KEYWORD_typeof:
+							parsingType = true;
+							break;
+					}
+					break;
+				default:
+					if (nestLevel <= 0) parsingType = false;
+					if (token->type != TOKEN_NL) expectingVariable = false;
+					break;
+			}
+
+			if (isType (token, TOKEN_EQUAL_SIGN) || isKeyword (token, KEYWORD_function)) mayBeFun = true;
+			else if (! isType(token, TOKEN_COMMENT_BLOCK) && nestLevel <= 0) mayBeFun = false;
+		}
+	} while (parsed &&
+			! ((token->type == TOKEN_SEMICOLON
+					|| (token->type == TOKEN_CLOSE_PAREN && ! isFunction)
+					|| (token->type == TOKEN_NL && ! expectingVariable))
+				&& ! parsingType && nestLevel <= 0));
+
+	clearPoolToken (token);
+}
+
+MULTI_CHAR_PARSER_DEF (FunctionArgsChars, "\n(", TOKEN_NL, TOKEN_OPEN_PAREN)
+MULTI_CHAR_PARSER_DEF (FunctionArgsAfterParenChars, "|&=?[]{})\n:,.@",
+		TOKEN_PIPE, TOKEN_AMPERSAND, TOKEN_EQUAL_SIGN, TOKEN_QUESTION_MARK,
+		TOKEN_OPEN_SQUARE, TOKEN_CLOSE_SQUARE, TOKEN_OPEN_CURLY, TOKEN_CLOSE_CURLY,
+		TOKEN_CLOSE_PAREN, TOKEN_NL, TOKEN_COLON, TOKEN_COMMA, TOKEN_PERIOD, TOKEN_AT)
+static void parseFunctionArgs (const int scope, tokenInfo *const token)
+{
+	bool parsed = false;
+	bool parsingType = false;
+	int nestLevel = 0;
+	tokenInfo *member = NULL;
+
+	do
+	{
+		clearPoolToken (token);
+		parsed = tryInSequence (token, false,
+								parseTemplate,
+								parseComment,
+								parseFunctionArgsChars,
+								NULL);
+	} while (parsed && token->type != TOKEN_OPEN_PAREN);
+
+	if (! parsed) return;
+
+	do
+	{
+		clearPoolToken (token);
+		parsed = tryInSequence (token, false,
+								parseTemplate,
+								parseComment,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseParens,
+								parseNumber,
+								parseFunctionArgsAfterParenChars,
+								parseArrow,
+								parseIdentifier,
+								NULL);
+
+		if (parsed)
+		{
+			switch (token->type)
+			{
+				case TOKEN_AT:
+					uwiUngetC ('@');
+					parseDecorator (token);
+					break;
+				case TOKEN_OPEN_SQUARE:
+				case TOKEN_OPEN_CURLY:
+					if (parsingType) nestLevel += 1;
+					break;
+				case TOKEN_CLOSE_SQUARE:
+				case TOKEN_CLOSE_CURLY:
+					if (parsingType) nestLevel -= 1;
+					break;
+				case TOKEN_COMMA:
+					if (nestLevel <= 0)
+					{
+						parsingType = false;
+						nestLevel = 0;
+					}
+					break;
+				case TOKEN_COLON:
+				case TOKEN_EQUAL_SIGN:
+					parsingType = true;
+					break;
+				case TOKEN_IDENTIFIER:
+					if (! parsingType)
+					{
+						member = newToken ();
+						copyToken (member, token, false);
+						member->scope = scope;
+						emitTag (member, TSTAG_PARAMETER);
+						deleteToken (member);
+					}
+					break;
+				default:
+					break;
+			}
+		}
+	} while (parsed && token->type != TOKEN_CLOSE_PAREN);
+}
+
+MULTI_CHAR_PARSER_DEF (FunctionBodyChars, "{}", TOKEN_OPEN_CURLY, TOKEN_CLOSE_CURLY)
+static void parseFunctionBody (const int scope, tokenInfo *const token)
+{
+	bool parsed = false;
+	int nestLevel = 1;
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, true,
+								parseOpenCurly,
+								parseComment,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseTemplate,
+								NULL);
+
+	} while (parsed && ! isType (token, TOKEN_OPEN_CURLY));
+
+	if (! parsed) return;
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, true,
+								parseFunctionBodyChars,
+								parseComment,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseTemplate,
+								parseVarKeyword,
+								parseLetKeyword,
+								parseConstKeyword,
+								NULL);
+
+		if (parsed)
+		{
+			switch (token->type)
+			{
+				case TOKEN_OPEN_CURLY:
+					nestLevel += 1;
+					break;
+				case TOKEN_CLOSE_CURLY:
+					nestLevel -= 1;
+					break;
+				case TOKEN_KEYWORD:
+					switch (token->keyword)
+					{
+						case KEYWORD_var:
+						case KEYWORD_let:
+							parseVariable (false, true, scope, token);
+							break;
+						case KEYWORD_const:
+							parseVariable (true, true, scope, token);
+							break;
+					}
+					break;
+				default:
+					break;
+			}
+		}
+	} while (parsed && ! (isType (token, TOKEN_CLOSE_CURLY) && nestLevel <= 0));
+
+	clearPoolToken (token);
+}
+
+static void parseFunction (const int scope, tokenInfo *const token)
+{
+	bool isGenerator = false;
+	bool parsed;
+
+	do
+	{
+		clearPoolToken (token);
+		parsed = tryInSequence (token, false,
+								parseComment,
+								parseStar,
+								parseIdentifier,
+								NULL);
+
+		if (parsed && isType (token, TOKEN_STAR)) isGenerator = true;
+	} while (parsed && token->type != TOKEN_IDENTIFIER);
+
+	if (! parsed) return;
+
+	token->scope = scope;
+
+	const int nscope = emitTag (token, isGenerator ? TSTAG_GENERATOR : TSTAG_FUNCTION);
+
+	parseFunctionArgs (nscope, token);
+	parseFunctionBody (nscope, token);
+}
+
+MULTI_CHAR_PARSER_DEF (PropertyTypeChars, "\n;|&,)",
+		TOKEN_NL, TOKEN_SEMICOLON, TOKEN_PIPE, TOKEN_AMPERSAND,
+		TOKEN_COMMA, TOKEN_CLOSE_PAREN)
+static void parsePropertyType (tokenInfo *const token)
+{
+	bool parsed = tryParser ((Parser) parseColon, token, true);
+	bool parsedIdentifier = false;
+	bool parseReturnValue = false;
+
+	if (! parsed) return;
+
+	do
+	{
+		clearPoolToken (token);
+
+		if (parsedIdentifier)
+		{
+			parsed = tryInSequence (token, false,
+									parsePropertyTypeChars,
+									parseArrow,
+									parseTemplate,
+									parseParens,
+									parseComment,
+									parseStringSQuote,
+									parseStringDQuote,
+									parseStringTemplate,
+									parseStringRegex,
+									parseSquares,
+									parseCurlies,
+									NULL);
+
+			if (isType (token, TOKEN_PIPE) || isType (token, TOKEN_AMPERSAND)) parsedIdentifier = false;
+		}
+		else
+		{
+			parsed = tryInSequence (token, false,
+									parsePropertyTypeChars,
+									parseArrow,
+									parseTemplate,
+									parseParens,
+									parseComment,
+									parseStringSQuote,
+									parseStringDQuote,
+									parseStringTemplate,
+									parseStringRegex,
+									parseSquares,
+									parseCurlies,
+									parseIdentifier,
+									NULL);
+
+			if (isType (token, TOKEN_IDENTIFIER)) parsedIdentifier = true;
+		}
+
+
+		if (isType (token, TOKEN_ARROW)) parseReturnValue = true;
+	} while (parsed
+			&& ! isType (token, TOKEN_CLOSE_PAREN)
+			&& ! isType (token, TOKEN_SEMICOLON)
+			&& ! isType (token, TOKEN_COMMA)
+			&& ! (parseReturnValue && (
+					isType (token, TOKEN_PARENS)
+					|| isType (token, TOKEN_CURLIES)
+					|| isType (token, TOKEN_SQUARES))));
+
+	if (! parsed) return;
+
+	if (isType (token, TOKEN_CLOSE_PAREN)) uwiUngetC (')');
+
+	clearPoolToken (token);
+}
+
+MULTI_CHAR_PARSER_DEF (ConstructorParamsChars, "\n(", TOKEN_NL, TOKEN_OPEN_PAREN)
+MULTI_CHAR_PARSER_DEF (ConstructorParamsAfterParenChars, "\n:,)@",
+		TOKEN_NL, TOKEN_COLON, TOKEN_COMMA, TOKEN_CLOSE_PAREN, TOKEN_AT)
+static void parseConstructorParams (const int classScope, const int constrScope, tokenInfo *const token)
+{
+	bool parsed = false;
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, false,
+								parseConstructorParamsChars,
+								parseComment,
+								NULL);
+	} while (parsed && ! isType (token, TOKEN_OPEN_PAREN));
+
+	if (! parsed) return;
+
+	tokenInfo *member = NULL;
+	int visibility = 0;
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, false,
+								parseConstructorParamsAfterParenChars,
+								parseComment,
+								parsePrivateKeyword,
+								parseProtectedKeyword,
+								parsePublicKeyword,
+								parseReadonlyKeyword,
+								parseStaticKeyword,
+								parseIdentifier,
+								NULL);
+
+		if (parsed)
+		{
+			switch (token->type)
+			{
+				case TOKEN_AT:
+					uwiUngetC ('@');
+					parseDecorator (token);
+					break;
+				case TOKEN_KEYWORD:
+					switch (token->keyword)
+					{
+						case KEYWORD_private:
+						case KEYWORD_public:
+						case KEYWORD_protected:
+							visibility = token->keyword;
+							break;
+					}
+					break;
+				case TOKEN_COLON:
+					uwiUngetC (':');
+					parsePropertyType (token);
+					break;
+				case TOKEN_IDENTIFIER:
+					member = newToken ();
+					copyToken (member, token, false);
+					if (visibility)
+					{
+						member->accessKeyword = visibility;
+						member->scope = classScope;
+						emitTag (member, TSTAG_PROPERTY);
+					}
+					else
+					{
+						member->scope = constrScope;
+						emitTag (member, TSTAG_PARAMETER);
+					}
+					deleteToken (member);
+					member = NULL;
+					visibility = 0;
+					break;
+				default:
+					break;
+			}
+		}
+	} while (parsed && ! isType (token, TOKEN_CLOSE_PAREN));
+
+}
+
+MULTI_CHAR_PARSER_DEF (ClassBodyChars, "\n,{", TOKEN_NL, TOKEN_COMMA, TOKEN_OPEN_CURLY)
+MULTI_CHAR_PARSER_DEF (ClassBodyAfterCurlyChars, "\n}*@(:;=-+/^<>.,|&",
+		TOKEN_NL, TOKEN_CLOSE_CURLY, TOKEN_STAR, TOKEN_AT, TOKEN_OPEN_PAREN,
+		TOKEN_COLON, TOKEN_SEMICOLON, TOKEN_EQUAL_SIGN,
+		TOKEN_MINUS, TOKEN_PLUS, TOKEN_DIV, TOKEN_POWER,
+		TOKEN_GREATER, TOKEN_LOWER, TOKEN_PERIOD, TOKEN_COMMA,
+		TOKEN_PIPE, TOKEN_AMPERSAND)
+static void parseClassBody (const int scope, tokenInfo *const token)
+{
+	bool parsed = false;
+	vString *inheritance = NULL;
+
+	//parse until {
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, false,
+								parseClassBodyChars,
+								parseTemplate,
+								parseComment,
+								parseExtendsKeyword,
+								parseImplementsKeyword,
+								parseIdentifier,
+								NULL);
+
+		if (token->type == TOKEN_KEYWORD
+			&& (token->keyword == KEYWORD_extends || token->keyword == KEYWORD_implements)
+			&& inheritance == NULL) inheritance = vStringNew ();
+		else if (inheritance && token->type == TOKEN_IDENTIFIER)
+		{
+			if (!vStringIsEmpty (inheritance)) vStringPut(inheritance, ',');
+			vStringCat(inheritance, token->string);
+		}
+	} while (parsed && token->type != TOKEN_OPEN_CURLY);
+
+	if (! parsed)
+	{
+		vStringDelete (inheritance); /* NULL is acceptable. */
+		return;
+	}
+
+	if (inheritance)
+	{
+		tagEntryInfo *klass = getEntryInCorkQueue (scope);
+		if (klass)
+		{
+			klass->extensionFields.inheritance = vStringDeleteUnwrap (inheritance);
+			inheritance = NULL;
+		}
+		vStringDelete (inheritance);
+	}
+
+	tokenInfo *member = NULL;
+	bool isGenerator = false;
+	bool parsingValue = false;
+	int visibility = 0;
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, false,
+								parseComment,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseAsyncKeyword,
+								parseConstructorKeyword,
+								parseNewKeyword,
+								parsePrivateKeyword,
+								parseProtectedKeyword,
+								parsePublicKeyword,
+								parseReadonlyKeyword,
+								parseStaticKeyword,
+								parseNumber,
+								parseTemplate,
+								parseTypeofKeyword,
+								parseClassBodyAfterCurlyChars,
+								parseSquares,
+								parseCurlies,
+								parseIdentifier,
+								NULL);
+
+		if (parsed)
+		{
+			switch (token->type)
+			{
+				case TOKEN_AT:
+					uwiUngetC ('@');
+					parseDecorator (token);
+					break;
+				case TOKEN_KEYWORD:
+					switch (token->keyword)
+					{
+						case KEYWORD_constructor:
+							if (member)
+							{
+								emitTag (member, TSTAG_PROPERTY);
+								deleteToken (member);
+								member = NULL;
+								isGenerator = false;
+							}
+							member = newToken ();
+							copyToken (member, token, false);
+							member->scope = scope;
+
+							if (visibility) member->accessKeyword = visibility;
+							else member->accessKeyword = KEYWORD_public;
+
+							const int nscope = emitTag (member, TSTAG_METHOD);
+							deleteToken (member);
+							member = NULL;
+							visibility = 0;
+
+							parseConstructorParams (scope, nscope, token);
+
+							parseFunctionBody (nscope, token);
+							parsingValue = false;
+							break;
+						case KEYWORD_private:
+						case KEYWORD_public:
+						case KEYWORD_protected:
+							if (member)
+							{
+								emitTag (member, TSTAG_PROPERTY);
+								deleteToken (member);
+								member = NULL;
+							}
+							visibility = token->keyword;
+							parsingValue = false;
+							break;
+						case KEYWORD_new:
+						case KEYWORD_typeof:
+							parsingValue = true;
+							break;
+					}
+					isGenerator = false;
+					break;
+				case TOKEN_EQUAL_SIGN:
+					if (member)
+					{
+						emitTag (member, TSTAG_PROPERTY);
+						deleteToken (member);
+						member = NULL;
+						isGenerator = false;
+					}
+					parsingValue = true;
+					break;
+				case TOKEN_COLON:
+					uwiUngetC (':');
+					parsePropertyType (token);
+				case TOKEN_SEMICOLON:
+					if (member)
+					{
+						emitTag (member, TSTAG_PROPERTY);
+						deleteToken (member);
+						member = NULL;
+						isGenerator = false;
+						visibility = 0;
+					}
+					parsingValue = false;
+					break;
+				case TOKEN_STAR:
+					isGenerator = true;
+				case TOKEN_MINUS:
+				case TOKEN_PLUS:
+				case TOKEN_DIV:
+				case TOKEN_POWER:
+				case TOKEN_GREATER:
+				case TOKEN_LOWER:
+				case TOKEN_PERIOD:
+				case TOKEN_PIPE:
+				case TOKEN_AMPERSAND:
+					parsingValue = true;
+					break;
+				case TOKEN_STRING:
+				case TOKEN_COMMA:
+				case TOKEN_NUMBER:
+				case TOKEN_SQUARES:
+				case TOKEN_CURLIES:
+					parsingValue = false;
+					break;
+				case TOKEN_OPEN_PAREN:
+					if (! member) break;
+					uwiUngetC ('(');
+
+					const int nscope = emitTag (member, isGenerator ? TSTAG_GENERATOR : TSTAG_METHOD);
+
+					deleteToken (member);
+					member = NULL;
+
+					parseFunctionArgs (nscope, token);
+					parseFunctionBody (nscope, token);
+
+					isGenerator = false;
+					visibility = 0;
+					parsingValue = false;
+					break;
+				case TOKEN_IDENTIFIER:
+					if (!parsingValue) {
+						if (member) deleteToken (member);
+						member = newToken ();
+						copyToken (member, token, false);
+						member->scope = scope;
+						if (visibility) member->accessKeyword = visibility;
+						else member->accessKeyword = KEYWORD_public;
+					}
+
+					parsingValue = false;
+					isGenerator = false;
+					break;
+				default:
+					isGenerator = false;
+					visibility = 0;
+					break;
+			}
+		}
+	} while (parsed && token->type != TOKEN_CLOSE_CURLY);
+
+	if (parsed && member)
+	{
+		emitTag (member, TSTAG_PROPERTY);
+		deleteToken (member);
+	}
+	else if (member) deleteToken (member);
+}
+
+static void parseClass (const int scope, tokenInfo *const token)
+{
+	bool parsed = false;
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, false,
+								parseNewLine,
+								parseComment,
+								parseIdentifier,
+								NULL);
+	} while (parsed && token->type != TOKEN_IDENTIFIER);
+
+	if (! parsed) return;
+
+	token->scope = scope;
+	const int nscope = emitTag (token, TSTAG_CLASS);
+
+	parseClassBody (nscope, token);
+}
+
+MULTI_CHAR_PARSER_DEF (NamespaceBodyChars, "\n{", TOKEN_NL, TOKEN_OPEN_CURLY)
+MULTI_CHAR_PARSER_DEF (NamespaceBodyAfterCurlyChars, "@{}()[]",
+		TOKEN_AT, TOKEN_OPEN_CURLY, TOKEN_CLOSE_CURLY, TOKEN_OPEN_PAREN,
+		TOKEN_CLOSE_PAREN, TOKEN_OPEN_SQUARE, TOKEN_CLOSE_SQUARE)
+static void parseNamespaceBody (const int scope, tokenInfo *const token)
+{
+	bool parsed = false;
+
+	//parse until {
+	do
+	{
+		parsed = tryInSequence (token, false,
+								parseNamespaceBodyChars,
+								parseComment,
+								NULL);
+	} while (parsed && token->type != TOKEN_OPEN_CURLY);
+
+	if (! parsed) return;
+
+	int parenLvl = 0;
+	int squareLvl = 0;
+	int curlyLvl = 1;
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, true,
+								parseComment,
+								parseTemplate,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseInterfaceKeyword,
+								parseTypeKeyword,
+								parseEnumKeyword,
+								parseFunctionKeyword,
+								parseClassKeyword,
+								parseVarKeyword,
+								parseLetKeyword,
+								parseConstKeyword,
+								parseNamespaceBodyAfterCurlyChars,
+								NULL);
+
+		switch (token->type)
+		{
+			case TOKEN_KEYWORD:
+				switch (token->keyword)
+				{
+					case KEYWORD_interface:
+						parseInterface (scope, token);
+						break;
+					case KEYWORD_type:
+						parseType (scope, token);
+						break;
+					case KEYWORD_enum:
+						parseEnum (scope, token);
+						break;
+					case KEYWORD_function:
+						parseFunction (scope, token);
+						break;
+					case KEYWORD_class:
+						parseClass (scope, token);
+						break;
+					case KEYWORD_var:
+					case KEYWORD_let:
+						parseVariable (false, false, scope, token);
+						break;
+					case KEYWORD_const:
+						parseVariable (true, false, scope, token);
+						break;
+				}
+				break;
+			case TOKEN_AT:
+				uwiUngetC ('@');
+				parseDecorator (token);
+				break;
+			case TOKEN_OPEN_CURLY:
+				curlyLvl += 1;
+				break;
+			case TOKEN_OPEN_SQUARE:
+				squareLvl += 1;
+				break;
+			case TOKEN_OPEN_PAREN:
+				parenLvl += 1;
+				break;
+			case TOKEN_CLOSE_CURLY:
+				curlyLvl -= 1;
+				break;
+			case TOKEN_CLOSE_SQUARE:
+				squareLvl -= 1;
+				break;
+			case TOKEN_CLOSE_PAREN:
+				parenLvl -= 1;
+				break;
+			default:
+				break;
+		}
+	} while (parsed && ! (isType (token, TOKEN_CLOSE_CURLY) && parenLvl <= 0 && squareLvl <= 0 && curlyLvl <= 0));
+}
+
+static void parseNamespace (tokenInfo *const token)
+{
+	bool parsed = false;
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, false,
+								parseNewLine,
+								parseComment,
+								parseFQIdentifier,
+								NULL);
+	} while (parsed && token->type != TOKEN_IDENTIFIER);
+
+	if (! parsed) return;
+
+	const int scope = emitTag (token, TSTAG_NAMESPACE);
+
+	parseNamespaceBody (scope, token);
+}
+
+static void parseTsFile (tokenInfo *const token)
+{
+	bool parsed;
+
+	do
+	{
+		clearPoolToken (token);
+
+		parsed = tryInSequence (token, true,
+								parseComment,
+								parseTemplate,
+								parseStringSQuote,
+								parseStringDQuote,
+								parseStringTemplate,
+								parseStringRegex,
+								parseInterfaceKeyword,
+								parseTypeKeyword,
+								parseEnumKeyword,
+								parseFunctionKeyword,
+								parseClassKeyword,
+								parseNamespaceKeyword,
+								parseVarKeyword,
+								parseLetKeyword,
+								parseConstKeyword,
+								parseAt,
+								NULL);
+
+		switch (token->type)
+		{
+			case TOKEN_KEYWORD:
+				switch (token->keyword)
+				{
+					case KEYWORD_interface:
+						parseInterface (CORK_NIL, token);
+						break;
+					case KEYWORD_type:
+						parseType (CORK_NIL, token);
+						break;
+					case KEYWORD_enum:
+						parseEnum (CORK_NIL, token);
+						break;
+					case KEYWORD_function:
+						parseFunction (CORK_NIL, token);
+						break;
+					case KEYWORD_class:
+						parseClass (CORK_NIL, token);
+						break;
+					case KEYWORD_namespace:
+						parseNamespace (token);
+						break;
+					case KEYWORD_var:
+					case KEYWORD_let:
+						parseVariable (false, false, CORK_NIL, token);
+						break;
+					case KEYWORD_const:
+						parseVariable (true, false, CORK_NIL, token);
+						break;
+				}
+				break;
+			case TOKEN_AT:
+				uwiUngetC ('@');
+				parseDecorator (token);
+				break;
+			default:
+				break;
+		}
+	} while (parsed);
+}
+
+static void findTsTags (void)
+{
+	uwiActivate (256);
+
+	tokenInfo *const token = newToken ();
+
+	parseTsFile (token);
+
+	deleteToken (token);
+
+	uwiDeactivate (&tsUwiStats);
+}
+
+static void initialize (const langType language)
+{
+	Lang_ts = language;
+
+	TokenPool = objPoolNew (16, newPoolToken, deletePoolToken, clearPoolToken, NULL);
+}
+
+static void finalize (langType language CTAGS_ATTR_UNUSED, bool initialized)
+{
+	if (! initialized) return;
+
+	objPoolDelete (TokenPool);
+}
+
+static void initStats (langType language CTAGS_ATTR_UNUSED)
+{
+	uwiStatsInit (&tsUwiStats);
+}
+static void printStats (langType language CTAGS_ATTR_UNUSED)
+{
+	uwiStatsPrint (&tsUwiStats);
+}
+
+/* Create parser definition structure */
+extern parserDefinition *TypeScriptParser (void)
+{
+	static const char *const extensions [] = { "ts", NULL };
+	parserDefinition *const def = parserNew ("TypeScript");
+	def->extensions = extensions;
+	def->kindTable  = TsKinds;
+	def->kindCount  = ARRAY_SIZE (TsKinds);
+	def->parser     = findTsTags;
+	def->initialize = initialize;
+	def->finalize   = finalize;
+	def->keywordTable = TsKeywordTable;
+	def->keywordCount = ARRAY_SIZE (TsKeywordTable);
+	def->useCork = CORK_QUEUE;
+	def->requestAutomaticFQTag = true;
+
+	def->initStats = initStats;
+	def->printStats = printStats;
+
+	return def;
+}


Modified: data/filedefs/filetypes.TypeScript.conf
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -15,6 +15,7 @@ lexer.cpp.backquoted.strings=1
 # default extension used when saving files
 extension=ts
 lexer_filetype=C
+tag_parser=TypeScript
 
 # MIME type
 mime_type=text/x-typescript


Modified: meson.build
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -674,6 +674,7 @@ ctags = static_library('ctags',
 	'ctags/parsers/tex.c',
 	'ctags/parsers/tex.h',
 	'ctags/parsers/txt2tags.c',
+	'ctags/parsers/typescript.c',
 	'ctags/parsers/verilog.c',
 	'ctags/parsers/vhdl.c',
 	c_args: geany_cflags + [ '-DG_LOG_DOMAIN="CTags"',


Modified: src/tagmanager/tm_parser.c
29 lines changed, 29 insertions(+), 0 deletions(-)
===================================================================
@@ -1001,6 +1001,33 @@ static TMParserMapGroup group_LISP[] = {
 	{_("Constants"), TM_ICON_VAR, tm_tag_field_t},
 };
 
+static TMParserMapEntry map_TYPESCRIPT[] = {
+	{'f', tm_tag_function_t},    // function
+	{'c', tm_tag_class_t},       // class
+	{'i', tm_tag_interface_t},   // interface
+	{'g', tm_tag_enum_t},        // enum
+	{'e', tm_tag_enumerator_t},  // enumerator
+	{'m', tm_tag_method_t},      // method
+	{'n', tm_tag_namespace_t},   // namespace
+	{'z', tm_tag_undef_t},       // parameter
+	{'p', tm_tag_member_t},      // property
+	{'v', tm_tag_variable_t},    // variable
+	{'l', tm_tag_undef_t},       // local
+	{'C', tm_tag_macro_t},       // constant
+	{'G', tm_tag_undef_t},       // generator
+	{'a', tm_tag_undef_t},       // alias
+};
+static TMParserMapGroup group_TYPESCRIPT[] = {
+	{_("Namespaces"), TM_ICON_NAMESPACE, tm_tag_namespace_t},
+	{_("Classes"), TM_ICON_CLASS, tm_tag_class_t},
+	{_("Interfaces"), TM_ICON_STRUCT, tm_tag_interface_t},
+	{_("Functions"), TM_ICON_METHOD, tm_tag_function_t | tm_tag_method_t},
+	{_("Enums"), TM_ICON_STRUCT, tm_tag_enum_t},
+	{_("Variables"), TM_ICON_VAR, tm_tag_variable_t},
+	{_("Constants"), TM_ICON_MACRO, tm_tag_macro_t},
+	{_("Other"), TM_ICON_MEMBER, tm_tag_member_t | tm_tag_enumerator_t},
+};
+
 typedef struct
 {
     TMParserMapEntry *entries;
@@ -1070,6 +1097,7 @@ static TMParserMap parser_map[] = {
 	MAP_ENTRY(TCLOO),
 	MAP_ENTRY(CLOJURE),
 	MAP_ENTRY(LISP),
+	MAP_ENTRY(TYPESCRIPT),
 };
 /* make sure the parser map is consistent and complete */
 G_STATIC_ASSERT(G_N_ELEMENTS(parser_map) == TM_PARSER_COUNT);
@@ -1573,6 +1601,7 @@ gboolean tm_parser_has_full_scope(TMParserType lang)
 		case TM_PARSER_TCL:
 		case TM_PARSER_TCLOO:
 		case TM_PARSER_TXT2TAGS:
+		case TM_PARSER_TYPESCRIPT:
 		case TM_PARSER_VALA:
 		case TM_PARSER_VHDL:
 		case TM_PARSER_VERILOG:


Modified: src/tagmanager/tm_parser.h
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -115,6 +115,7 @@ enum
 	TM_PARSER_TCLOO,
 	TM_PARSER_CLOJURE,
 	TM_PARSER_LISP,
+	TM_PARSER_TYPESCRIPT,
 	TM_PARSER_COUNT
 };
 


Modified: src/tagmanager/tm_parsers.h
3 lines changed, 2 insertions(+), 1 deletions(-)
===================================================================
@@ -70,6 +70,7 @@
 	CPreProParser, \
 	TclOOParser, \
 	ClojureParser, \
-	LispParser
+	LispParser, \
+	TypeScriptParser
 
 #endif


Modified: tests/ctags/Makefile.am
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -312,6 +312,7 @@ test_sources = \
 	simple.rst						\
 	simple.sh						\
 	simple.tcl						\
+	simple.ts						\
 	simple.vala						\
 	simple.zep						\
 	size_t_wchar_t_alias.d			\


Modified: tests/ctags/simple.ts
231 lines changed, 231 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,231 @@
+class CPoint {
+  x: number;
+  y: number;
+  constructor(x: number, y: number) {
+    this.x = x;
+    this.y = y;
+  }
+}
+
+class BankAccount {
+  balance = 0;
+  deposit(credit: number) {
+    this.balance += credit;
+    return this.balance;
+  }
+}
+
+class CheckingAccount extends BankAccount {
+  constructor(balance: number) {
+    super(balance);
+  }
+  writeCheck(debit: number) {
+    this.balance -= debit;
+  }
+}
+
+class List<T extends NamedItem> {
+  next: List<T> = null;
+  constructor(public item: T) {
+  }
+  insertAfter(item: T) {
+    var temp = this.next;
+    this.next = new List(item);
+    this.next.next = temp;
+  }
+  log() {
+    console.log(this.item.name);
+  }
+  // ...
+}
+
+class C {
+  x: number;
+  static x: string;
+}
+
+class Messenger {
+  message = "Hello World";
+  start() {
+    var _this = this;
+    setTimeout(function() { alert(_this.message); }, 3000);
+  }
+};
+
+class D {
+  data: string | string[];
+  getData() {
+    var data = this.data;
+    return typeof data === "string" ? data : data.join(" ");
+  }
+}
+
+class Point {
+  protected fakePointBuilder: () => { x: number, y: number };
+  constructor(public x: number, public y: number) { }
+  public length() { return Math.sqrt(this.x * this.x + this.y * this.y); }
+  static origin = new Point(0, 0);
+}
+
+class A {
+  private x: number;
+  protected y: number;
+  public fun: (a: 22 | 30, b: CPoint) => number | string;
+
+  static f(a: A, b: B) {
+    a.x = 1; // Ok
+    b.x = 1; // Ok
+    a.y = 1; // Ok
+    b.y = 1; // Ok
+  }
+
+  getXAsT<T = any>(): T {
+    return this.x as T;
+  }
+
+  register(...args) {
+    return this.f(...args);
+  }
+
+  longArgsFun(options: {
+    root: string;
+    prefix?: string;
+    setHeaders?: Function;
+    send?: any;
+  }) {
+    return this.f(options);
+  }
+
+  closure(
+    x: number,
+  ): (path: string, callback: Function) => any {
+    const normalizedPath = path === '/*' ? '' : path;
+  }
+}
+
+namespace Validation {
+    export interface StringValidator {
+        isAcceptable(s: string): boolean;
+    }
+
+    const lettersRegexp = /^[A-Za-z]+$/;
+    const numberRegexp = /^[0-9]+$/;
+
+    export class LettersOnlyValidator implements StringValidator {
+        isAcceptable(s: string) {
+            return lettersRegexp.test(s);
+        }
+    }
+
+    export class ZipCodeValidator implements StringValidator {
+        isAcceptable(s: string) {
+            return s.length === 5 && numberRegexp.test(s);
+        }
+    }
+}
+
+// Some samples to try
+let strings = ["Hello", "98052", "101"];
+
+// Validators to use
+let validators: { [s: string]: Validation.StringValidator; } = {};
+validators["ZIP code"] = new Validation.ZipCodeValidator();
+validators["Letters only"] = new Validation.LettersOnlyValidator();
+
+// Show whether each string passed each validator
+for (let s of strings) {
+    for (let name in validators) {
+        console.log(`"${ s }" - ${ validators[name].isAcceptable(s) ? "matches" : "does not match" } ${ name }`);
+    }
+}
+
+interface Mover {
+  move(): void
+  getStatus(): { speed: number }
+}
+
+interface Shaker {
+  shake(): void
+  getStatus(): { frequency: number }
+}
+
+interface MoverShaker extends Mover, Shaker {
+  getStatus(/*)*/): { speed: number; frequency: number }
+  //somethingCommentedOut(): string
+  getSomething(): /* } */ void
+  getSomethingSophisticated()/*
+    comment
+    block
+    */: void
+
+  getTpl<T>(): Promise<T>
+}
+
+interface SimpleRecord {
+  propertyA: number
+  propertyB: string
+  readonly propertyC: []
+  withoutType?
+}
+
+interface Document {
+  createElement(tagName: "div"): HTMLDivElement
+  createElement(tagName: "span"): HTMLSpanElement
+  createElement(tagName: "canvas"): HTMLCanvasElement
+  createElement(tagName: string): HTMLElement
+}
+
+interface CompilerOptions {
+  strict?: boolean
+  sourcePath?: string
+  targetPath?: string
+}
+
+interface List<T> {
+  data: T
+  next: List<T>
+  owner: List<List<T>>
+}
+
+interface JQuery {
+  text(content: string)
+}
+
+interface JQueryStatic {
+  get(url: string, callback: (data: string) => any)
+  (query: string): JQuery
+}
+
+interface Array<T> {
+  length: number
+  [x: number]: T
+  // Other members
+}
+
+enum Color { Red, Green, Blue }
+enum Test {
+  A,
+  B,
+  C = Math.floor(Math.random() * 1000),
+  D = 10,
+  E
+}
+
+export enum Style {
+  None = 0,
+  Bold = 1,
+  Italic = 2,
+  Underline = 4,
+  Emphasis = Bold | Italic,
+  Hyperlink = Bold | Underline
+}
+
+const enum Comparison {
+  LessThan = -1,
+  EqualTo = 0,
+  GreaterThan = 1
+}
+
+function add(x, y) {
+  return x + y;
+}
\ No newline at end of file


Modified: tests/ctags/simple.ts.tags
108 lines changed, 108 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,108 @@
+# format=tagmanager
+A�1�0
+A�4�Test�0
+Array�32�0
+B�4�Test�0
+BankAccount�1�0
+Blue�4�Color�0
+Bold�4�Style�0
+C�1�0
+C�4�Test�0
+CPoint�1�0
+CheckingAccount�1�0
+Color�2�0
+Comparison�2�0
+CompilerOptions�32�0
+D�1�0
+D�4�Test�0
+Document�32�0
+E�4�Test�0
+Emphasis�4�Style�0
+EqualTo�4�Comparison�0
+GreaterThan�4�Comparison�0
+Green�4�Color�0
+Hyperlink�4�Style�0
+Italic�4�Style�0
+JQuery�32�0
+JQueryStatic�32�0
+LessThan�4�Comparison�0
+LettersOnlyValidator�1�Validation�0
+List�1�0
+List�32�0
+Messenger�1�0
+Mover�32�0
+MoverShaker�32�0
+None�4�Style�0
+Point�1�0
+Red�4�Color�0
+Shaker�32�0
+SimpleRecord�32�0
+StringValidator�32�Validation�0
+Style�2�0
+Test�2�0
+Underline�4�Style�0
+Validation�256�0
+ZipCodeValidator�1�Validation�0
+add�16�0
+balance�64�BankAccount�0
+closure�128�A�0
+constructor�128�CPoint�0
+constructor�128�CheckingAccount�0
+constructor�128�List�0
+constructor�128�Point�0
+createElement�128�Document�0
+data�64�D�0
+data�64�List�0
+deposit�128�BankAccount�0
+f�128�A�0
+fakePointBuilder�64�Point�0
+fun�64�A�0
+get�128�JQueryStatic�0
+getData�128�D�0
+getSomething�128�MoverShaker�0
+getSomethingSophisticated�128�MoverShaker�0
+getStatus�128�Mover�0
+getStatus�128�MoverShaker�0
+getStatus�128�Shaker�0
+getTpl�128�MoverShaker�0
+getXAsT�128�A�0
+insertAfter�128�List�0
+isAcceptable�128�Validation.LettersOnlyValidator�0
+isAcceptable�128�Validation.StringValidator�0
+isAcceptable�128�Validation.ZipCodeValidator�0
+item�64�List�0
+length�64�Array�0
+length�128�Point�0
+lettersRegexp�65536�Validation�0
+log�128�List�0
+longArgsFun�128�A�0
+message�64�Messenger�0
+move�128�Mover�0
+name�16384�0
+next�64�List�0
+normalizedPath�65536�A.closure�0
+numberRegexp�65536�Validation�0
+origin�64�Point�0
+owner�64�List�0
+propertyA�64�SimpleRecord�0
+propertyB�64�SimpleRecord�0
+propertyC�64�SimpleRecord�0
+register�128�A�0
+s�16384�0
+shake�128�Shaker�0
+sourcePath�64�CompilerOptions�0
+start�128�Messenger�0
+strict�64�CompilerOptions�0
+strings�16384�0
+targetPath�64�CompilerOptions�0
+text�128�JQuery�0
+validators�16384�0
+withoutType�64�SimpleRecord�0
+writeCheck�128�CheckingAccount�0
+x�64�A�0
+x�64�C�0
+x�64�CPoint�0
+x�64�Point�0
+y�64�A�0
+y�64�CPoint�0
+y�64�Point�0


Modified: tests/meson.build
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -308,6 +308,7 @@ ctags_tests = files([
 	'ctags/simple.rst.tags',
 	'ctags/simple.sh.tags',
 	'ctags/simple.tcl.tags',
+	'ctags/simple.ts.tags',
 	'ctags/simple.vala.tags',
 	'ctags/simple.zep.tags',
 	'ctags/size_t_wchar_t_alias.d.tags',



--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Commits mailing list