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}