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}