001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 the ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package eu.cqse.check.framework.shallowparser.languages.javascript;
018
019import static eu.cqse.check.framework.scanner.ETokenType.AND;
020import static eu.cqse.check.framework.scanner.ETokenType.AS;
021import static eu.cqse.check.framework.scanner.ETokenType.ASYNC;
022import static eu.cqse.check.framework.scanner.ETokenType.CASE;
023import static eu.cqse.check.framework.scanner.ETokenType.CATCH;
024import static eu.cqse.check.framework.scanner.ETokenType.CLASS;
025import static eu.cqse.check.framework.scanner.ETokenType.COLON;
026import static eu.cqse.check.framework.scanner.ETokenType.COMMA;
027import static eu.cqse.check.framework.scanner.ETokenType.CONST;
028import static eu.cqse.check.framework.scanner.ETokenType.CONSTRUCTOR;
029import static eu.cqse.check.framework.scanner.ETokenType.DECLARE;
030import static eu.cqse.check.framework.scanner.ETokenType.DEFAULT;
031import static eu.cqse.check.framework.scanner.ETokenType.DELETE;
032import static eu.cqse.check.framework.scanner.ETokenType.DO;
033import static eu.cqse.check.framework.scanner.ETokenType.DOT;
034import static eu.cqse.check.framework.scanner.ETokenType.DOUBLE_ARROW;
035import static eu.cqse.check.framework.scanner.ETokenType.ELSE;
036import static eu.cqse.check.framework.scanner.ETokenType.ENUM;
037import static eu.cqse.check.framework.scanner.ETokenType.EQ;
038import static eu.cqse.check.framework.scanner.ETokenType.EXPORT;
039import static eu.cqse.check.framework.scanner.ETokenType.EXTENDS;
040import static eu.cqse.check.framework.scanner.ETokenType.FINALLY;
041import static eu.cqse.check.framework.scanner.ETokenType.FOR;
042import static eu.cqse.check.framework.scanner.ETokenType.FROM;
043import static eu.cqse.check.framework.scanner.ETokenType.FUNCTION;
044import static eu.cqse.check.framework.scanner.ETokenType.GET;
045import static eu.cqse.check.framework.scanner.ETokenType.GT;
046import static eu.cqse.check.framework.scanner.ETokenType.GTEQ;
047import static eu.cqse.check.framework.scanner.ETokenType.IDENTIFIER;
048import static eu.cqse.check.framework.scanner.ETokenType.IDENTIFIERS;
049import static eu.cqse.check.framework.scanner.ETokenType.IF;
050import static eu.cqse.check.framework.scanner.ETokenType.IMPLEMENTS;
051import static eu.cqse.check.framework.scanner.ETokenType.IMPORT;
052import static eu.cqse.check.framework.scanner.ETokenType.INSTANCEOF;
053import static eu.cqse.check.framework.scanner.ETokenType.INTERFACE;
054import static eu.cqse.check.framework.scanner.ETokenType.LBRACE;
055import static eu.cqse.check.framework.scanner.ETokenType.LBRACK;
056import static eu.cqse.check.framework.scanner.ETokenType.LET;
057import static eu.cqse.check.framework.scanner.ETokenType.LITERALS;
058import static eu.cqse.check.framework.scanner.ETokenType.LPAREN;
059import static eu.cqse.check.framework.scanner.ETokenType.LT;
060import static eu.cqse.check.framework.scanner.ETokenType.MODULE;
061import static eu.cqse.check.framework.scanner.ETokenType.MULT;
062import static eu.cqse.check.framework.scanner.ETokenType.NAMESPACE;
063import static eu.cqse.check.framework.scanner.ETokenType.NEW;
064import static eu.cqse.check.framework.scanner.ETokenType.OR;
065import static eu.cqse.check.framework.scanner.ETokenType.PRIVATE;
066import static eu.cqse.check.framework.scanner.ETokenType.PROTECTED;
067import static eu.cqse.check.framework.scanner.ETokenType.PROTOTYPE;
068import static eu.cqse.check.framework.scanner.ETokenType.PUBLIC;
069import static eu.cqse.check.framework.scanner.ETokenType.QUESTION;
070import static eu.cqse.check.framework.scanner.ETokenType.RBRACE;
071import static eu.cqse.check.framework.scanner.ETokenType.RBRACK;
072import static eu.cqse.check.framework.scanner.ETokenType.READONLY;
073import static eu.cqse.check.framework.scanner.ETokenType.RPAREN;
074import static eu.cqse.check.framework.scanner.ETokenType.RSHIFTEQ;
075import static eu.cqse.check.framework.scanner.ETokenType.SEMICOLON;
076import static eu.cqse.check.framework.scanner.ETokenType.SET;
077import static eu.cqse.check.framework.scanner.ETokenType.STATIC;
078import static eu.cqse.check.framework.scanner.ETokenType.STRING_LITERAL;
079import static eu.cqse.check.framework.scanner.ETokenType.SWITCH;
080import static eu.cqse.check.framework.scanner.ETokenType.TRY;
081import static eu.cqse.check.framework.scanner.ETokenType.TYPE;
082import static eu.cqse.check.framework.scanner.ETokenType.VAR;
083import static eu.cqse.check.framework.scanner.ETokenType.VOID;
084import static eu.cqse.check.framework.scanner.ETokenType.WHILE;
085import static eu.cqse.check.framework.scanner.ETokenType.WITH;
086import static eu.cqse.check.framework.shallowparser.SubTypeNames.METHOD;
087import static eu.cqse.check.framework.shallowparser.languages.javascript.JavaScriptShallowParser.EJavaScriptParserStates.ANY;
088import static eu.cqse.check.framework.shallowparser.languages.javascript.JavaScriptShallowParser.EJavaScriptParserStates.BASE_JS_EXTEND;
089import static eu.cqse.check.framework.shallowparser.languages.javascript.JavaScriptShallowParser.EJavaScriptParserStates.CLOSURE_CONSTRUCTOR;
090import static eu.cqse.check.framework.shallowparser.languages.javascript.JavaScriptShallowParser.EJavaScriptParserStates.EXT_JS_TYPE;
091import static eu.cqse.check.framework.shallowparser.languages.javascript.JavaScriptShallowParser.EJavaScriptParserStates.IN_LAMBDA;
092import static eu.cqse.check.framework.shallowparser.languages.javascript.JavaScriptShallowParser.EJavaScriptParserStates.IN_TYPE;
093import static eu.cqse.check.framework.shallowparser.languages.javascript.JavaScriptShallowParser.EJavaScriptParserStates.TOP_LEVEL;
094import static eu.cqse.check.framework.util.javascript.TypeScriptDependencyExtractor.TYPESCRIPT_IMPORT_SEPARATOR;
095
096import java.util.EnumSet;
097import java.util.HashSet;
098
099import org.conqat.lib.commons.region.Region;
100
101import eu.cqse.check.framework.scanner.ETokenType;
102import eu.cqse.check.framework.scanner.IToken;
103import eu.cqse.check.framework.shallowparser.SubTypeNames;
104import eu.cqse.check.framework.shallowparser.framework.AllLowercaseIdentifierMatcher;
105import eu.cqse.check.framework.shallowparser.framework.EShallowEntityType;
106import eu.cqse.check.framework.shallowparser.framework.ExactIdentifierMatcher;
107import eu.cqse.check.framework.shallowparser.framework.RecognizerBase;
108import eu.cqse.check.framework.shallowparser.framework.ShallowParserBase;
109import eu.cqse.check.framework.shallowparser.framework.UpperCaseStartIdentifierMatcher;
110import eu.cqse.check.framework.shallowparser.languages.javascript.JavaScriptShallowParser.EJavaScriptParserStates;
111
112/**
113 * Shallow parser for JavaScript. The parser also supports various newer
114 * language features from EcmaScript as well as TypeScript. Additionally,
115 * library specific constructs from multiple libraries (e.g., Google Closure,
116 * Base.js, AngularJS) are handled explicitly.
117 */
118public class JavaScriptShallowParser extends ShallowParserBase<EJavaScriptParserStates> {
119
120        /** The states used in this parser. */
121        public enum EJavaScriptParserStates {
122
123                /** Top-level parsing state. */
124                TOP_LEVEL,
125
126                /** Generic state, as many constructs can occur at any place. */
127                ANY,
128
129                /** Parsing within a type, such as a TypeScript class. */
130                IN_TYPE,
131
132                /** Parses a lambda (arrow notation) construct. */
133                IN_LAMBDA,
134
135                /**
136                 * Special state that is only valid in the constructor of a google closure type.
137                 */
138                CLOSURE_CONSTRUCTOR,
139
140                /** Special state used inside of the base.js extend method */
141                BASE_JS_EXTEND,
142
143                /** Special state used inside of the ext.js types. */
144                EXT_JS_TYPE
145        }
146
147        /**
148         * All possible identifiers, including new keywords, which can be used as
149         * identifiers in older JavaScript files.
150         */
151        private static final EnumSet<ETokenType> ALL_IDENTIFIERS = EnumSet.of(IDENTIFIER, CLASS, IMPLEMENTS, DECLARE, ENUM,
152                        EXPORT, EXTENDS, IMPLEMENTS, IMPORT, INSTANCEOF, INTERFACE, MODULE, NAMESPACE, PRIVATE, PROTECTED, PUBLIC,
153                        STATIC, TYPE, SET, GET, ASYNC, DELETE, IMPORT);
154
155        /** Valid modifiers in TypeScript. */
156        private static final EnumSet<ETokenType> TYPESCRIPT_MODIFIERS = EnumSet.of(STATIC, PUBLIC, PRIVATE, PROTECTED,
157                        READONLY, GET, SET, ASYNC);
158
159        /** Constructor. */
160        public JavaScriptShallowParser() {
161                super(EJavaScriptParserStates.class, EJavaScriptParserStates.TOP_LEVEL);
162
163                createLambdaRules();
164                createMetaRules();
165                createAngularRules();
166                createModuleRules();
167                createBaseJSRules();
168                createClosureRecognizers();
169                createClosureES6Recognizers();
170                createTypeRules();
171                createExtJsTypeSupport();
172                createTypeMemberRules();
173                createFunctionRules();
174                createStatementRules();
175        }
176
177        /** Creates the rules for handling lambda expressions (arrow notation). */
178        private void createLambdaRules() {
179                /*
180                 * Start of a lambda without parentheses. Also need to start in state IN_LAMBDA
181                 * here since JavaScriptFunctionRecognizer starts an IN_LAMBDA parse on an
182                 * "IDENTIFIER DOUBLE_ARROW" sequence.
183                 */
184                completeLambda(inState(IN_LAMBDA, ANY).repeated(IDENTIFIER, DOT).sequence(IDENTIFIER));
185                /* Start of a lambda with parentheses, e.g "(el1, el2) => {...}" */
186                completeLambda(
187                                inState(IN_LAMBDA, ANY).optional(ASYNC).sequence(LPAREN).skipToWithNesting(RPAREN, LPAREN, RPAREN));
188
189                createLambdaBodyRules();
190        }
191
192        /**
193         * Rules for the body of a lambda ("=>" and everything afterwards). Rules are
194         * triggered by completeLambda() of the parent expression. The node start is
195         * moved one token to the right so the shallow entities produced by this rule
196         * don't include the double arrow (instead it will be included in the parent
197         * entity).
198         * 
199         * Creates parsers for the following token patterns (in state
200         * {@link EJavaScriptParserStates#IN_LAMBDA}):
201         * <ul>
202         * <li>=> \<type\>identifier[...]</li>
203         * <li>=> \<type\>identifier(...)</li>
204         * <li>=> \<type\>identifier</li>
205         * </ul>
206         */
207        private void createLambdaBodyRules() {
208                EnumSet<ETokenType> lambdaBodyEndTokens = EnumSet.of(RPAREN, SEMICOLON, RBRACE, RBRACK, COMMA);
209                RecognizerBase<EJavaScriptParserStates> lambdaBodyStart = inState(IN_LAMBDA).sequence(DOUBLE_ARROW)
210                                .optional(LT, IDENTIFIER, GT).sequence(IDENTIFIER).repeated(DOT, IDENTIFIER);
211                // lambda with brackets: => <type>identifier[...]
212                lambdaBodyStart.sequence(LBRACK)
213                                .createNode(EShallowEntityType.STATEMENT, SubTypeNames.LAMBDA_EXPRESSION, null, 1)
214                                .skipToWithNesting(lambdaBodyEndTokens, LBRACK, RBRACK).endNode();
215                // lambda with parentheses: => <type>identifier(...)
216                lambdaBodyStart.sequence(LPAREN)
217                                .createNode(EShallowEntityType.STATEMENT, SubTypeNames.LAMBDA_EXPRESSION, null, 1)
218                                .skipToWithNesting(lambdaBodyEndTokens, LPAREN, RPAREN).endNode();
219                // lambda with simple value: => identifier
220                lambdaBodyStart.createNode(EShallowEntityType.STATEMENT, SubTypeNames.LAMBDA_EXPRESSION, null, 1).endNode();
221
222                inState(IN_LAMBDA).sequence(DOUBLE_ARROW)
223                                .createNode(EShallowEntityType.STATEMENT, SubTypeNames.LAMBDA_EXPRESSION, null, 1)
224                                .skipBeforeWithNesting(lambdaBodyEndTokens, LPAREN, RPAREN).endNode();
225        }
226
227        /**
228         * Completes a rule for parsing lambda expressions. The generated parser matches
229         * on token streams where given <code>ruleStart</code> continues with one of the
230         * following patterns (Notation is BNF where ANY and IN_LAMBDA denote subparsers
231         * with the respective state):
232         * <ul>
233         * <li>=> { ANY }</li>
234         * <li>: ... => IN_LAMBDA</li>
235         * <li>=> IN_LAMBDA</li>
236         * <li>: ... => { ANY }</li>
237         * </ul>
238         * The subparser used for IN_LAMBDA is started before the => token, to allow
239         * precise matching. This is required, as this kind of expression is not
240         * terminated by a semicolon.
241         * 
242         * Lambdas may optionally specify the type of the return value, e.g:
243         * <code>() : any => {}</code>
244         */
245        private static void completeLambda(RecognizerBase<EJavaScriptParserStates> ruleStart) {
246
247                RecognizerBase<EJavaScriptParserStates> lambdaAlternativeWithReturnType = ruleStart.sequence(COLON)
248                                // we don't want to arbitrarily consume tokens until we find a
249                                // double arrow, since we are not yet sure that we are actually
250                                // parsing a lambda. So we skip to either a double arrow (in
251                                // which case this is a lambda) or a token that we know cannot
252                                // occur before the double arrow
253                                .skipBefore(EnumSet.of(DOUBLE_ARROW, SEMICOLON)).sequenceBefore(DOUBLE_ARROW)
254                                .createNode(EShallowEntityType.METHOD, "lambda");
255                lambdaAlternativeWithReturnType.sequence(DOUBLE_ARROW, LBRACE).parseUntil(ANY).sequence(RBRACE).endNode();
256                lambdaAlternativeWithReturnType.parseOnce(IN_LAMBDA).endNode();
257
258                RecognizerBase<EJavaScriptParserStates> lambdaAlternative = ruleStart.sequenceBefore(DOUBLE_ARROW)
259                                .createNode(EShallowEntityType.METHOD, "lambda");
260                lambdaAlternative.sequence(DOUBLE_ARROW, LBRACE).parseUntil(ANY).sequence(RBRACE).endNode();
261                lambdaAlternative.parseOnce(IN_LAMBDA).endNode();
262        }
263
264        /** Creates parsing rules for meta elements. */
265        private void createMetaRules() {
266                // Closure special commands (require, provide, inherits, module)
267                inState(TOP_LEVEL).sequence(identifiers("goog"), DOT, identifiers("provide", "require", "inherits"), LPAREN)
268                                .skipTo(RPAREN).createNode(EShallowEntityType.META, new Region(0, 2), new Region(4, -2))
269                                .optional(SEMICOLON).endNode();
270                inState(TOP_LEVEL).sequence(identifiers("goog"), DOT, MODULE, LPAREN).skipTo(RPAREN)
271                                .createNode(EShallowEntityType.META, new Region(0, 2), new Region(4, -2)).optional(SEMICOLON).endNode();
272
273                // modules style abbreviated goog.require
274                inState(TOP_LEVEL)
275                                .sequence(EnumSet.of(VAR, CONST), IDENTIFIER, EQ, identifiers("goog"), DOT, identifiers("require"),
276                                                LPAREN)
277                                .skipTo(RPAREN).createNode(EShallowEntityType.META, new Region(3, 5), new Region(7, -2))
278                                .optional(SEMICOLON).endNode();
279
280                // Support for namespace.js
281                inState(TOP_LEVEL).sequence(NAMESPACE, LPAREN).createNode(EShallowEntityType.META, SubTypeNames.NAMESPACE, 2)
282                                .skipTo(RPAREN).optional(SEMICOLON).endNode();
283
284                // support for TypeScript imports
285                recognizeTypeScriptImports();
286                recognizeTypeScriptExports();
287
288                // support for TypeScript decorators
289                RecognizerBase<EJavaScriptParserStates> decoratorStart = inAnyState().sequence(ETokenType.AT, IDENTIFIER)
290                                .createNode(EShallowEntityType.META, SubTypeNames.DECORATOR, new Region(0, 1));
291                decoratorStart.sequence(LPAREN).skipToWithNesting(RPAREN, LPAREN, RPAREN).endNode();
292                decoratorStart.endNode();
293
294        }
295
296        /** Recognizes TypeScript Import statements */
297        private void recognizeTypeScriptImports() {
298                RecognizerBase<EJavaScriptParserStates> identifierRecognizer = createRecognizer(
299                                start -> start.sequence(IDENTIFIER).optional(AS, IDENTIFIER));
300                RecognizerBase<EJavaScriptParserStates> multipleImportRecognizer = createRecognizer(
301                                start -> start.optional(COMMA).subRecognizer(identifierRecognizer));
302
303                inState(TOP_LEVEL).sequence(IMPORT, LBRACE).subRecognizer(identifierRecognizer)
304                                .repeatedSubRecognizer(multipleImportRecognizer).sequence(RBRACE, FROM, STRING_LITERAL)
305                                .createNode(EShallowEntityType.META, SubTypeNames.IMPORT,
306                                                new Object[] { new Region(2, 2), TYPESCRIPT_IMPORT_SEPARATOR, new Region(-1, -1) })
307                                .optional(SEMICOLON).endNode();
308
309                // star imports
310                inState(TOP_LEVEL).sequence(IMPORT, MULT).skipTo(FROM).skipTo(STRING_LITERAL)
311                                .createNode(EShallowEntityType.META, SubTypeNames.STAR_IMPORT, -1).optional(SEMICOLON).endNode();
312                // default export import
313                inState(TOP_LEVEL).sequence(IMPORT, IDENTIFIER, FROM, STRING_LITERAL)
314                                .createNode(EShallowEntityType.META, SubTypeNames.DEFAULT_EXPORT_IMPORT, -1).optional(SEMICOLON)
315                                .endNode();
316                // triple slash directive import
317                inState(TOP_LEVEL).sequence(ETokenType.TRIPLE_SLASH_DIRECTIVE)
318                                .createNode(EShallowEntityType.META, SubTypeNames.TRIPLE_SLASH_DIRECTIVE_IMPORT, 0).endNode();
319                // exported object import
320                inState(TOP_LEVEL).sequence(IMPORT, IDENTIFIER, EQ, identifiers("require"), LPAREN, STRING_LITERAL)
321                                .createNode(EShallowEntityType.META, SubTypeNames.OBJECT_EXPORT_IMPORT, -1).sequence(RPAREN)
322                                .optional(SEMICOLON).endNode();
323                // abbreviations
324                inState(TOP_LEVEL).sequence(IMPORT, IDENTIFIER, EQ).markStart().repeated(EnumSet.of(IDENTIFIER, DOT))
325                                .createNode(EShallowEntityType.META, SubTypeNames.IMPORT, new Region(0, -1)).optional(SEMICOLON)
326                                .endNode();
327                // default import from framework
328                inState(TOP_LEVEL).sequence(IMPORT, STRING_LITERAL)
329                                .createNode(EShallowEntityType.META, SubTypeNames.DEFAULT_EXPORT_IMPORT, -1).optional(SEMICOLON)
330                                .endNode();
331        }
332
333        /** Recognizes TypeScript Export statements */
334        private void recognizeTypeScriptExports() {
335                inState(TOP_LEVEL).sequence(EXPORT, LBRACE).optional(IDENTIFIER, AS).sequence(IDENTIFIER, RBRACE)
336                                .createNode(EShallowEntityType.META, SubTypeNames.EXPORT, -2).optional(SEMICOLON).endNode();
337                inState(TOP_LEVEL).sequence(EXPORT, ETokenType.EQ, IDENTIFIER)
338                                .createNode(EShallowEntityType.META, SubTypeNames.OBJECT_EXPORT, -1).optional(SEMICOLON).endNode();
339        }
340
341        /**
342         * Returns a matcher for {@link ETokenType#IDENTIFIER} tokens with given texts.
343         */
344        private static ExactIdentifierMatcher identifiers(String... names) {
345                return new ExactIdentifierMatcher(names);
346        }
347
348        /**
349         * Creates type rules specific to the Base.JS Framework
350         * http://dean.edwards.name/weblog/2006/03/base/
351         */
352        private void createBaseJSRules() {
353                RecognizerBase<EJavaScriptParserStates> baseJSTypeRecognizer = inState(TOP_LEVEL).repeated(ALL_IDENTIFIERS, DOT)
354                                .markStart().sequence(new UpperCaseStartIdentifierMatcher(), EQ)
355                                .repeated(new AllLowercaseIdentifierMatcher(), DOT)
356                                .sequence(new UpperCaseStartIdentifierMatcher(), DOT, new ExactIdentifierMatcher("extend"), LPAREN,
357                                                LBRACE)
358                                .createNode(EShallowEntityType.TYPE, SubTypeNames.CLASS, 0).parseUntil(BASE_JS_EXTEND).sequence(RBRACE);
359
360                baseJSTypeRecognizer.sequence(RPAREN).optional(SEMICOLON).endNode();
361
362                baseJSTypeRecognizer.sequence(COMMA, LBRACE).parseUntil(BASE_JS_EXTEND).sequence(RBRACE, RPAREN)
363                                .optional(SEMICOLON).endNode();
364        }
365
366        /** Creates recognizers for the google closure framework */
367        private void createClosureRecognizers() {
368                // Google closure constructor, displayed as method here, as that is what
369                // it technically is
370                inState(TOP_LEVEL).repeated(ALL_IDENTIFIERS, DOT).markStart()
371                                .sequence(new UpperCaseStartIdentifierMatcher(), EQ, FUNCTION)
372                                .createNode(EShallowEntityType.METHOD, SubTypeNames.CONSTRUCTOR, new Region(0, -3)).skipTo(LBRACE)
373                                .parseUntil(CLOSURE_CONSTRUCTOR).sequence(RBRACE).optional(SEMICOLON).endNode();
374
375                RecognizerBase<EJavaScriptParserStates> attributeBeginning = inState(CLOSURE_CONSTRUCTOR)
376                                .sequence(ETokenType.THIS, DOT, ALL_IDENTIFIERS);
377                attributeBeginning.sequenceBefore(EnumSet.of(ETokenType.THIS, RBRACE, SEMICOLON)).optional(SEMICOLON)
378                                .createNode(EShallowEntityType.ATTRIBUTE, SubTypeNames.ATTRIBUTE, new Region(0, 2)).endNode();
379
380                attributeBeginning.sequence(EQ, FUNCTION)
381                                .createNode(EShallowEntityType.METHOD, SubTypeNames.METHOD, new Region(0, 2)).skipTo(LBRACE)
382                                .parseUntil(ANY).sequence(RBRACE).optional(SEMICOLON).endNode();
383
384                RecognizerBase<EJavaScriptParserStates> attributeAssignment = attributeBeginning.sequence(EQ)
385                                .createNode(EShallowEntityType.ATTRIBUTE, SubTypeNames.ATTRIBUTE, new Region(0, 2));
386                attributeAssignment.sequenceBefore(LBRACE).skipToWithNesting(SEMICOLON, LBRACE, RBRACE).endNode();
387                attributeAssignment.skipBeforeWithNesting(EnumSet.of(SEMICOLON, RBRACE, ETokenType.THIS), LPAREN, RPAREN)
388                                .optional(SEMICOLON).endNode();
389        }
390
391        /** Creates recognizers for the google closure framework at ES6 level */
392        private void createClosureES6Recognizers() {
393                inState(TOP_LEVEL).markStart().repeated(ALL_IDENTIFIERS, DOT)
394                                .sequence(new UpperCaseStartIdentifierMatcher(), EQ, CLASS)
395                                .createNode(EShallowEntityType.TYPE, SubTypeNames.CLASS, new Region(0, -3)).skipTo(LBRACE)
396                                .parseUntil(IN_TYPE).sequence(RBRACE).optional(SEMICOLON).endNode();
397        }
398
399        /** Creates parsing rules specifically for AngularJS. */
400        private void createAngularRules() {
401                // angular modules
402                inAnyState().optional(VAR, ALL_IDENTIFIERS, EQ).sequence(identifiers("angular"), DOT, MODULE, LPAREN)
403                                .createNode(EShallowEntityType.META, "AngularJS module declaration")
404                                .skipToWithNesting(RPAREN, LPAREN, RPAREN).optional(SEMICOLON).endNode();
405
406                // angular methods in the format: method(string, function)
407                // the function could also be a string referencing a function or a new instance
408                RecognizerBase<EJavaScriptParserStates> angularMethodDeclarationStart = inAnyState().optional(ALL_IDENTIFIERS)
409                                .markStart().sequence(DOT, identifiers("controller", "filter", "factory", "directive", "provider",
410                                                "service", "decorator", "animation", "value", "component", "constant"), LPAREN);
411                // declaration of a service where we do know the name (string literal)
412                // {@code angular.module("CompanyX.Y").service("ServiceName", DetailService);}
413                createAngularFunctionRule(angularMethodDeclarationStart.sequence(STRING_LITERAL, COMMA), true);
414                // declaration of a service where we do not know the name (reference)
415                // {@code angular.module("CompanyX.Y").service(some.variable, DetailService);}
416                createAngularFunctionRule(angularMethodDeclarationStart.repeated(IDENTIFIER, DOT).sequence(IDENTIFIER, COMMA),
417                                false);
418
419                // angular methods in the format: method(function)
420                RecognizerBase<EJavaScriptParserStates> singleParameterMethodRecognizer = inAnyState().optional(ALL_IDENTIFIERS)
421                                .markStart().sequence(DOT, identifiers("config", "run"), LPAREN);
422                createAngularFunctionRule(singleParameterMethodRecognizer, false);
423
424                // angular methods in the format: method(string, *)
425                RecognizerBase<EJavaScriptParserStates> angularConstantRecognizer = inAnyState().optional(ALL_IDENTIFIERS)
426                                .markStart().sequence(DOT, identifiers("value", "component", "constant"), LPAREN, STRING_LITERAL, COMMA)
427                                .createNode(EShallowEntityType.METHOD, new Object[] { "AngularJS", 1 }, 3);
428                angularConstantRecognizer.parseUntil(ANY).sequence(RPAREN).optional(SEMICOLON).endNode();
429        }
430
431        /**
432         * Creates the Angular rules for functions. Given the recognizer for the method
433         * declaration, it handles the case where the function parameter is a variable
434         * referencing a function (e.g. .filter('methodName', getName)), as well as the
435         * case where the function parameter is initialized (e.g. .filter('methodName',
436         * function() {....}))
437         *
438         * @param angularMethodRecognizer
439         *            the angular method recognizer
440         * @param includeFunctionName
441         *            whether the parser should list the function's name or not
442         */
443        private static void createAngularFunctionRule(RecognizerBase<EJavaScriptParserStates> angularMethodRecognizer,
444                        boolean includeFunctionName) {
445                createAngularMethodNode(angularMethodRecognizer.optional(NEW).repeated(IDENTIFIER, DOT)
446                                .sequence(EnumSet.of(STRING_LITERAL, IDENTIFIER)).skipToWithNesting(RPAREN, LPAREN, RPAREN)
447                                .optional(SEMICOLON), includeFunctionName).endNode();
448
449                angularMethodRecognizer = angularMethodRecognizer.optional(LBRACK).repeated(STRING_LITERAL, COMMA)
450                                .sequence(FUNCTION);
451                createAngularMethodNode(angularMethodRecognizer, includeFunctionName).skipTo(LBRACE).parseUntil(ANY)
452                                .sequence(RBRACE).optional(RBRACK).sequence(RPAREN).optional(SEMICOLON).endNode();
453        }
454
455        /**
456         * Creates the node for an Angular method and depending on the given boolean, it
457         * is decided whether the parser includes the function name or not.
458         */
459        private static RecognizerBase<EJavaScriptParserStates> createAngularMethodNode(
460                        RecognizerBase<EJavaScriptParserStates> angularMethodRecognizer, boolean includeFunctionName) {
461                if (includeFunctionName) {
462                        return angularMethodRecognizer.createNode(EShallowEntityType.METHOD, new Object[] { "AngularJS", 1 }, 3);
463                }
464                return angularMethodRecognizer.createNode(EShallowEntityType.METHOD, new Object[] { "AngularJS", 1 });
465        }
466
467        /**
468         * Creates rules for parsing namespace/module constructs. These are used to
469         * handle those constructs provided by well-known libraries, as JavaScript
470         * itself does not provide a direct namespace concept.
471         */
472        private void createModuleRules() {
473                // YUI library, see http://yuilibrary.com/yui/docs/yui/
474                completeModuleRecognizer(inAnyState().ensureTopLevel().subRecognizer(new YuiCallRecognizer("add"), 1, 1)
475                                .skipTo(STRING_LITERAL).createNode(EShallowEntityType.MODULE, "YUI module", -1));
476                completeModuleRecognizer(inAnyState().ensureTopLevel().subRecognizer(new YuiCallRecognizer("use"), 1, 1)
477                                .createNode(EShallowEntityType.MODULE, "YUI code", -1));
478
479                // TypeScript namespace
480                inState(TOP_LEVEL).repeated(EnumSet.of(DECLARE, EXPORT)).markStart().sequence(EnumSet.of(NAMESPACE, MODULE))
481                                .sequence(IDENTIFIER).repeated(DOT, IDENTIFIER).sequence(LBRACE)
482                                .createNode(EShallowEntityType.MODULE, 0, new Region(1, -2)).parseUntil(TOP_LEVEL).sequence(RBRACE)
483                                .endNode();
484        }
485
486        /** Completes a recognizer for modules. */
487        private static void completeModuleRecognizer(RecognizerBase<EJavaScriptParserStates> recognizer) {
488                recognizer.skipTo(FUNCTION).skipTo(LBRACE).parseUntil(ANY).sequence(RBRACE)
489                                .skipToWithNesting(RPAREN, LPAREN, RPAREN).optional(SEMICOLON).endNode();
490        }
491
492        /**
493         * Creates rules for TypeScript types. See
494         * https://www.typescriptlang.org/docs/handbook/advanced-types.html for type
495         * alias definitions
496         */
497        private void createTypeRules() {
498                RecognizerBase<EJavaScriptParserStates> typeRecognizer = inAnyState()
499                                .repeated(EnumSet.of(DECLARE, EXPORT, DEFAULT)).markStart();
500                createLambdaArrayAndGenericRulesForTypeAlias(
501                                typeRecognizer.sequence(TYPE, IDENTIFIER).skipTo(EnumSet.of(EQ, GTEQ, RSHIFTEQ)));
502
503                typeRecognizer.sequence(EnumSet.of(CLASS, INTERFACE), IDENTIFIER).createNode(EShallowEntityType.TYPE, 0, 1)
504                                .skipTo(LBRACE).parseUntil(IN_TYPE).sequence(RBRACE).endNode();
505
506                // enums
507                inAnyState().repeated(EnumSet.of(DECLARE, EXPORT, CONST)).markStart().sequence(ENUM, IDENTIFIER, LBRACE)
508                                .createNode(EShallowEntityType.TYPE, 0, 1).skipToWithNesting(RBRACE, ETokenType.LBRACE, RBRACE)
509                                .endNode();
510        }
511
512        /**
513         * Creates rule for matching lambda, array and generic expressions in type alias
514         * definitions. Also matches union and intersection expressions that combine
515         * basic type value expressions. Examples of code the rules will match: a)
516         * Lambda. <code>type Callback = () => void;</code> b) Array.
517         * <code>type c = [0, null, null, null];</code> c) Generics.
518         * <code>type Container<T> = { value: T }</code> d) Others.
519         * <code>type f = [a, b] | [c, d] & [e, g]</code>
520         */
521        private void createLambdaArrayAndGenericRulesForTypeAlias(
522                        RecognizerBase<EJavaScriptParserStates> typeAliasRecognizer) {
523                RecognizerBase<EJavaScriptParserStates> typeAliasLambdaOrArrayRecognizer = typeAliasRecognizer.sequence(LPAREN)
524                                .skipToWithNesting(RPAREN, LPAREN, RPAREN);
525                typeAliasLambdaOrArrayRecognizer.sequence(DOUBLE_ARROW, EnumSet.of(IDENTIFIER, VOID)).optional(SEMICOLON)
526                                .createNode(EShallowEntityType.TYPE, 0, 1).endNode();
527                typeAliasLambdaOrArrayRecognizer.sequence(LBRACK, RBRACK).optional(SEMICOLON)
528                                .createNode(EShallowEntityType.TYPE, 0, 1).endNode();
529                typeAliasRecognizer.sequence(LBRACE).skipToWithNesting(RBRACE, LBRACE, RBRACE).optional(SEMICOLON)
530                                .createNode(EShallowEntityType.TYPE, 0, 1).endNode();
531
532                RecognizerBase<EJavaScriptParserStates> typeNameRecognizer = createRecognizer(
533                                start -> start.sequence(LBRACK).skipToWithNesting(RBRACK, LBRACK, RBRACK));
534                typeNameRecognizer.sequence(LBRACE).skipToWithNesting(RBRACE, LBRACE, RBRACE);
535                typeNameRecognizer.sequence(LT, IDENTIFIER).repeated(OR, IDENTIFIER).sequence(GT);
536                typeNameRecognizer.optional(DOT).sequence(IDENTIFIER);
537
538                RecognizerBase<EJavaScriptParserStates> typeNameUnionAndIntersectionRecognizer = createRecognizer(
539                                start -> start.sequence(EnumSet.of(OR, AND)).subRecognizer(typeNameRecognizer));
540
541                typeAliasRecognizer.optional(IDENTIFIER).optionalSubRecognizer(typeNameRecognizer)
542                                .repeatedSubRecognizer(typeNameUnionAndIntersectionRecognizer).optional(SEMICOLON)
543                                .createNode(EShallowEntityType.TYPE, 0, 1).endNode();
544        }
545
546        /** Creates support for ext.js library. */
547        private void createExtJsTypeSupport() {
548                // ext.js classes
549                inState(TOP_LEVEL).sequence(identifiers("Ext"), DOT, identifiers("define"), LPAREN, STRING_LITERAL)
550                                .createNode(EShallowEntityType.TYPE, SubTypeNames.CLASS, 4).skipTo(LBRACE).parseUntil(EXT_JS_TYPE)
551                                .sequence(RBRACE).skipToWithNesting(RPAREN, LPAREN, RPAREN).optional(SEMICOLON).endNode();
552
553                inState(EXT_JS_TYPE).sequence(identifiers("config"), COLON, LBRACE)
554                                .createNode(EShallowEntityType.ATTRIBUTE, "config section").skipToWithNesting(RBRACE, LBRACE, RBRACE)
555                                .optional(COMMA).endNode();
556
557                inState(EXT_JS_TYPE).sequence(CONSTRUCTOR, COLON, FUNCTION)
558                                .createNode(EShallowEntityType.METHOD, SubTypeNames.CONSTRUCTOR).skipTo(LBRACE).parseUntil(ANY)
559                                .sequence(RBRACE).optional(COMMA).endNode();
560
561                inState(EXT_JS_TYPE).sequence(identifiers("statics"), COLON, LBRACE)
562                                .createNode(EShallowEntityType.META, SubTypeNames.STATICS).parseUntil(EXT_JS_TYPE).sequence(RBRACE)
563                                .optional(COMMA).endNode();
564
565                inState(EXT_JS_TYPE).sequence(extendAllIdentifiers(STRING_LITERAL), COLON, LITERALS).optional(COMMA)
566                                .createNode(EShallowEntityType.ATTRIBUTE, SubTypeNames.ATTRIBUTE, 0).endNode();
567                inState(EXT_JS_TYPE).sequence(extendAllIdentifiers(STRING_LITERAL), COLON, LBRACE)
568                                .skipToWithNesting(RBRACE, LBRACE, RBRACE).optional(COMMA)
569                                .createNode(EShallowEntityType.ATTRIBUTE, SubTypeNames.ATTRIBUTE, 0).endNode();
570        }
571
572        /** Creates rules for TypeScript type members. */
573        private void createTypeMemberRules() {
574                // constructor
575                RecognizerBase<EJavaScriptParserStates> constructorRecognizer = inState(IN_TYPE).repeated(TYPESCRIPT_MODIFIERS)
576                                .markStart().sequence(CONSTRUCTOR).skipNested(ETokenType.LPAREN, RPAREN)
577                                .createNode(EShallowEntityType.METHOD, SubTypeNames.CONSTRUCTOR, 0);
578                constructorRecognizer.sequence(LBRACE).parseUntil(ANY).sequence(RBRACE).endNode();
579                constructorRecognizer.optional(SEMICOLON).endNode();
580
581                createTypeMemberMethodRules();
582
583                // attributes
584                // the semicolon at the end of attribute declarations is optional, so we
585                // have to use a simple statement recognizer to find the correct end of
586                // the current statement
587                inState(IN_TYPE).repeated(TYPESCRIPT_MODIFIERS).markStart()
588                                .sequenceBefore(IDENTIFIER, EnumSet.of(EQ, COLON, QUESTION))
589                                .subRecognizer(new JavaScriptSimpleStatementRecognizer(EShallowEntityType.ATTRIBUTE,
590                                                SubTypeNames.ATTRIBUTE, true), 1, 1)
591                                .endNode();
592        }
593
594        /**
595         * Create rules for type member methods. We handle parsing of synchronous and
596         * asynchronous methods that turn classes into iterators. See
597         * <code>https://dev.to/nestedsoftware/the-iterators-are-coming-symboliterator-and-symbolasynciterator-in-javascript-hj</code>
598         */
599        private void createTypeMemberMethodRules() {
600                RecognizerBase<EJavaScriptParserStates> genericDefinitionRecognizer = createRecognizer(
601                                start -> start.sequence(LT).skipToWithNesting(GT, LT, GT));
602                RecognizerBase<EJavaScriptParserStates> methodHeadRecognizer = createRecognizer(
603                                start -> start.optionalSubRecognizer(genericDefinitionRecognizer).sequence(LPAREN)
604                                                .skipToWithNesting(RPAREN, LPAREN, RPAREN));
605
606                EnumSet<ETokenType> identifiersWithCatchAndFinally = EnumSet.copyOf(ALL_IDENTIFIERS);
607                identifiersWithCatchAndFinally.add(CATCH);
608                identifiersWithCatchAndFinally.add(FINALLY);
609                EnumSet<ETokenType> modifiersWithoutGetAndSet = EnumSet.copyOf(TYPESCRIPT_MODIFIERS);
610                modifiersWithoutGetAndSet.remove(GET);
611                modifiersWithoutGetAndSet.remove(SET);
612                RecognizerBase<EJavaScriptParserStates> methodAlternative = inState(IN_TYPE)
613                                .repeated(modifiersWithoutGetAndSet);
614                RecognizerBase<EJavaScriptParserStates> methodWithUserDefinedNameAlternative = methodAlternative
615                                .preCondition(new MethodNameIsUserDefinedRecognizer()).optional(EnumSet.of(GET, SET)).markStart()
616                                .repeatedAtLeastOnce(identifiersWithCatchAndFinally).subRecognizer(methodHeadRecognizer);
617                RecognizerBase<EJavaScriptParserStates> methodWithSpecialIdentifierAsNameAlternative = methodAlternative
618                                .markStart().sequence(identifiersWithCatchAndFinally).subRecognizer(methodHeadRecognizer);
619                // Synchronous and asynchronous methods that turn classes into iterators.
620                RecognizerBase<EJavaScriptParserStates> methodAlternativeBrackedNamed = methodAlternative.optional(MULT)
621                                .sequence(LBRACK, IDENTIFIER, DOT).markStart().sequence(IDENTIFIERS).sequence(RBRACK)
622                                .subRecognizer(methodHeadRecognizer);
623
624                completeRuleForMethodRecognizer(methodWithUserDefinedNameAlternative);
625                completeRuleForMethodRecognizer(methodWithSpecialIdentifierAsNameAlternative);
626                completeRuleForMethodRecognizer(methodAlternativeBrackedNamed);
627        }
628
629        /**
630         * Completes rule for a method recognizer by matching declared return type and
631         * method body definitions.
632         */
633        private static void completeRuleForMethodRecognizer(RecognizerBase<EJavaScriptParserStates> methodAlternative) {
634                RecognizerBase<EJavaScriptParserStates> methodWithDeclaredReturnTypeAlternative = methodAlternative
635                                .sequence(COLON, IDENTIFIER).repeated(DOT, IDENTIFIER).createNode(EShallowEntityType.METHOD, METHOD, 0);
636
637                completeRuleForMethodsWithDeclaredReturnType(
638                                methodWithDeclaredReturnTypeAlternative.skipNested(LT, GT).skipNested(LBRACK, RBRACK));
639                completeRuleForMethodsWithDeclaredReturnType(methodWithDeclaredReturnTypeAlternative.sequence(LBRACK, RBRACK));
640                completeRuleForMethodsWithDeclaredReturnType(methodWithDeclaredReturnTypeAlternative);
641
642                methodWithDeclaredReturnTypeAlternative.optional(SEMICOLON).endNode();
643
644                RecognizerBase<EJavaScriptParserStates> methodNoReturnTypeAlternative = methodAlternative
645                                .skipBefore(EnumSet.of(LBRACE, SEMICOLON));
646                methodNoReturnTypeAlternative.sequence(SEMICOLON)
647                                .createNode(EShallowEntityType.METHOD, SubTypeNames.METHOD_DECLARATION, 0).endNode();
648                methodNoReturnTypeAlternative.sequence(LBRACE).createNode(EShallowEntityType.METHOD, METHOD, 0).parseUntil(ANY)
649                                .sequence(RBRACE).endNode();
650        }
651
652        /**
653         * Completes rule for matching methods with declared return types.
654         */
655        private static void completeRuleForMethodsWithDeclaredReturnType(
656                        RecognizerBase<EJavaScriptParserStates> recognizer) {
657                recognizer.sequence(LBRACE).parseUntil(ANY).sequence(RBRACE).endNode();
658                recognizer.optional(SEMICOLON).endNode();
659        }
660
661        /** Creates parsing rules for functions. */
662        private void createFunctionRules() {
663                createAssignedFunctionRule();
664                createNamedFunctionRule();
665                createAttributeFunctionRule();
666                createAnonymousFunctionRule();
667        }
668
669        /** Creates parsing rule for assigned functions. */
670        private void createAssignedFunctionRule() {
671                inAnyState().repeated(EnumSet.of(DECLARE, EXPORT)).optional(EnumSet.of(VAR, CONST, LET))
672                                .repeated(extendAllIdentifiers(PROTOTYPE), DOT).markStart().sequence(ALL_IDENTIFIERS)
673                                .sequence(EQ, FUNCTION).createNode(EShallowEntityType.METHOD, SubTypeNames.ASSIGNED_FUNCTION, 0)
674                                .skipNested(LPAREN, RPAREN).skipTo(LBRACE).parseUntil(ANY).sequence(RBRACE).optional(SEMICOLON)
675                                .endNode();
676        }
677
678        /** Creates parsing rule for named functions. */
679        private void createNamedFunctionRule() {
680                RecognizerBase<EJavaScriptParserStates> namedFunctionAlternative = inAnyState()
681                                .repeated(EnumSet.of(DECLARE, EXPORT)).optional(ASYNC).markStart().sequence(FUNCTION, ALL_IDENTIFIERS)
682                                .skipNested(LT, GT).skipNested(LPAREN, RPAREN).skipBefore(EnumSet.of(LBRACE, SEMICOLON));
683                namedFunctionAlternative.sequence(SEMICOLON)
684                                .createNode(EShallowEntityType.METHOD, SubTypeNames.FUNCTION_DECLARATION, 1).endNode();
685                namedFunctionAlternative.sequence(LBRACE).createNode(EShallowEntityType.METHOD, SubTypeNames.NAMED_FUNCTION, 1)
686                                .parseUntil(ANY).sequence(RBRACE).endNode();
687        }
688
689        /** Creates parsing rule for attribute functions. */
690        private void createAttributeFunctionRule() {
691                inAnyState().sequence(extendAllIdentifiers(STRING_LITERAL), COLON, FUNCTION)
692                                .createNode(EShallowEntityType.METHOD, SubTypeNames.ATTRIBUTE_FUNCTION, 0).skipNested(LPAREN, RPAREN)
693                                .skipTo(LBRACE).parseUntil(ANY).sequence(RBRACE).optional(COMMA).endNode();
694
695                inAnyState().optional(EnumSet.of(STATIC, ASYNC)).markStart()
696                                .sequence(extendAllIdentifiers(STRING_LITERAL), LPAREN).skipToWithNesting(RPAREN, LPAREN, RPAREN)
697                                .sequence(LBRACE).createNode(EShallowEntityType.METHOD, SubTypeNames.ATTRIBUTE_FUNCTION, 0)
698                                .parseUntil(ANY).sequence(RBRACE).optional(COMMA).endNode();
699        }
700
701        /** Creates parsing rule for anonymous functions. */
702        private void createAnonymousFunctionRule() {
703                inAnyState().sequence(FUNCTION).createNode(EShallowEntityType.METHOD, SubTypeNames.ANONYMOUS_FUNCTION)
704                                .skipNested(LPAREN, RPAREN).skipTo(LBRACE).parseUntil(ANY).sequence(RBRACE).endNode();
705        }
706
707        /**
708         * Returns a copy of {@link #ALL_IDENTIFIERS} extended by the given type.
709         */
710        private static EnumSet<ETokenType> extendAllIdentifiers(ETokenType extension) {
711                EnumSet<ETokenType> result = EnumSet.copyOf(ALL_IDENTIFIERS);
712                result.add(extension);
713                return result;
714        }
715
716        /** Creates parsing rules for statements. */
717        private void createStatementRules() {
718
719                // empty statement
720                inAnyState().sequence(SEMICOLON).createNode(EShallowEntityType.STATEMENT, SubTypeNames.EMPTY_STATEMENT)
721                                .endNode();
722
723                // Attributes
724                RecognizerBase<EJavaScriptParserStates> baseJsAttribute = inState(BASE_JS_EXTEND)
725                                .sequence(ALL_IDENTIFIERS, COLON).createNode(EShallowEntityType.ATTRIBUTE, SubTypeNames.ATTRIBUTE, 0);
726                baseJsAttribute.skipToWithNesting(COMMA, LBRACE, RBRACE).endNode();
727                // Might be the last one in the constructor
728                baseJsAttribute.skipBeforeWithNesting(RBRACE, LBRACE, RBRACE).endNode();
729
730                // filter out labels as meta as they do not increase statement count
731                inAnyState().sequence(ALL_IDENTIFIERS, COLON).createNode(EShallowEntityType.META, SubTypeNames.LABEL, 0)
732                                .endNode();
733
734                createElseIfRule();
735                createSimpleBlockRules();
736                createSwitchCaseRules();
737                createDoWhileRules();
738
739                // anonymous block
740                inAnyState().sequence(LBRACE).createNode(EShallowEntityType.STATEMENT, SubTypeNames.ANONYMOUS_BLOCK)
741                                .parseUntil(ANY).sequence(RBRACE).endNode();
742
743                // variables
744                inAnyState().sequence(EXPORT, EnumSet.of(VAR, CONST, LET))
745                                .subRecognizer(new JavaScriptSimpleStatementRecognizer(EShallowEntityType.ATTRIBUTE,
746                                                SubTypeNames.EXPORTED_VARIABLE, false), 1, 1)
747                                .endNode();
748
749                // simple statement
750                inAnyState().subRecognizer(new JavaScriptSimpleStatementRecognizer(EShallowEntityType.STATEMENT,
751                                SubTypeNames.SIMPLE_STATEMENT, true), 1, 1).endNode();
752        }
753
754        /**
755         * Creates the rule for "else if" construct. This is separated from the simple
756         * block rules as we need a sequence of keywords instead of a single keyword as
757         * start token.
758         */
759        private void createElseIfRule() {
760                RecognizerBase<EJavaScriptParserStates> elseIfAlternative = inAnyState().sequence(ELSE, IF)
761                                .createNode(EShallowEntityType.STATEMENT, new int[] { 0, 1 });
762                blockRuleWithContinuationHelper(elseIfAlternative, EnumSet.of(ELSE), true);
763        }
764
765        /** Creates simple block rules. */
766        private void createSimpleBlockRules() {
767                createBlockRuleWithContinuation(EnumSet.of(WHILE, FOR, SWITCH, WITH), null, true);
768                createBlockRuleWithContinuation(EnumSet.of(ELSE, FINALLY), null, false);
769                createBlockRuleWithContinuation(EnumSet.of(IF), EnumSet.of(ELSE), true);
770                createBlockRuleWithContinuation(EnumSet.of(TRY, CATCH), EnumSet.of(CATCH, FINALLY), true);
771        }
772
773        /** Creates rules for do/while. */
774        private void createDoWhileRules() {
775                RecognizerBase<EJavaScriptParserStates> doWhileAlternative = inState(ANY).sequence(DO)
776                                .createNode(EShallowEntityType.STATEMENT, 0);
777                doWhileAlternative.sequence(LBRACE).parseUntil(ANY).sequence(RBRACE, WHILE)
778                                .skipNested(LPAREN, RPAREN, new JavaScriptFunctionRecognizer()).endNode();
779                doWhileAlternative.parseOnce(ANY).sequence(WHILE).skipNested(LPAREN, RPAREN, new JavaScriptFunctionRecognizer())
780                                .endNode();
781        }
782
783        /** Creates rules for switch/case. */
784        private void createSwitchCaseRules() {
785                HashSet<ETokenType> literalsAndIdentifiers = new HashSet<>(LITERALS);
786                literalsAndIdentifiers.addAll(IDENTIFIERS);
787                inAnyState().sequence(CASE, literalsAndIdentifiers, COLON).createNode(EShallowEntityType.META, 0).endNode();
788                inAnyState().sequence(CASE).skipToWithNesting(COLON, LPAREN, RPAREN).createNode(EShallowEntityType.META, 0)
789                                .endNode();
790
791                inAnyState().sequence(DEFAULT, COLON).createNode(EShallowEntityType.META, 0).endNode();
792        }
793
794        /**
795         * Creates a rule for recognizing a statement starting with a single keyword,
796         * optionally followed by an expression in parentheses, and followed by a block
797         * or a single statement.
798         *
799         * @param continuationTokens
800         *            list of tokens that indicate a continued statement if encountered
801         *            after the block. May be null.
802         */
803        private void createBlockRuleWithContinuation(EnumSet<ETokenType> startTokens,
804                        EnumSet<ETokenType> continuationTokens, boolean canBeFollowedByParentheses) {
805                RecognizerBase<EJavaScriptParserStates> alternative = inAnyState().sequence(startTokens)
806                                .createNode(EShallowEntityType.STATEMENT, 0);
807                blockRuleWithContinuationHelper(alternative, continuationTokens, canBeFollowedByParentheses);
808        }
809
810        /**
811         * This method extracts shared code for block rules with single (e.g. 'for') and
812         * multiple keywords ('else if').
813         * 
814         * @param alternative
815         *            recognizer
816         * @param continuationTokens
817         *            list of tokens that indicate a continued statement if encountered
818         *            * after the block. May be null.
819         * @param canBeFollowedByParentheses
820         *            True e.g. for 'if' and 'for', False e.g. for 'else' and 'finally'
821         */
822        private void blockRuleWithContinuationHelper(RecognizerBase<EJavaScriptParserStates> alternative,
823                        EnumSet<ETokenType> continuationTokens, boolean canBeFollowedByParentheses) {
824                if (canBeFollowedByParentheses) {
825                        alternative = alternative.skipNested(LPAREN, RPAREN, new JavaScriptFunctionRecognizer());
826                }
827
828                endWithPossibleContinuation(alternative.sequence(LBRACE).parseUntil(ANY).sequence(RBRACE), continuationTokens);
829                endWithPossibleContinuation(alternative.parseOnce(ANY), continuationTokens);
830        }
831
832        /** {@inheritDoc} */
833        @Override
834        protected boolean isFilteredToken(IToken token, IToken previousToken) {
835                // Triple slash directives have ETokenClass COMMENT, but they indicate
836                // TypeScript dependencies. Thus, they should not be filtered out.
837                return super.isFilteredToken(token, previousToken) && token.getType() != ETokenType.TRIPLE_SLASH_DIRECTIVE;
838        }
839}