001package eu.cqse.check.framework.util.javascript; 002 003import static eu.cqse.check.framework.scanner.ETokenType.CLASS; 004import static eu.cqse.check.framework.scanner.ETokenType.COMMA; 005import static eu.cqse.check.framework.scanner.ETokenType.DOCUMENTATION_COMMENT; 006import static eu.cqse.check.framework.scanner.ETokenType.DOT; 007import static eu.cqse.check.framework.scanner.ETokenType.EQ; 008import static eu.cqse.check.framework.scanner.ETokenType.FUNCTION; 009import static eu.cqse.check.framework.scanner.ETokenType.IDENTIFIER; 010import static eu.cqse.check.framework.scanner.ETokenType.LPAREN; 011import static eu.cqse.check.framework.scanner.ETokenType.NEW; 012import static eu.cqse.check.framework.scanner.ETokenType.RPAREN; 013import static eu.cqse.check.framework.scanner.ETokenType.THIS; 014import static org.conqat.lib.commons.collections.CollectionUtils.emptyList; 015 016import java.util.ArrayList; 017import java.util.EnumSet; 018import java.util.List; 019import java.util.Set; 020import java.util.regex.Matcher; 021import java.util.regex.Pattern; 022 023import org.conqat.lib.commons.collections.CollectionUtils; 024import org.conqat.lib.commons.string.StringUtils; 025 026import eu.cqse.check.framework.scanner.ETokenType; 027import eu.cqse.check.framework.scanner.IToken; 028import eu.cqse.check.framework.util.tokens.TokenPattern; 029 030/** Collects patterns to determine Closure dependencies. */ 031public class ClosurePatterns { 032 033 /** The index of the group in matches that contain the dependent namespace. */ 034 public static final int DEPENDENCY_NAMESPACE_GROUP_INDEX = 2; 035 036 /** 037 * Pattern to extract a (Closure) type dependency, e.g. x.y.MyNamespace. The 038 * target namespace will be in group 2. 039 */ 040 private static final Pattern DEPENDENCY_COMMENT_PATTERN = Pattern.compile("([|{:<\\s])((\\w+\\.)+\\w+)[|}>,]"); 041 042 /** Closure annotations that can be followed by a dependency. */ 043 private static final Set<String> DEPENDENCY_ANNOTATIONS = CollectionUtils.asHashSet("@type", "@extends", "@param", 044 "@return", "@implements"); 045 046 /** A closure namespace, e.g. x.y.MyType */ 047 public static final TokenPattern NAMESPACE_PATTERN = new TokenPattern().sequence(IDENTIFIER, 048 new TokenPattern().repeatedAtLeastOnce(DOT, IDENTIFIER)); 049 050 /** Pattern for a namespace with a constant, e.g. x.y.z.MY_CONSTANT */ 051 public static final TokenPattern NAMESPACE_CONSTANT_PATTERN = new TokenPattern().sequence(NAMESPACE_PATTERN) 052 .group(DEPENDENCY_NAMESPACE_GROUP_INDEX).notFollowedBy(LPAREN); 053 054 /** Pattern for constructor, e.g. new x.y.MyClass( */ 055 public static final TokenPattern CONSTRUCTOR_PATTERN = new TokenPattern().sequence(NEW).sequence(NAMESPACE_PATTERN) 056 .group(DEPENDENCY_NAMESPACE_GROUP_INDEX).sequence(LPAREN); 057 058 /** Pattern for a static method call, e.g. x.y.mynamespace.foo( */ 059 public static final TokenPattern STATIC_METHOD_CALL_PATTERN = new TokenPattern() 060 .notPrecededBy(EnumSet.of(NEW, THIS, DOT)).sequence(NAMESPACE_PATTERN) 061 .group(DEPENDENCY_NAMESPACE_GROUP_INDEX).sequence(LPAREN); 062 063 /** 064 * Pattern of a closure constructor declaration, e.g. x.y.MyNamespace = 065 * function( 066 */ 067 public static final TokenPattern CLOSURE_CONSTRUCTOR_PATTERN = new TokenPattern().optional(DOCUMENTATION_COMMENT) 068 .sequence(NAMESPACE_PATTERN).group(DEPENDENCY_NAMESPACE_GROUP_INDEX).alternative(ETokenType.SEMICOLON, 069 new TokenPattern().sequence(EQ, FUNCTION, LPAREN), new TokenPattern().sequence(EQ, CLASS)); 070 071 /** 072 * A pattern that matches against Closure type inheritance functions, e.g. 073 * 'goog.inherits(ts.dashboard.widgets.GridAwareResizeDragger, goog.fx.Dragger)' 074 */ 075 public static final TokenPattern GOOG_INHERITS_PATTERN = new TokenPattern() 076 .sequence(new TokenPattern().sequence(IDENTIFIER, DOT, IDENTIFIER).group(1), LPAREN, NAMESPACE_PATTERN, 077 COMMA) 078 .sequence(NAMESPACE_PATTERN).group(DEPENDENCY_NAMESPACE_GROUP_INDEX).sequence(RPAREN); 079 080 /** 081 * @return the Closure dependencies in the given comment token, or an empty 082 * list. 083 */ 084 public static List<String> getDependenciesFromComment(IToken commentToken) { 085 if (commentToken.getType() != DOCUMENTATION_COMMENT 086 || !StringUtils.containsOneOf(commentToken.getText(), DEPENDENCY_ANNOTATIONS)) { 087 return emptyList(); 088 } 089 090 List<String> dependencies = new ArrayList<>(); 091 Matcher matcher = DEPENDENCY_COMMENT_PATTERN.matcher(commentToken.getText()); 092 while (matcher.find()) { 093 dependencies.add(matcher.group(2)); 094 } 095 return dependencies; 096 } 097}