diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/StringUtil.java b/flow-server/src/main/java/com/vaadin/flow/internal/StringUtil.java index a62f9b27b39..9e0fec24889 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/StringUtil.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/StringUtil.java @@ -34,7 +34,7 @@ public final class StringUtil { * Comment parser state enumeration. */ private enum State { - NORMAL, IN_LINE_COMMENT, IN_BLOCK_COMMENT, IN_STRING + NORMAL, IN_LINE_COMMENT, IN_BLOCK_COMMENT, IN_STRING, IN_STRING_APOSTROPHE } /** @@ -99,6 +99,78 @@ public final static String removeComments(String code) { return handled; } + /** + * Removes comments (block comments and line comments) from the JS code. + * + * @return the code with removed comments + */ + public static String removeJsComments(String code) { + State state = State.NORMAL; + StringBuilder result = new StringBuilder(); + Map replacements = new HashMap<>(); + Scanner scanner = new Scanner(normalize(code, replacements)); + scanner.useDelimiter(""); + while (scanner.hasNext()) { + String character = scanner.next(); + switch (state) { + case NORMAL: + if (character.equals("/") && scanner.hasNext()) { + String nextCharacter = scanner.next(); + if (nextCharacter.equals("/")) { + state = State.IN_LINE_COMMENT; + } else if (nextCharacter.equals("*")) { + state = State.IN_BLOCK_COMMENT; + } else { + result.append(character).append(nextCharacter); + } + } else { + result.append(character); + if (character.equals("\"")) { + state = State.IN_STRING; + } else if (character.equals("\'")) { + state = State.IN_STRING_APOSTROPHE; + } + } + break; + case IN_STRING: + result.append(character); + if (character.equals("\"")) { + state = State.NORMAL; + } else if (character.equals("\\") && scanner.hasNext()) { + result.append(scanner.next()); + } + break; + case IN_STRING_APOSTROPHE: + result.append(character); + if (character.equals("\'")) { + state = State.NORMAL; + } else if (character.equals("\\") && scanner.hasNext()) { + result.append(scanner.next()); + } + break; + case IN_LINE_COMMENT: + if (character.equals("\n")) { + result.append(character); + state = State.NORMAL; + } + break; + case IN_BLOCK_COMMENT: + if (character.equals("*") && scanner.hasNext("/")) { + scanner.next(); + state = State.NORMAL; + break; + } + } + } + scanner.close(); + String handled = result.toString(); + for (Entry entry : replacements.entrySet()) { + handled = handled.replace(entry.getKey(), + String.valueOf(entry.getValue())); + } + return handled; + } + private static String normalize(String str, Map replacements) { StringBuilder builder = new StringBuilder(); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/AbstractUpdateImports.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/AbstractUpdateImports.java index ec0fd986980..ebcea585423 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/AbstractUpdateImports.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/AbstractUpdateImports.java @@ -22,6 +22,7 @@ import java.nio.charset.MalformedInputException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; @@ -574,7 +575,10 @@ private void visitImportsRecursively(Path filePath, String path, if (file == null && !importedPath.startsWith("./")) { // In case such file doesn't exist it may be external: inside // node_modules folder - file = getFile(getNodeModulesDir(), resolvedPath); + file = getFile(getNodeModulesDir(), importedPath); + if (!file.exists()) { + file = null; + } resolvedPath = importedPath; } if (file == null) { @@ -632,12 +636,19 @@ private String resolve(String importedPath, Path moduleFile, String path) { String pathPrefix = moduleFile.toString(); pathPrefix = pathPrefix.substring(0, pathPrefix.length() - path.length()); - String resolvedPath = moduleFile.getParent().resolve(importedPath) - .toString(); - if (resolvedPath.startsWith(pathPrefix)) { - resolvedPath = resolvedPath.substring(pathPrefix.length()); + try { + String resolvedPath = moduleFile.getParent().resolve(importedPath) + .toString(); + if (resolvedPath.startsWith(pathPrefix)) { + resolvedPath = resolvedPath.substring(pathPrefix.length()); + } + return resolvedPath; + } catch (InvalidPathException ipe) { + getLogger().error("Invalid import '{}' in file '{}'", importedPath, + moduleFile); + getLogger().debug("Failed to resolve path.", ipe); } - return resolvedPath; + return importedPath; } private String normalizePath(String path) { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/ImportExtractor.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/ImportExtractor.java index 9d443dee2e3..120dd2de6a2 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/ImportExtractor.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/ImportExtractor.java @@ -86,7 +86,7 @@ List getImportedPaths() { * @return the code with removed comments */ String removeComments() { - return StringUtil.removeComments(content); + return StringUtil.removeJsComments(content); } /** diff --git a/flow-server/src/test/java/com/vaadin/flow/internal/StringUtilTest.java b/flow-server/src/test/java/com/vaadin/flow/internal/StringUtilTest.java index 5de3fa59b76..dd9a9e0a168 100644 --- a/flow-server/src/test/java/com/vaadin/flow/internal/StringUtilTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/internal/StringUtilTest.java @@ -66,7 +66,44 @@ public void commentRemoval_emojiInString_removalDoesnotThrowResultIsTheSame() { @Test public void removeComments_commentsWithAsterisksInside_commentIsRemoved() { String result = StringUtil.removeComments("/* comment **/ ;"); - Assert.assertEquals(result, " ;"); + Assert.assertEquals(" ;", result); } + @Test + public void removeJsComments_handlesApostropheAsInString() { + String httpImport = "import 'http://localhost:56445/files/transformed/@vaadin/vaadin-text-field/vaadin-text-field.js';"; + + Assert.assertEquals("Nothing shoiuld be removed for import", httpImport, + StringUtil.removeJsComments(httpImport)); + + String result = StringUtil.removeJsComments("/* comment **/ ;"); + Assert.assertEquals(" ;", result); + + String singleLineBlock = StringUtil.removeJsComments( + "return html`/* single line block comment*/`;"); + + Assert.assertEquals("return html``;", singleLineBlock); + + String blockComment = StringUtil + .removeJsComments("return html`/* block with new lines\n" + + "* still in my/their block */`;"); + Assert.assertEquals("return html``;", blockComment); + + String newLineSingleBlock = StringUtil + .removeJsComments("return html`/* not here \n*/`;"); + Assert.assertEquals("return html``;", newLineSingleBlock); + + String noComments = "`"; + Assert.assertEquals(noComments, + StringUtil.removeJsComments(noComments)); + + String lineComment = StringUtil + .removeJsComments("return html`// this line comment\n`;"); + Assert.assertEquals("return html`\n`;", lineComment); + + String mixedComments = StringUtil.removeJsComments( + "return html`/* not here \n*/\nCode;// neither this\n" + + "/* this should // be fine\n* to remove / */`;"); + Assert.assertEquals("return html`\nCode;\n`;", mixedComments); + } }