From 0cd159ea57bc5d8a1dcadb03bc5a240e2fe1ca80 Mon Sep 17 00:00:00 2001 From: Joe Wu Date: Wed, 9 Apr 2025 11:14:35 -0700 Subject: [PATCH 1/6] Add argumen parser feature to the LSP. --- .../amazon/smithy/lsp/ArgumentParser.java | 82 ++++++++++ .../java/software/amazon/smithy/lsp/Main.java | 104 ++----------- .../amazon/smithy/lsp/ServerArguments.java | 83 ++++++++++ .../amazon/smithy/lsp/ServerLauncher.java | 95 +++++++++++ .../amazon/smithy/lsp/ArgumentParserTest.java | 92 +++++++++++ .../amazon/smithy/lsp/ServerLauncherTest.java | 147 ++++++++++++++++++ 6 files changed, 511 insertions(+), 92 deletions(-) create mode 100644 src/main/java/software/amazon/smithy/lsp/ArgumentParser.java create mode 100644 src/main/java/software/amazon/smithy/lsp/ServerArguments.java create mode 100644 src/main/java/software/amazon/smithy/lsp/ServerLauncher.java create mode 100644 src/test/java/software/amazon/smithy/lsp/ArgumentParserTest.java create mode 100644 src/test/java/software/amazon/smithy/lsp/ServerLauncherTest.java diff --git a/src/main/java/software/amazon/smithy/lsp/ArgumentParser.java b/src/main/java/software/amazon/smithy/lsp/ArgumentParser.java new file mode 100644 index 00000000..fec6c431 --- /dev/null +++ b/src/main/java/software/amazon/smithy/lsp/ArgumentParser.java @@ -0,0 +1,82 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.lsp; + +import java.util.List; +import software.amazon.smithy.cli.Arguments; +import software.amazon.smithy.cli.CliError; +import software.amazon.smithy.cli.CliPrinter; +import software.amazon.smithy.cli.HelpPrinter; + +final class ArgumentParser { + private static final int PORT_NUMBER_INDEX = 0; + private final ServerArguments argumentReceiver = new ServerArguments(); + private final Arguments arguments; + private final CliPrinter cliPrinter; + private List positional; + private int portNumber; + + ArgumentParser(String[] args) { + this(args, CliPrinter.fromOutputStream(System.out)); + } + + ArgumentParser(String[] args, CliPrinter cliPrinter) { + arguments = Arguments.of(args); + arguments.addReceiver(argumentReceiver); + this.cliPrinter = cliPrinter; + } + + private void printHelp() { + HelpPrinter helpPrinter = HelpPrinter.fromArguments("java -jar smithy-lsp.jar", arguments); + helpPrinter.summary("Options for the Smithy Language Server:"); + helpPrinter.print(argumentReceiver.colorSetting(), cliPrinter); + cliPrinter.flush(); + } + + /** + * Parse the arguments received. + * + */ + public void parse() { + positional = arguments.getPositional(); + if (argumentReceiver.help()) { + printHelp(); + } else { + parsePortNumber(); + } + } + + /** + * Check if the help flag was passed. + * + * @return True if the help flag was passed, false otherwise. + */ + public boolean isHelp() { + return argumentReceiver.help(); + } + + /** + * Parse the port number from the arguments. + * + */ + public void parsePortNumber() { + portNumber = argumentReceiver.getPortNumber(); + if (!positional.isEmpty() && portNumber == ServerArguments.DEFAULT_PORT) { + portNumber = argumentReceiver.validatePortNumber(positional.get(PORT_NUMBER_INDEX)); + } + if (portNumber == ServerArguments.INVALID_PORT) { + throw new CliError("Invalid port number."); + } + } + + /** + * Get the port number based on the positional and flag arguments. + * @return The port number of the Smithy Language Server. + */ + public int getPortNumber() { + return portNumber; + } +} diff --git a/src/main/java/software/amazon/smithy/lsp/Main.java b/src/main/java/software/amazon/smithy/lsp/Main.java index 87add549..2a36faed 100644 --- a/src/main/java/software/amazon/smithy/lsp/Main.java +++ b/src/main/java/software/amazon/smithy/lsp/Main.java @@ -15,15 +15,6 @@ package software.amazon.smithy.lsp; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.util.Optional; -import org.eclipse.lsp4j.jsonrpc.Launcher; -import org.eclipse.lsp4j.launch.LSPLauncher; -import org.eclipse.lsp4j.services.LanguageClient; - /** * Main launcher for the Language server, started by the editor. */ @@ -32,90 +23,19 @@ private Main() { } /** - * Launch the LSP and wait for it to terminate. - * - * @param in input stream for communication - * @param out output stream for communication - * @return Empty Optional if service terminated successfully, error otherwise + * Main entry point for the language server. + * @param args Arguments passed to the server. + * @throws Exception If there is an error starting the server. */ - public static Optional launch(InputStream in, OutputStream out) { - SmithyLanguageServer server = new SmithyLanguageServer(); - Launcher launcher = LSPLauncher.createServerLauncher( - server, - exitOnClose(in), - out); - - LanguageClient client = launcher.getRemoteProxy(); - - server.connect(client); - try { - launcher.startListening().get(); - return Optional.empty(); - } catch (Exception e) { - return Optional.of(e); - } - } - - private static InputStream exitOnClose(InputStream delegate) { - return new InputStream() { - @Override - public int read() throws IOException { - int result = delegate.read(); - if (result < 0) { - System.exit(0); - } - return result; - } - }; - } - - /** - * @param args Arguments passed to launch server. First argument must either be - * a port number for socket connection, or 0 to use STDIN and STDOUT - * for communication - */ - public static void main(String[] args) { - - Socket socket = null; - InputStream in; - OutputStream out; - - try { - String port = args[0]; - // If port is set to "0", use System.in/System.out. - if (port.equals("0")) { - in = System.in; - out = System.out; - } else { - socket = new Socket("localhost", Integer.parseInt(port)); - in = socket.getInputStream(); - out = socket.getOutputStream(); - } - - Optional launchFailure = launch(in, out); - - if (launchFailure.isPresent()) { - throw launchFailure.get(); - } else { - System.out.println("Server terminated without errors"); - } - } catch (ArrayIndexOutOfBoundsException e) { - System.out.println("Missing port argument"); - } catch (NumberFormatException e) { - System.out.println("Port number must be a valid integer"); - } catch (Exception e) { - System.out.println(e); - - e.printStackTrace(); - } finally { - try { - if (socket != null) { - socket.close(); - } - } catch (Exception e) { - System.out.println("Failed to close the socket"); - System.out.println(e); - } + public static void main(String[] args) throws Exception { + ArgumentParser parser = new ArgumentParser(args); + parser.parse(); + if (parser.isHelp()) { + System.exit(0); } + ServerLauncher launcher = new ServerLauncher(parser.getPortNumber()); + launcher.initConnection(); + launcher.launch(); + launcher.closeConnection(); } } diff --git a/src/main/java/software/amazon/smithy/lsp/ServerArguments.java b/src/main/java/software/amazon/smithy/lsp/ServerArguments.java new file mode 100644 index 00000000..9363dbfb --- /dev/null +++ b/src/main/java/software/amazon/smithy/lsp/ServerArguments.java @@ -0,0 +1,83 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.lsp; + +import java.util.function.Consumer; +import software.amazon.smithy.cli.AnsiColorFormatter; +import software.amazon.smithy.cli.ArgumentReceiver; +import software.amazon.smithy.cli.HelpPrinter; + +/** + * Options and Params available for LSP. + */ +final class ServerArguments implements ArgumentReceiver { + + static final int MIN_PORT = 0; + static final int MAX_PORT = 65535; + static final int DEFAULT_PORT = 0; // Default value for unset port number. + static final int INVALID_PORT = -1; + static final String HELP = "--help"; + static final String HELP_SHORT = "-h"; + static final String PORT_NUMBER = "--port-number"; + static final String PORT_NUMBER_SHORT = "-p"; + static final String PORT_NUMBER_POSITIONAL = ""; + private int portNumberInt = DEFAULT_PORT; + private boolean help = false; + private AnsiColorFormatter colorSetting = AnsiColorFormatter.AUTO; + + @Override + public void registerHelp(HelpPrinter printer) { + printer.option(HELP, HELP_SHORT, "Print this help output."); + printer.param(PORT_NUMBER, PORT_NUMBER_SHORT, "PORT_NUMBER", + "The port number to be used by the Smithy Language Server. Default port number is 0 if not specified."); + printer.option(PORT_NUMBER_POSITIONAL, null, "Positional port-number."); + } + + @Override + public boolean testOption(String name) { + if (name.equals(HELP) || name.equals(HELP_SHORT)) { + help = true; + return true; + } + return false; + } + + @Override + public Consumer testParameter(String name) { + if (name.equals(PORT_NUMBER_SHORT) || name.equals(PORT_NUMBER)) { + return value -> { + portNumberInt = validatePortNumber(value); + }; + } + return null; + } + + public int getPortNumber() { + return portNumberInt; + } + + public boolean help() { + return help; + } + + public int validatePortNumber(String portNumberStr) { + try { + int portNumber = Integer.parseInt(portNumberStr); + if (portNumber < MIN_PORT || portNumber > MAX_PORT) { + return INVALID_PORT; + } else { + return portNumber; + } + } catch (NumberFormatException e) { + return INVALID_PORT; + } + } + + public AnsiColorFormatter colorSetting() { + return colorSetting; + } + +} diff --git a/src/main/java/software/amazon/smithy/lsp/ServerLauncher.java b/src/main/java/software/amazon/smithy/lsp/ServerLauncher.java new file mode 100644 index 00000000..a8c6f0fa --- /dev/null +++ b/src/main/java/software/amazon/smithy/lsp/ServerLauncher.java @@ -0,0 +1,95 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package software.amazon.smithy.lsp; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; +import org.eclipse.lsp4j.jsonrpc.Launcher; +import org.eclipse.lsp4j.launch.LSPLauncher; +import org.eclipse.lsp4j.services.LanguageClient; + +class ServerLauncher { + private static final String SOCKET_HOST = "localhost"; + private static final Logger LOGGER = Logger.getLogger(ServerLauncher.class.getName()); + private final int portNumber; + private InputStream in; + private OutputStream out; + private Socket socket; + + ServerLauncher(int portNumber) { + this.portNumber = portNumber; + } + + Socket createSocket() throws IOException { + return new Socket(SOCKET_HOST, portNumber); + } + + void initConnection() throws IOException { + if (portNumber == ServerArguments.DEFAULT_PORT) { + in = System.in; + out = System.out; + } else { + socket = createSocket(); + in = socket.getInputStream(); + out = socket.getOutputStream(); + } + } + + InputStream getInputStream() { + return in; + } + + OutputStream getOutputStream() { + return out; + } + + void closeConnection() { + try { + if (socket != null) { + socket.close(); + } + } catch (IOException e) { + LOGGER.severe("Failed to close the socket"); + } + } + + void handleExit() { + closeConnection(); + System.exit(0); + } + + private InputStream exitOnClose(InputStream delegate) { + return new InputStream() { + @Override + public int read() throws IOException { + int result = delegate.read(); + if (result < 0) { + handleExit(); + } + return result; + } + }; + } + + /** + * Launch the LSP and wait for it to terminate. + * + */ + void launch() throws InterruptedException, ExecutionException { + SmithyLanguageServer server = new SmithyLanguageServer(); + Launcher launcher = LSPLauncher.createServerLauncher( + server, + exitOnClose(in), + out); + LanguageClient client = launcher.getRemoteProxy(); + server.connect(client); + launcher.startListening().get(); + } +} diff --git a/src/test/java/software/amazon/smithy/lsp/ArgumentParserTest.java b/src/test/java/software/amazon/smithy/lsp/ArgumentParserTest.java new file mode 100644 index 00000000..a7994f65 --- /dev/null +++ b/src/test/java/software/amazon/smithy/lsp/ArgumentParserTest.java @@ -0,0 +1,92 @@ +package software.amazon.smithy.lsp; + +import java.io.ByteArrayOutputStream; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.cli.CliError; +import software.amazon.smithy.cli.CliPrinter; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ArgumentParserTest { + @Test + void validPositionalPortNumber() { + String[] args = {"1"}; + ArgumentParser parser = new ArgumentParser(args); + parser.parse(); + assertEquals(1, parser.getPortNumber()); + } + + @Test + void invalidPositionalPortNumber() { + String[] args = {"65536"}; + ArgumentParser parser = new ArgumentParser(args); + CliError error = assertThrows(CliError.class, parser::parse); + assertEquals("Invalid port number.", error.getMessage()); + } + + @Test + void invalidFlagPortNumber() { + String[] args = {"-p","65536"}; + ArgumentParser parser = new ArgumentParser(args); + CliError error = assertThrows(CliError.class, parser::parse); + assertEquals("Invalid port number.", error.getMessage()); + } + + @Test + void validFlagPortNumberShort() { + String[] args = {"-p","100"}; + ArgumentParser parser = new ArgumentParser(args); + parser.parse(); + assertEquals(100, parser.getPortNumber()); + } + + @Test + void defaultPortNumber() { + String[] args = {}; + ArgumentParser parser = new ArgumentParser(args); + parser.parse(); + assertEquals(0, parser.getPortNumber()); + } + + @Test + void defaultPortNumberInArg() { + String[] args = {"0"}; + ArgumentParser parser = new ArgumentParser(args); + parser.parse(); + assertEquals(0, parser.getPortNumber()); + } + + @Test + void validFlagPortNumber() { + String[] args = {"--port-number","200"}; + ArgumentParser parser = new ArgumentParser(args); + parser.parse(); + assertEquals(200, parser.getPortNumber()); + } + + @Test + void validHelp() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + CliPrinter testPrinter = CliPrinter.fromOutputStream(outputStream); + String[] args = {"--help"}; + + ArgumentParser parser = new ArgumentParser(args, testPrinter); + + parser.parse(); + + String output = outputStream.toString(); + assertTrue(output.contains("Usage: java -jar smithy-lsp.jar [--help | -h] ")); + assertTrue(output.contains("[--port-number | -p PORT_NUMBER] ")); + } + + @Test + void invalidFlag() { + String[] args = {"--foo"}; + ArgumentParser parser = new ArgumentParser(args); + CliError error = assertThrows(CliError.class, parser::parse); + assertEquals("Unexpected CLI argument: --foo", error.getMessage()); + } +} diff --git a/src/test/java/software/amazon/smithy/lsp/ServerLauncherTest.java b/src/test/java/software/amazon/smithy/lsp/ServerLauncherTest.java new file mode 100644 index 00000000..471922a8 --- /dev/null +++ b/src/test/java/software/amazon/smithy/lsp/ServerLauncherTest.java @@ -0,0 +1,147 @@ +package software.amazon.smithy.lsp; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.Socket; +import org.junit.jupiter.api.Test; + + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ServerLauncherTest { + private static class MockInputStream extends InputStream { + private boolean closed = false; + @Override + public int read() throws IOException { + return -1; + } + + @Override + public void close() { + closed = true; + } + } + + private static class MockOutputStream extends OutputStream { + private boolean used = false; + private boolean closed = false; + + @Override + public void write(int b) { + used = true; + } + + @Override + public void close() { + closed = true; + } + } + + private static class TestServerLauncher extends ServerLauncher { + private final MockSocket mockSocket = new MockSocket(); + private boolean exited = false; + TestServerLauncher(int portNumber) { + super(portNumber); + } + + @Override + void handleExit() { + exited = true; + } + + boolean exited() { + return exited; + } + + @Override + Socket createSocket() { + return mockSocket; + } + + Socket getMockSocket() { + return mockSocket; + } + + } + + private static class MockSocket extends Socket { + private boolean isClosed = false; + private final MockInputStream inputStream = new MockInputStream(); + private final MockOutputStream outputStream = new MockOutputStream(); + + @Override + public InputStream getInputStream() { + return inputStream; + } + + @Override + public OutputStream getOutputStream() { + return outputStream; + } + + @Override + public void close() { + isClosed = true; + } + + public boolean isClosed() { + return isClosed; + } + } + + @Test + void testCustomPortConnection() throws IOException { + TestServerLauncher launcher = new TestServerLauncher(8080); + launcher.initConnection(); + + assertFalse(launcher.getMockSocket().isClosed()); + assertNotNull(launcher.getMockSocket().getInputStream()); + assertNotNull(launcher.getMockSocket().getOutputStream()); + } + + @Test + void testConnectionClose() throws IOException { + TestServerLauncher launcher = new TestServerLauncher(8080); + launcher.initConnection(); + launcher.closeConnection(); + + assertTrue(launcher.getMockSocket().isClosed()); + } + + @Test + void testDefaultPortConnection() throws IOException { + ByteArrayInputStream testIn = new ByteArrayInputStream("test".getBytes()); + ByteArrayOutputStream testOut = new ByteArrayOutputStream(); + PrintStream testPrintStream = new PrintStream(testOut); + + System.setIn(testIn); + System.setOut(testPrintStream); + + ServerLauncher launcher = new ServerLauncher(ServerArguments.DEFAULT_PORT); + launcher.initConnection(); + + assertEquals(System.in, launcher.getInputStream()); + assertEquals(System.out, launcher.getOutputStream()); + testIn.close(); + testOut.close(); + } + + @Test + void testLaunchWithDefaultPort() throws IOException { + TestServerLauncher launcher = new TestServerLauncher(ServerArguments.DEFAULT_PORT); + launcher.initConnection(); + assertDoesNotThrow(launcher::launch); + launcher.closeConnection(); + assertTrue(launcher.exited()); + } + +} + From b0151c0ea9e6a39dafa002c181eda5b3cf7e9a0f Mon Sep 17 00:00:00 2001 From: Joe Wu Date: Thu, 10 Apr 2025 11:02:46 -0700 Subject: [PATCH 2/6] Moved ArgumentParser functionalities to ServerArguments --- .../amazon/smithy/lsp/ArgumentParser.java | 82 ---------------- .../java/software/amazon/smithy/lsp/Main.java | 7 +- .../amazon/smithy/lsp/ServerArguments.java | 39 ++++++-- .../amazon/smithy/lsp/ArgumentParserTest.java | 92 ------------------ .../smithy/lsp/ServerArgumentsTest.java | 96 +++++++++++++++++++ 5 files changed, 129 insertions(+), 187 deletions(-) delete mode 100644 src/main/java/software/amazon/smithy/lsp/ArgumentParser.java delete mode 100644 src/test/java/software/amazon/smithy/lsp/ArgumentParserTest.java create mode 100644 src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java diff --git a/src/main/java/software/amazon/smithy/lsp/ArgumentParser.java b/src/main/java/software/amazon/smithy/lsp/ArgumentParser.java deleted file mode 100644 index fec6c431..00000000 --- a/src/main/java/software/amazon/smithy/lsp/ArgumentParser.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.lsp; - -import java.util.List; -import software.amazon.smithy.cli.Arguments; -import software.amazon.smithy.cli.CliError; -import software.amazon.smithy.cli.CliPrinter; -import software.amazon.smithy.cli.HelpPrinter; - -final class ArgumentParser { - private static final int PORT_NUMBER_INDEX = 0; - private final ServerArguments argumentReceiver = new ServerArguments(); - private final Arguments arguments; - private final CliPrinter cliPrinter; - private List positional; - private int portNumber; - - ArgumentParser(String[] args) { - this(args, CliPrinter.fromOutputStream(System.out)); - } - - ArgumentParser(String[] args, CliPrinter cliPrinter) { - arguments = Arguments.of(args); - arguments.addReceiver(argumentReceiver); - this.cliPrinter = cliPrinter; - } - - private void printHelp() { - HelpPrinter helpPrinter = HelpPrinter.fromArguments("java -jar smithy-lsp.jar", arguments); - helpPrinter.summary("Options for the Smithy Language Server:"); - helpPrinter.print(argumentReceiver.colorSetting(), cliPrinter); - cliPrinter.flush(); - } - - /** - * Parse the arguments received. - * - */ - public void parse() { - positional = arguments.getPositional(); - if (argumentReceiver.help()) { - printHelp(); - } else { - parsePortNumber(); - } - } - - /** - * Check if the help flag was passed. - * - * @return True if the help flag was passed, false otherwise. - */ - public boolean isHelp() { - return argumentReceiver.help(); - } - - /** - * Parse the port number from the arguments. - * - */ - public void parsePortNumber() { - portNumber = argumentReceiver.getPortNumber(); - if (!positional.isEmpty() && portNumber == ServerArguments.DEFAULT_PORT) { - portNumber = argumentReceiver.validatePortNumber(positional.get(PORT_NUMBER_INDEX)); - } - if (portNumber == ServerArguments.INVALID_PORT) { - throw new CliError("Invalid port number."); - } - } - - /** - * Get the port number based on the positional and flag arguments. - * @return The port number of the Smithy Language Server. - */ - public int getPortNumber() { - return portNumber; - } -} diff --git a/src/main/java/software/amazon/smithy/lsp/Main.java b/src/main/java/software/amazon/smithy/lsp/Main.java index 2a36faed..9265ae60 100644 --- a/src/main/java/software/amazon/smithy/lsp/Main.java +++ b/src/main/java/software/amazon/smithy/lsp/Main.java @@ -28,12 +28,11 @@ private Main() { * @throws Exception If there is an error starting the server. */ public static void main(String[] args) throws Exception { - ArgumentParser parser = new ArgumentParser(args); - parser.parse(); - if (parser.isHelp()) { + var serverArguments = ServerArguments.create(args); + if (serverArguments.help()) { System.exit(0); } - ServerLauncher launcher = new ServerLauncher(parser.getPortNumber()); + ServerLauncher launcher = new ServerLauncher(serverArguments.getPortNumber()); launcher.initConnection(); launcher.launch(); launcher.closeConnection(); diff --git a/src/main/java/software/amazon/smithy/lsp/ServerArguments.java b/src/main/java/software/amazon/smithy/lsp/ServerArguments.java index 9363dbfb..f0d8793c 100644 --- a/src/main/java/software/amazon/smithy/lsp/ServerArguments.java +++ b/src/main/java/software/amazon/smithy/lsp/ServerArguments.java @@ -8,6 +8,9 @@ import java.util.function.Consumer; import software.amazon.smithy.cli.AnsiColorFormatter; import software.amazon.smithy.cli.ArgumentReceiver; +import software.amazon.smithy.cli.Arguments; +import software.amazon.smithy.cli.CliError; +import software.amazon.smithy.cli.CliPrinter; import software.amazon.smithy.cli.HelpPrinter; /** @@ -18,15 +21,28 @@ final class ServerArguments implements ArgumentReceiver { static final int MIN_PORT = 0; static final int MAX_PORT = 65535; static final int DEFAULT_PORT = 0; // Default value for unset port number. - static final int INVALID_PORT = -1; static final String HELP = "--help"; static final String HELP_SHORT = "-h"; static final String PORT_NUMBER = "--port-number"; static final String PORT_NUMBER_SHORT = "-p"; static final String PORT_NUMBER_POSITIONAL = ""; - private int portNumberInt = DEFAULT_PORT; + private int portNumber = DEFAULT_PORT; private boolean help = false; - private AnsiColorFormatter colorSetting = AnsiColorFormatter.AUTO; + + + static ServerArguments create(String[] args) { + Arguments arguments = Arguments.of(args); + var serverArguments = new ServerArguments(); + arguments.addReceiver(serverArguments); + var positional = arguments.getPositional(); + if (serverArguments.help()) { + serverArguments.printHelp(arguments); + } + if (!positional.isEmpty()) { + serverArguments.portNumber = serverArguments.validatePortNumber(positional.getFirst()); + } + return serverArguments; + } @Override public void registerHelp(HelpPrinter printer) { @@ -49,14 +65,14 @@ public boolean testOption(String name) { public Consumer testParameter(String name) { if (name.equals(PORT_NUMBER_SHORT) || name.equals(PORT_NUMBER)) { return value -> { - portNumberInt = validatePortNumber(value); + portNumber = validatePortNumber(value); }; } return null; } public int getPortNumber() { - return portNumberInt; + return portNumber; } public boolean help() { @@ -67,17 +83,22 @@ public int validatePortNumber(String portNumberStr) { try { int portNumber = Integer.parseInt(portNumberStr); if (portNumber < MIN_PORT || portNumber > MAX_PORT) { - return INVALID_PORT; + throw new CliError("Invalid port number!"); } else { return portNumber; } } catch (NumberFormatException e) { - return INVALID_PORT; + throw new CliError("Invalid port number!"); } } - public AnsiColorFormatter colorSetting() { - return colorSetting; + + private void printHelp(Arguments arguments) { + CliPrinter printer = CliPrinter.fromOutputStream(System.out); + HelpPrinter helpPrinter = HelpPrinter.fromArguments("java -jar smithy-lsp.jar", arguments); + helpPrinter.summary("Options for the Smithy Language Server:"); + helpPrinter.print(AnsiColorFormatter.AUTO, printer); + printer.flush(); } } diff --git a/src/test/java/software/amazon/smithy/lsp/ArgumentParserTest.java b/src/test/java/software/amazon/smithy/lsp/ArgumentParserTest.java deleted file mode 100644 index a7994f65..00000000 --- a/src/test/java/software/amazon/smithy/lsp/ArgumentParserTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package software.amazon.smithy.lsp; - -import java.io.ByteArrayOutputStream; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.cli.CliError; -import software.amazon.smithy.cli.CliPrinter; - - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class ArgumentParserTest { - @Test - void validPositionalPortNumber() { - String[] args = {"1"}; - ArgumentParser parser = new ArgumentParser(args); - parser.parse(); - assertEquals(1, parser.getPortNumber()); - } - - @Test - void invalidPositionalPortNumber() { - String[] args = {"65536"}; - ArgumentParser parser = new ArgumentParser(args); - CliError error = assertThrows(CliError.class, parser::parse); - assertEquals("Invalid port number.", error.getMessage()); - } - - @Test - void invalidFlagPortNumber() { - String[] args = {"-p","65536"}; - ArgumentParser parser = new ArgumentParser(args); - CliError error = assertThrows(CliError.class, parser::parse); - assertEquals("Invalid port number.", error.getMessage()); - } - - @Test - void validFlagPortNumberShort() { - String[] args = {"-p","100"}; - ArgumentParser parser = new ArgumentParser(args); - parser.parse(); - assertEquals(100, parser.getPortNumber()); - } - - @Test - void defaultPortNumber() { - String[] args = {}; - ArgumentParser parser = new ArgumentParser(args); - parser.parse(); - assertEquals(0, parser.getPortNumber()); - } - - @Test - void defaultPortNumberInArg() { - String[] args = {"0"}; - ArgumentParser parser = new ArgumentParser(args); - parser.parse(); - assertEquals(0, parser.getPortNumber()); - } - - @Test - void validFlagPortNumber() { - String[] args = {"--port-number","200"}; - ArgumentParser parser = new ArgumentParser(args); - parser.parse(); - assertEquals(200, parser.getPortNumber()); - } - - @Test - void validHelp() { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - CliPrinter testPrinter = CliPrinter.fromOutputStream(outputStream); - String[] args = {"--help"}; - - ArgumentParser parser = new ArgumentParser(args, testPrinter); - - parser.parse(); - - String output = outputStream.toString(); - assertTrue(output.contains("Usage: java -jar smithy-lsp.jar [--help | -h] ")); - assertTrue(output.contains("[--port-number | -p PORT_NUMBER] ")); - } - - @Test - void invalidFlag() { - String[] args = {"--foo"}; - ArgumentParser parser = new ArgumentParser(args); - CliError error = assertThrows(CliError.class, parser::parse); - assertEquals("Unexpected CLI argument: --foo", error.getMessage()); - } -} diff --git a/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java b/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java new file mode 100644 index 00000000..b5ada80d --- /dev/null +++ b/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java @@ -0,0 +1,96 @@ +package software.amazon.smithy.lsp; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.cli.CliError; + + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ServerArgumentsTest { + @Test + void validPositionalPortNumber() { + String[] args = {"1"}; + ServerArguments serverArguments = ServerArguments.create(args); + assertEquals(1, serverArguments.getPortNumber()); + } + + @Test + void invalidPositionalPortNumber() { + String[] args = {"65536"}; + assertThrows(CliError.class,()-> {ServerArguments.create(args);}); + } + + @Test + void invalidFlagPortNumber() { + String[] args = {"-p","65536"}; + assertThrows(CliError.class,()-> {ServerArguments.create(args);}); + } + + @Test + void validFlagPortNumberShort() { + String[] args = {"-p","100"}; + ServerArguments serverArguments = ServerArguments.create(args); + assertEquals(100, serverArguments.getPortNumber()); + } + + @Test + void defaultPortNumber() { + String[] args = {}; + ServerArguments serverArguments = ServerArguments.create(args); + + assertEquals(0, serverArguments.getPortNumber()); + } + + @Test + void defaultPortNumberInArg() { + String[] args = {"0"}; + ServerArguments serverArguments = ServerArguments.create(args); + + assertEquals(0, serverArguments.getPortNumber()); + } + + @Test + void validFlagPortNumber() { + String[] args = {"--port-number","200"}; + ServerArguments serverArguments = ServerArguments.create(args); + assertEquals(200, serverArguments.getPortNumber()); + } + + @Test + void validHelp() { + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + PrintStream originalOut = System.out; + System.setOut(new PrintStream(outContent)); + + try { + ServerArguments.create(new String[]{"--help"}); + + String output = outContent.toString().trim(); + + assertTrue(output.contains("Options for the Smithy Language Server:")); + assertTrue(output.contains("--help")); + assertTrue(output.contains("-h")); + assertTrue(output.contains("Print this help output.")); + assertTrue(output.contains("--port-number")); + assertTrue(output.contains("-p")); + assertTrue(output.contains("PORT_NUMBER")); + assertTrue(output.contains("The port number to be used by the Smithy Language Server.")); + assertTrue(output.contains("")); + assertTrue(output.contains("Positional port-number.")); + + } finally { + // Restore original System.out + System.setOut(originalOut); + } + } + + @Test + void invalidFlag() { + String[] args = {"--foo"}; + assertThrows(CliError.class,()-> {ServerArguments.create(args);}); + } +} From c049c77a19fb34e830b795ffb193e081310d3da8 Mon Sep 17 00:00:00 2001 From: Joe Wu Date: Fri, 11 Apr 2025 11:12:01 -0700 Subject: [PATCH 3/6] Removed ServerLauncher class and moved functions to Main directly --- .../java/software/amazon/smithy/lsp/Main.java | 31 +++- .../amazon/smithy/lsp/ServerLauncher.java | 95 ----------- .../amazon/smithy/lsp/ServerLauncherTest.java | 147 ------------------ 3 files changed, 27 insertions(+), 246 deletions(-) delete mode 100644 src/main/java/software/amazon/smithy/lsp/ServerLauncher.java delete mode 100644 src/test/java/software/amazon/smithy/lsp/ServerLauncherTest.java diff --git a/src/main/java/software/amazon/smithy/lsp/Main.java b/src/main/java/software/amazon/smithy/lsp/Main.java index 9265ae60..64184647 100644 --- a/src/main/java/software/amazon/smithy/lsp/Main.java +++ b/src/main/java/software/amazon/smithy/lsp/Main.java @@ -15,6 +15,11 @@ package software.amazon.smithy.lsp; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import org.eclipse.lsp4j.launch.LSPLauncher; + /** * Main launcher for the Language server, started by the editor. */ @@ -32,9 +37,27 @@ public static void main(String[] args) throws Exception { if (serverArguments.help()) { System.exit(0); } - ServerLauncher launcher = new ServerLauncher(serverArguments.getPortNumber()); - launcher.initConnection(); - launcher.launch(); - launcher.closeConnection(); + + launch(serverArguments); + } + + private static void launch(ServerArguments serverArguments) throws Exception { + if (serverArguments.getPortNumber() == ServerArguments.DEFAULT_PORT) { + startServer(System.in, System.out); + } else { + try (var socket = new Socket("localhost", serverArguments.getPortNumber())) { + startServer(socket.getInputStream(), socket.getOutputStream()); + } + } + } + + private static void startServer(InputStream in, OutputStream out) throws Exception { + var server = new SmithyLanguageServer(); + var launcher = LSPLauncher.createServerLauncher(server, in, out); + + var client = launcher.getRemoteProxy(); + server.connect(client); + + launcher.startListening().get(); } } diff --git a/src/main/java/software/amazon/smithy/lsp/ServerLauncher.java b/src/main/java/software/amazon/smithy/lsp/ServerLauncher.java deleted file mode 100644 index a8c6f0fa..00000000 --- a/src/main/java/software/amazon/smithy/lsp/ServerLauncher.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package software.amazon.smithy.lsp; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.util.concurrent.ExecutionException; -import java.util.logging.Logger; -import org.eclipse.lsp4j.jsonrpc.Launcher; -import org.eclipse.lsp4j.launch.LSPLauncher; -import org.eclipse.lsp4j.services.LanguageClient; - -class ServerLauncher { - private static final String SOCKET_HOST = "localhost"; - private static final Logger LOGGER = Logger.getLogger(ServerLauncher.class.getName()); - private final int portNumber; - private InputStream in; - private OutputStream out; - private Socket socket; - - ServerLauncher(int portNumber) { - this.portNumber = portNumber; - } - - Socket createSocket() throws IOException { - return new Socket(SOCKET_HOST, portNumber); - } - - void initConnection() throws IOException { - if (portNumber == ServerArguments.DEFAULT_PORT) { - in = System.in; - out = System.out; - } else { - socket = createSocket(); - in = socket.getInputStream(); - out = socket.getOutputStream(); - } - } - - InputStream getInputStream() { - return in; - } - - OutputStream getOutputStream() { - return out; - } - - void closeConnection() { - try { - if (socket != null) { - socket.close(); - } - } catch (IOException e) { - LOGGER.severe("Failed to close the socket"); - } - } - - void handleExit() { - closeConnection(); - System.exit(0); - } - - private InputStream exitOnClose(InputStream delegate) { - return new InputStream() { - @Override - public int read() throws IOException { - int result = delegate.read(); - if (result < 0) { - handleExit(); - } - return result; - } - }; - } - - /** - * Launch the LSP and wait for it to terminate. - * - */ - void launch() throws InterruptedException, ExecutionException { - SmithyLanguageServer server = new SmithyLanguageServer(); - Launcher launcher = LSPLauncher.createServerLauncher( - server, - exitOnClose(in), - out); - LanguageClient client = launcher.getRemoteProxy(); - server.connect(client); - launcher.startListening().get(); - } -} diff --git a/src/test/java/software/amazon/smithy/lsp/ServerLauncherTest.java b/src/test/java/software/amazon/smithy/lsp/ServerLauncherTest.java deleted file mode 100644 index 471922a8..00000000 --- a/src/test/java/software/amazon/smithy/lsp/ServerLauncherTest.java +++ /dev/null @@ -1,147 +0,0 @@ -package software.amazon.smithy.lsp; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.net.Socket; -import org.junit.jupiter.api.Test; - - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class ServerLauncherTest { - private static class MockInputStream extends InputStream { - private boolean closed = false; - @Override - public int read() throws IOException { - return -1; - } - - @Override - public void close() { - closed = true; - } - } - - private static class MockOutputStream extends OutputStream { - private boolean used = false; - private boolean closed = false; - - @Override - public void write(int b) { - used = true; - } - - @Override - public void close() { - closed = true; - } - } - - private static class TestServerLauncher extends ServerLauncher { - private final MockSocket mockSocket = new MockSocket(); - private boolean exited = false; - TestServerLauncher(int portNumber) { - super(portNumber); - } - - @Override - void handleExit() { - exited = true; - } - - boolean exited() { - return exited; - } - - @Override - Socket createSocket() { - return mockSocket; - } - - Socket getMockSocket() { - return mockSocket; - } - - } - - private static class MockSocket extends Socket { - private boolean isClosed = false; - private final MockInputStream inputStream = new MockInputStream(); - private final MockOutputStream outputStream = new MockOutputStream(); - - @Override - public InputStream getInputStream() { - return inputStream; - } - - @Override - public OutputStream getOutputStream() { - return outputStream; - } - - @Override - public void close() { - isClosed = true; - } - - public boolean isClosed() { - return isClosed; - } - } - - @Test - void testCustomPortConnection() throws IOException { - TestServerLauncher launcher = new TestServerLauncher(8080); - launcher.initConnection(); - - assertFalse(launcher.getMockSocket().isClosed()); - assertNotNull(launcher.getMockSocket().getInputStream()); - assertNotNull(launcher.getMockSocket().getOutputStream()); - } - - @Test - void testConnectionClose() throws IOException { - TestServerLauncher launcher = new TestServerLauncher(8080); - launcher.initConnection(); - launcher.closeConnection(); - - assertTrue(launcher.getMockSocket().isClosed()); - } - - @Test - void testDefaultPortConnection() throws IOException { - ByteArrayInputStream testIn = new ByteArrayInputStream("test".getBytes()); - ByteArrayOutputStream testOut = new ByteArrayOutputStream(); - PrintStream testPrintStream = new PrintStream(testOut); - - System.setIn(testIn); - System.setOut(testPrintStream); - - ServerLauncher launcher = new ServerLauncher(ServerArguments.DEFAULT_PORT); - launcher.initConnection(); - - assertEquals(System.in, launcher.getInputStream()); - assertEquals(System.out, launcher.getOutputStream()); - testIn.close(); - testOut.close(); - } - - @Test - void testLaunchWithDefaultPort() throws IOException { - TestServerLauncher launcher = new TestServerLauncher(ServerArguments.DEFAULT_PORT); - launcher.initConnection(); - assertDoesNotThrow(launcher::launch); - launcher.closeConnection(); - assertTrue(launcher.exited()); - } - -} - From 9a030a0ae3b5b3fd522cc67ed9e8d8c69678aae7 Mon Sep 17 00:00:00 2001 From: Joe Wu Date: Fri, 11 Apr 2025 15:15:49 -0700 Subject: [PATCH 4/6] Address comments, improved help messages --- .../java/software/amazon/smithy/lsp/Main.java | 8 +-- .../amazon/smithy/lsp/ServerArguments.java | 57 +++++++++++-------- .../smithy/lsp/ServerArgumentsTest.java | 22 +++---- 3 files changed, 45 insertions(+), 42 deletions(-) diff --git a/src/main/java/software/amazon/smithy/lsp/Main.java b/src/main/java/software/amazon/smithy/lsp/Main.java index 64184647..504b3828 100644 --- a/src/main/java/software/amazon/smithy/lsp/Main.java +++ b/src/main/java/software/amazon/smithy/lsp/Main.java @@ -42,12 +42,12 @@ public static void main(String[] args) throws Exception { } private static void launch(ServerArguments serverArguments) throws Exception { - if (serverArguments.getPortNumber() == ServerArguments.DEFAULT_PORT) { - startServer(System.in, System.out); - } else { - try (var socket = new Socket("localhost", serverArguments.getPortNumber())) { + if (serverArguments.useSocket()) { + try (var socket = new Socket("localhost", serverArguments.port())) { startServer(socket.getInputStream(), socket.getOutputStream()); } + } else { + startServer(System.in, System.out); } } diff --git a/src/main/java/software/amazon/smithy/lsp/ServerArguments.java b/src/main/java/software/amazon/smithy/lsp/ServerArguments.java index f0d8793c..77b22718 100644 --- a/src/main/java/software/amazon/smithy/lsp/ServerArguments.java +++ b/src/main/java/software/amazon/smithy/lsp/ServerArguments.java @@ -18,15 +18,15 @@ */ final class ServerArguments implements ArgumentReceiver { - static final int MIN_PORT = 0; - static final int MAX_PORT = 65535; - static final int DEFAULT_PORT = 0; // Default value for unset port number. - static final String HELP = "--help"; - static final String HELP_SHORT = "-h"; - static final String PORT_NUMBER = "--port-number"; - static final String PORT_NUMBER_SHORT = "-p"; - static final String PORT_NUMBER_POSITIONAL = ""; - private int portNumber = DEFAULT_PORT; + private static final int MIN_PORT = 0; + private static final int MAX_PORT = 65535; + private static final int DEFAULT_PORT = 0; // Default value for unset port number. + private static final String HELP = "--help"; + private static final String HELP_SHORT = "-h"; + private static final String PORT = "--port"; + private static final String PORT_SHORT = "-p"; + private static final String PORT_POSITIONAL = ""; + private int port = DEFAULT_PORT; private boolean help = false; @@ -39,7 +39,7 @@ static ServerArguments create(String[] args) { serverArguments.printHelp(arguments); } if (!positional.isEmpty()) { - serverArguments.portNumber = serverArguments.validatePortNumber(positional.getFirst()); + serverArguments.port = serverArguments.validatePortNumber(positional.getFirst()); } return serverArguments; } @@ -47,9 +47,12 @@ static ServerArguments create(String[] args) { @Override public void registerHelp(HelpPrinter printer) { printer.option(HELP, HELP_SHORT, "Print this help output."); - printer.param(PORT_NUMBER, PORT_NUMBER_SHORT, "PORT_NUMBER", - "The port number to be used by the Smithy Language Server. Default port number is 0 if not specified."); - printer.option(PORT_NUMBER_POSITIONAL, null, "Positional port-number."); + printer.param(PORT, PORT_SHORT, "PORT", + "The port to use for talking to the client. When not specified, or set to 0, " + + "standard in/out is used. Standard in/out is preferred, " + + "so usually this shouldn't be specified."); + printer.option(PORT_POSITIONAL, null, "Deprecated: use --port instead. When not specified, or set to 0, " + + "standard in/out is used. Standard in/out is preferred, so usually this shouldn't be specified."); } @Override @@ -63,40 +66,44 @@ public boolean testOption(String name) { @Override public Consumer testParameter(String name) { - if (name.equals(PORT_NUMBER_SHORT) || name.equals(PORT_NUMBER)) { + if (name.equals(PORT_SHORT) || name.equals(PORT)) { return value -> { - portNumber = validatePortNumber(value); + port = validatePortNumber(value); }; } return null; } - public int getPortNumber() { - return portNumber; + int port() { + return port; } - public boolean help() { + boolean help() { return help; } - public int validatePortNumber(String portNumberStr) { + public boolean useSocket() { + return port != 0; + } + + private int validatePortNumber(String portStr) { try { - int portNumber = Integer.parseInt(portNumberStr); + int portNumber = Integer.parseInt(portStr); if (portNumber < MIN_PORT || portNumber > MAX_PORT) { - throw new CliError("Invalid port number!"); + throw new CliError("Invalid port number: should be an integer between " + + MIN_PORT + " and " + MAX_PORT + ", inclusive."); } else { return portNumber; } } catch (NumberFormatException e) { - throw new CliError("Invalid port number!"); + throw new CliError("Invalid port number: Can not parse " + portStr); } } - private void printHelp(Arguments arguments) { CliPrinter printer = CliPrinter.fromOutputStream(System.out); - HelpPrinter helpPrinter = HelpPrinter.fromArguments("java -jar smithy-lsp.jar", arguments); - helpPrinter.summary("Options for the Smithy Language Server:"); + HelpPrinter helpPrinter = HelpPrinter.fromArguments("smithy-language-server", arguments); + helpPrinter.summary("Run the Smithy Language Server."); helpPrinter.print(AnsiColorFormatter.AUTO, printer); printer.flush(); } diff --git a/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java b/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java index b5ada80d..80d677d3 100644 --- a/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java +++ b/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java @@ -15,7 +15,7 @@ public class ServerArgumentsTest { void validPositionalPortNumber() { String[] args = {"1"}; ServerArguments serverArguments = ServerArguments.create(args); - assertEquals(1, serverArguments.getPortNumber()); + assertEquals(1, serverArguments.port()); } @Test @@ -34,7 +34,7 @@ void invalidFlagPortNumber() { void validFlagPortNumberShort() { String[] args = {"-p","100"}; ServerArguments serverArguments = ServerArguments.create(args); - assertEquals(100, serverArguments.getPortNumber()); + assertEquals(100, serverArguments.port()); } @Test @@ -42,7 +42,7 @@ void defaultPortNumber() { String[] args = {}; ServerArguments serverArguments = ServerArguments.create(args); - assertEquals(0, serverArguments.getPortNumber()); + assertEquals(0, serverArguments.port()); } @Test @@ -50,14 +50,14 @@ void defaultPortNumberInArg() { String[] args = {"0"}; ServerArguments serverArguments = ServerArguments.create(args); - assertEquals(0, serverArguments.getPortNumber()); + assertEquals(0, serverArguments.port()); } @Test void validFlagPortNumber() { - String[] args = {"--port-number","200"}; + String[] args = {"--port","200"}; ServerArguments serverArguments = ServerArguments.create(args); - assertEquals(200, serverArguments.getPortNumber()); + assertEquals(200, serverArguments.port()); } @Test @@ -71,16 +71,12 @@ void validHelp() { String output = outContent.toString().trim(); - assertTrue(output.contains("Options for the Smithy Language Server:")); assertTrue(output.contains("--help")); assertTrue(output.contains("-h")); - assertTrue(output.contains("Print this help output.")); - assertTrue(output.contains("--port-number")); + assertTrue(output.contains("--port")); assertTrue(output.contains("-p")); - assertTrue(output.contains("PORT_NUMBER")); - assertTrue(output.contains("The port number to be used by the Smithy Language Server.")); - assertTrue(output.contains("")); - assertTrue(output.contains("Positional port-number.")); + assertTrue(output.contains("PORT")); + assertTrue(output.contains("")); } finally { // Restore original System.out From 0bc0b252bc059ae03ac409cecd12b1bdb0f3a903 Mon Sep 17 00:00:00 2001 From: Joe Wu Date: Mon, 14 Apr 2025 14:48:41 -0700 Subject: [PATCH 5/6] Addres comment, move printhelp to main from serverargument, add helper function for error --- .../java/software/amazon/smithy/lsp/Main.java | 13 +++++ .../amazon/smithy/lsp/ServerArguments.java | 22 +++------ .../smithy/lsp/ServerArgumentsTest.java | 49 ++++++++++--------- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/main/java/software/amazon/smithy/lsp/Main.java b/src/main/java/software/amazon/smithy/lsp/Main.java index 504b3828..d7a7054f 100644 --- a/src/main/java/software/amazon/smithy/lsp/Main.java +++ b/src/main/java/software/amazon/smithy/lsp/Main.java @@ -19,6 +19,9 @@ import java.io.OutputStream; import java.net.Socket; import org.eclipse.lsp4j.launch.LSPLauncher; +import software.amazon.smithy.cli.AnsiColorFormatter; +import software.amazon.smithy.cli.CliPrinter; +import software.amazon.smithy.cli.HelpPrinter; /** * Main launcher for the Language server, started by the editor. @@ -35,6 +38,7 @@ private Main() { public static void main(String[] args) throws Exception { var serverArguments = ServerArguments.create(args); if (serverArguments.help()) { + printHelp(serverArguments); System.exit(0); } @@ -60,4 +64,13 @@ private static void startServer(InputStream in, OutputStream out) throws Excepti launcher.startListening().get(); } + + private static void printHelp(ServerArguments serverArguments) { + CliPrinter printer = CliPrinter.fromOutputStream(System.out); + HelpPrinter helpPrinter = new HelpPrinter("smithy-language-server"); + serverArguments.registerHelp(helpPrinter); + helpPrinter.summary("Run the Smithy Language Server."); + helpPrinter.print(AnsiColorFormatter.AUTO, printer); + printer.flush(); + } } diff --git a/src/main/java/software/amazon/smithy/lsp/ServerArguments.java b/src/main/java/software/amazon/smithy/lsp/ServerArguments.java index 77b22718..345bb3ba 100644 --- a/src/main/java/software/amazon/smithy/lsp/ServerArguments.java +++ b/src/main/java/software/amazon/smithy/lsp/ServerArguments.java @@ -6,11 +6,9 @@ package software.amazon.smithy.lsp; import java.util.function.Consumer; -import software.amazon.smithy.cli.AnsiColorFormatter; import software.amazon.smithy.cli.ArgumentReceiver; import software.amazon.smithy.cli.Arguments; import software.amazon.smithy.cli.CliError; -import software.amazon.smithy.cli.CliPrinter; import software.amazon.smithy.cli.HelpPrinter; /** @@ -35,9 +33,6 @@ static ServerArguments create(String[] args) { var serverArguments = new ServerArguments(); arguments.addReceiver(serverArguments); var positional = arguments.getPositional(); - if (serverArguments.help()) { - serverArguments.printHelp(arguments); - } if (!positional.isEmpty()) { serverArguments.port = serverArguments.validatePortNumber(positional.getFirst()); } @@ -82,7 +77,7 @@ boolean help() { return help; } - public boolean useSocket() { + boolean useSocket() { return port != 0; } @@ -90,22 +85,17 @@ private int validatePortNumber(String portStr) { try { int portNumber = Integer.parseInt(portStr); if (portNumber < MIN_PORT || portNumber > MAX_PORT) { - throw new CliError("Invalid port number: should be an integer between " - + MIN_PORT + " and " + MAX_PORT + ", inclusive."); + throw invalidPort(portStr); } else { return portNumber; } } catch (NumberFormatException e) { - throw new CliError("Invalid port number: Can not parse " + portStr); + throw invalidPort(portStr); } } - private void printHelp(Arguments arguments) { - CliPrinter printer = CliPrinter.fromOutputStream(System.out); - HelpPrinter helpPrinter = HelpPrinter.fromArguments("smithy-language-server", arguments); - helpPrinter.summary("Run the Smithy Language Server."); - helpPrinter.print(AnsiColorFormatter.AUTO, printer); - printer.flush(); + private static CliError invalidPort(String portStr) { + return new CliError("Invalid port number: expected an integer between " + + MIN_PORT + " and " + MAX_PORT + ", inclusive. Was: " + portStr); } - } diff --git a/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java b/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java index 80d677d3..48cb92c6 100644 --- a/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java +++ b/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -16,12 +17,15 @@ void validPositionalPortNumber() { String[] args = {"1"}; ServerArguments serverArguments = ServerArguments.create(args); assertEquals(1, serverArguments.port()); + assertFalse(serverArguments.help()); + assertTrue(serverArguments.useSocket()); } @Test void invalidPositionalPortNumber() { String[] args = {"65536"}; assertThrows(CliError.class,()-> {ServerArguments.create(args);}); + } @Test @@ -35,6 +39,8 @@ void validFlagPortNumberShort() { String[] args = {"-p","100"}; ServerArguments serverArguments = ServerArguments.create(args); assertEquals(100, serverArguments.port()); + assertFalse(serverArguments.help()); + assertTrue(serverArguments.useSocket()); } @Test @@ -43,14 +49,17 @@ void defaultPortNumber() { ServerArguments serverArguments = ServerArguments.create(args); assertEquals(0, serverArguments.port()); + assertFalse(serverArguments.help()); + assertFalse(serverArguments.useSocket()); } @Test void defaultPortNumberInArg() { String[] args = {"0"}; ServerArguments serverArguments = ServerArguments.create(args); - assertEquals(0, serverArguments.port()); + assertFalse(serverArguments.help()); + assertFalse(serverArguments.useSocket()); } @Test @@ -61,32 +70,24 @@ void validFlagPortNumber() { } @Test - void validHelp() { - ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - PrintStream originalOut = System.out; - System.setOut(new PrintStream(outContent)); - - try { - ServerArguments.create(new String[]{"--help"}); - - String output = outContent.toString().trim(); - - assertTrue(output.contains("--help")); - assertTrue(output.contains("-h")); - assertTrue(output.contains("--port")); - assertTrue(output.contains("-p")); - assertTrue(output.contains("PORT")); - assertTrue(output.contains("")); + void invalidFlag() { + String[] args = {"--foo"}; + assertThrows(CliError.class,()-> {ServerArguments.create(args);}); + } - } finally { - // Restore original System.out - System.setOut(originalOut); - } + @Test + void validHelpShort() { + String[] args = {"-h"}; + ServerArguments serverArguments = ServerArguments.create(args); + assertTrue(serverArguments.help()); + assertFalse(serverArguments.useSocket()); } @Test - void invalidFlag() { - String[] args = {"--foo"}; - assertThrows(CliError.class,()-> {ServerArguments.create(args);}); + void validHelp() { + String[] args = {"--help"}; + ServerArguments serverArguments = ServerArguments.create(args); + assertTrue(serverArguments.help()); + assertFalse(serverArguments.useSocket()); } } From 3a50a9bea8d0554a7a4088081dc6519a1a105638 Mon Sep 17 00:00:00 2001 From: Joe Wu Date: Mon, 14 Apr 2025 15:09:02 -0700 Subject: [PATCH 6/6] Add tests for default port using flag arguments. --- .../amazon/smithy/lsp/ServerArgumentsTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java b/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java index 48cb92c6..048b83a1 100644 --- a/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java +++ b/src/test/java/software/amazon/smithy/lsp/ServerArgumentsTest.java @@ -62,6 +62,24 @@ void defaultPortNumberInArg() { assertFalse(serverArguments.useSocket()); } + @Test + void defaultPortNumberWithFlag() { + String[] args = {"--port","0"}; + ServerArguments serverArguments = ServerArguments.create(args); + assertEquals(0, serverArguments.port()); + assertFalse(serverArguments.help()); + assertFalse(serverArguments.useSocket()); + } + + @Test + void defaultPortNumberWithShotFlag() { + String[] args = {"-p","0"}; + ServerArguments serverArguments = ServerArguments.create(args); + assertEquals(0, serverArguments.port()); + assertFalse(serverArguments.help()); + assertFalse(serverArguments.useSocket()); + } + @Test void validFlagPortNumber() { String[] args = {"--port","200"};