Skip to content

Commit

Permalink
refactor: add a new parser for TypeScript types in Fusion Generator (…
Browse files Browse the repository at this point in the history
…cherry-pick to 8.0) (#11901)

* refactor: add a new parser for TypeScript types in Fusion Generator (#11522)

(cherry picked from commit c5d7a5f)

* refactor: address Sonar issues

* style: fix

* fix: address Sonar review
  • Loading branch information
Lodin committed Sep 22, 2021
1 parent 8934075 commit cd17bfe
Show file tree
Hide file tree
Showing 18 changed files with 1,552 additions and 652 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import com.vaadin.flow.server.ExecutionFailedException;
import com.vaadin.flow.server.frontend.TaskGenerateFusion;
import com.vaadin.fusion.generator.VaadinConnectClientGenerator;
import com.vaadin.fusion.generator.VaadinConnectTsGenerator;
import com.vaadin.fusion.generator.typescript.VaadinConnectTsGenerator;

import static com.vaadin.fusion.generator.VaadinConnectClientGenerator.CONNECT_CLIENT_NAME;
import static com.vaadin.fusion.generator.VaadinConnectClientGenerator.CUSTOM_CONNECT_CLIENT_NAME;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.stream.IntStream;
Expand All @@ -31,97 +32,241 @@
* A set of static methods used in CCDM generators, so as flow do not depend on
* external libraries for these operations.
*/
final class GeneratorUtils {
public final class GeneratorUtils {

private GeneratorUtils() {
}

static boolean equals(String s1, String s2) {
return s1 == s2 || s1 != null && s1.equals(s2);
/**
* Capitalizes the string.
*
* @param s
* string to capitalize.
* @return result of capitalization.
*/
public static String capitalize(String s) {
return s == null ? s : s.substring(0, 1).toUpperCase() + s.substring(1);
}

static int compare(String s1, String s2) {
return equals(s1, s2) ? 0
/**
* Compares two strings lexicographically.
*
* @see String#compareTo
*
* @param s1
* first string.
* @param s2
* second string.
* @return result of comparison. Can be -1, 0, 1.
*/
public static int compare(String s1, String s2) {
return Objects.equals(s1, s2) ? 0
: s1 == null ? -1 : s2 == null ? 1 : s1.compareTo(s2);
}

static String capitalize(String s) {
return s == null ? s : s.substring(0, 1).toUpperCase() + s.substring(1);
}

static boolean isBlank(String s) {
return s == null || s.replaceAll("\\s+", "").isEmpty();
/**
* Checks if the string s contains string p with an additional check for
* them to not be blank.
*
* @param s
* a string where search to be performed.
* @param p
* a string to be used for search.
* @return the result of a check.
*/
@SuppressWarnings("squid:S2259")
public static boolean contains(String s, String p) {
return isNotBlank(s) && isNotBlank(p) && s.contains(p);
}

static boolean isNotBlank(String s) {
return !isBlank(s);
/**
* Returns a value if it is not null; otherwise, returns default value.
*
* @param o
* value
* @param def
* defaultValue
* @param <T>
* value type
* @return value or default value
*/
public static <T> T defaultIfNull(T o, T def) {
return o != null ? o : def;
}

static String firstNonBlank(String... values) {
/**
* Searches a first non-blank string in the received arguments.
*
* @param values
* a vararg array to search within.
* @return a result of the search.
*/
public static String firstNonBlank(String... values) {
return Arrays.stream(values).filter(GeneratorUtils::isNotBlank)
.findFirst().orElse(null);
}

static boolean isTrue(Boolean b) {
return Boolean.TRUE.equals(b);
/**
* Checks whether the declaration has the specific annotation.
*
* @param declaration
* a declaration that is checked to have an annotation.
* @param compilationUnit
* a compilation unit.
* @param annotation
* an annotation to check.
* @return the result of a check.
*/
public static boolean hasAnnotation(NodeWithAnnotations<?> declaration,
CompilationUnit compilationUnit,
Class<? extends Annotation> annotation) {
Optional<AnnotationExpr> endpointAnnotation = declaration
.getAnnotationByClass(annotation);
if (endpointAnnotation.isPresent()) {
return compilationUnit.getImports().stream()
.anyMatch(importDeclaration -> annotation.getName()
.equals(importDeclaration.getNameAsString())); // NOSONAR
}
return false;
}

static boolean isNotTrue(Boolean b) {
return !isTrue(b);
/**
* Checks if the string contains only whitespaces.
*
* @param s
* a string to check.
* @return a result of a check.
*/
public static boolean isBlank(String s) {
return s == null || s.replaceAll("\\s+", "").isEmpty();
}

static <T> T defaultIfNull(T o, T def) {
return o != null ? o : def;
/**
* Checks if the string contains not only whitespaces.
*
* @param s
* a string to check.
* @return a result of a check.
*/
public static boolean isNotBlank(String s) {
return !isBlank(s);
}

static String replaceChars(String s, char c1, final char c2) {
return s == null ? s : s.replace(c1, c2);
/**
* Checks a value for being not truth (or null).
*
* @param b
* a boolean to check.
* @return a result of a check.
*/
public static boolean isNotTrue(Boolean b) {
return !isTrue(b);
}

@SuppressWarnings("squid:S2259")
static boolean contains(String s, String p) {
return isNotBlank(s) && isNotBlank(p) && s.contains(p);
/**
* Checks a value for being true with an additional null check.
*
* @param b
* a boolean to check for truth.
* @return a result of a check.
*/
public static boolean isTrue(Boolean b) {
return Boolean.TRUE.equals(b);
}

/**
* Removes the end of a string.
*
* @param s
* a string to remove an end.
* @param p
* an end part of a string.
* @return a result string.
*/
@SuppressWarnings("squid:S2259")
static String substringAfter(String s, String p) {
return contains(s, p) ? s.substring(s.indexOf(p) + p.length()) : "";
public static String removeEnd(String s, String p) {
return s.endsWith(p) ? s.substring(0, s.lastIndexOf(p)) : s;
}

@SuppressWarnings("squid:S2259")
static String substringAfterLast(String s, String p) {
return contains(s, p) ? s.substring(s.lastIndexOf(p) + p.length()) : "";
/**
* Replaces one char with another in a string with an additional null check.
*
* @param s
* a string to perform replace within.
* @param c1
* a char to be replaced.
* @param c2
* a char to be replacement.
* @return a result string.
*/
public static String replaceChars(String s, char c1, final char c2) {
return s == null ? s : s.replace(c1, c2);
}

/**
* Gets the substring of an s string that goes after the first entry of the
* p string.
*
* @param s
* a string to get substring of.
* @param p
* a string to be searched.
* @return a substring.
*/
@SuppressWarnings("squid:S2259")
static String substringBeforeLast(String s, String p) {
return contains(s, p) ? s.substring(0, s.lastIndexOf(p)) : s;
public static String substringAfter(String s, String p) {
return contains(s, p) ? s.substring(s.indexOf(p) + p.length()) : "";
}

/**
* Gets the substring of an s string that goes after the last entry of the p
* string.
*
* @param s
* a string to get substring of.
* @param p
* a string to be searched.
* @return a substring.
*/
@SuppressWarnings("squid:S2259")
static boolean endsWith(String s, String p) {
return contains(s, p) && s.length() == p.length() + s.lastIndexOf(p);
public static String substringAfterLast(String s, String p) {
return contains(s, p) ? s.substring(s.lastIndexOf(p) + p.length()) : "";
}

/**
* Gets the substring of an s string that goes before the last entry of the
* p string.
*
* @param s
* a string to get substring of.
* @param p
* a string to be searched.
* @return a substring.
*/
@SuppressWarnings("squid:S2259")
static String removeEnd(String s, String p) {
return endsWith(s, p) ? s.substring(0, s.lastIndexOf(p)) : s;
}

static boolean hasAnnotation(NodeWithAnnotations<?> declaration,
CompilationUnit compilationUnit,
Class<? extends Annotation> annotation) {
Optional<AnnotationExpr> endpointAnnotation = declaration
.getAnnotationByClass(annotation);
if (endpointAnnotation.isPresent()) {
return compilationUnit.getImports().stream()
.anyMatch(importDeclaration -> annotation.getName()
.equals(importDeclaration.getNameAsString())); // NOSONAR
}
return false;
public static String substringBeforeLast(String s, String p) {
return contains(s, p) ? s.substring(0, s.lastIndexOf(p)) : s;
}

static <P1, P2, R> Stream<R> zip(List<P1> first, List<P2> second,
/**
* Runs a lambda against elements of two lists at once.
*
* @param first
* a first list.
* @param second
* a second list.
* @param zipper
* a lambda function that accepts elements from both lists at
* once.
* @param <P1>
* a type of item of a first list.
* @param <P2>
* a type of item of a second list.
* @param <R>
* a type of the streamed result.
* @return a stream with the values zipper produced.
*/
public static <P1, P2, R> Stream<R> zip(List<P1> first, List<P2> second,
BiFunction<P1, P2, R> zipper) {
return IntStream.range(0, Math.min(first.size(), second.size()))
.mapToObj(i -> zipper.apply(first.get(i), second.get(i)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

import com.vaadin.flow.server.frontend.FrontendUtils;

import static com.vaadin.fusion.generator.VaadinConnectTsGenerator.TS;
import static com.vaadin.fusion.generator.typescript.VaadinConnectTsGenerator.TS;

/**
* Generates the Vaadin connect-client file, based on the application
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2000-2021 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.fusion.generator.typescript;

import java.util.List;
import java.util.Map;
import java.util.Objects;

import com.vaadin.fusion.generator.GeneratorUtils;

import static com.vaadin.fusion.generator.typescript.VaadinConnectTsGenerator.IMPORT;

class CodeGeneratorUtils {
private CodeGeneratorUtils() {
}

// Method for extracting fully qualified name in a complex type. E.g.
// 'com.example.mypackage.Bean' will be extracted in the type
// `Map<String, Map<String, com.example.mypackage.Bean>>`
static String getSimpleNameFromComplexType(String dataType,
List<Map<String, String>> imports) {
return TypeParser.parse(dataType).traverse()
.visit(new SimpleNameVisitor(imports)).finish().toString();
}

static String getSimpleNameFromImports(String dataType,
List<Map<String, String>> imports) {
for (Map<String, String> anImport : imports) {
if (Objects.equals(dataType, anImport.get(IMPORT))) {
return GeneratorUtils.firstNonBlank(anImport.get("importAs"),
anImport.get("className"));
}
}
if (GeneratorUtils.contains(dataType, "<")
|| GeneratorUtils.contains(dataType, "{")
|| GeneratorUtils.contains(dataType, "|")) {
return getSimpleNameFromComplexType(dataType, imports);
}
return getSimpleNameFromQualifiedName(dataType);
}

static String getSimpleNameFromQualifiedName(String qualifiedName) {
if (GeneratorUtils.contains(qualifiedName, ".")) {
return GeneratorUtils.substringAfterLast(qualifiedName, ".");
}
return qualifiedName;
}

static class SimpleNameVisitor implements TypeParser.Visitor {
private final List<Map<String, String>> imports;

SimpleNameVisitor(List<Map<String, String>> imports) {
this.imports = imports;
}

@Override
public TypeParser.Node enter(TypeParser.Node node,
TypeParser.Node parent) {
String name = node.getName();

if (name.contains(".")) {
node.setName(getSimpleNameFromImports(name, imports));
}

return node;
}
}
}

0 comments on commit cd17bfe

Please sign in to comment.