Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2dbce08
build(ls): initialized language server project
mjossdev Apr 28, 2023
1c5ff55
build(ls): add lsp4j dependency
mjossdev Apr 28, 2023
6c32d49
feat(ls): create language server
mjossdev Apr 28, 2023
dc21959
feat(ls): redirect logs to client
mjossdev Apr 29, 2023
d79ae12
feat(ls): implement exit
mjossdev Apr 30, 2023
b74465e
feat(ls): remove untested server-port option
mjossdev Apr 30, 2023
7b6eaac
feat(ide): add run config for remote debugging
mjossdev Apr 30, 2023
1720f6a
feat(ls): basic text document service
mjossdev Apr 30, 2023
6eed552
feat(ls): show diagnostics via LSP
PascalHonegger May 1, 2023
c8a6889
feat(ci): release samt-ls.jar
PascalHonegger May 4, 2023
0e09bb1
feat(ls): report diagnostics and keep up with changes
mjossdev May 4, 2023
02ceb43
fix(ls): if a workspace contains nested folders, only use the outermo…
mjossdev May 5, 2023
72b1550
refactor(common): use URI instead of String for paths
PascalHonegger May 7, 2023
2537980
feat(common): ensure .samt directory is never read as a file
PascalHonegger May 7, 2023
d5dae9f
feat(cli): add more tests for ASTPrinter
PascalHonegger May 7, 2023
b963ffb
feat(parser): make name accessible in base import node
PascalHonegger May 7, 2023
185f0eb
feat(common): use `||` instead of `or`
PascalHonegger May 7, 2023
41bdf92
feat(semantic): AST node for constraint should be whole constraint an…
PascalHonegger May 7, 2023
4b1ff7a
feat(ls): only look for single workspace as same file shouldn't exist…
PascalHonegger May 7, 2023
b0b18b4
fix(ci): satisfy Qodana
mjossdev May 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
uses: gradle/gradle-build-action@v2

- name: Build CLI
run: ./gradlew --no-daemon :cli:shadowDistZip :cli:shadowDistTar
run: ./gradlew --no-daemon :cli:shadowDistZip :cli:shadowDistTar :language-server:shadowJar

- name: Rename cli-shadow to cli
run: |
Expand All @@ -32,4 +32,5 @@ jobs:
files: |
cli/build/distributions/cli.zip
cli/build/distributions/cli.tar
language-server/build/libs/samt-ls.jar
fail_on_unmatched_files: true
15 changes: 15 additions & 0 deletions .idea/runConfigurations/Language_Server_Debug.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/src/main/kotlin/tools/samt/cli/ASTPrinter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ internal object ASTPrinter {
}

private fun dumpInfo(node: Node): String? = when (node) {
is FileNode -> gray(node.sourceFile.absolutePath)
is FileNode -> gray(node.sourceFile.path.path)
is RequestResponseOperationNode -> if (node.isAsync) red("async") else null
is IdentifierNode -> yellow(node.name)
is ImportBundleIdentifierNode -> yellow(node.name) + if (node.isWildcard) yellow(".*") else ""
Expand Down
3 changes: 2 additions & 1 deletion cli/src/main/kotlin/tools/samt/cli/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tools.samt.cli
import com.beust.jcommander.JCommander
import com.github.ajalt.mordant.terminal.Terminal
import tools.samt.common.DiagnosticController
import kotlin.io.path.Path

fun main(args: Array<String>) {
val cliArgs = CliArgs()
Expand All @@ -24,7 +25,7 @@ fun main(args: Array<String>) {

val terminal = Terminal()

val workingDirectory = System.getProperty("user.dir")
val workingDirectory = Path(System.getProperty("user.dir")).toUri()

val controller = DiagnosticController(workingDirectory)

Expand Down
2 changes: 1 addition & 1 deletion cli/src/main/kotlin/tools/samt/cli/CliCompiler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal fun compile(command: CompileCommand, controller: DiagnosticController)
// attempt to parse each source file into an AST
val fileNodes = buildList {
for (source in sourceFiles) {
val context = controller.createContext(source)
val context = controller.getOrCreateContext(source)
val tokenStream = Lexer.scan(source.content.reader(), context)

if (context.hasErrors()) {
Expand Down
4 changes: 2 additions & 2 deletions cli/src/main/kotlin/tools/samt/cli/CliDumper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ internal fun dump(command: DumpCommand, terminal: Terminal, controller: Diagnost
// attempt to parse each source file into an AST
val fileNodes = buildList {
for (source in sourceFiles) {
val context = controller.createContext(source)
val context = controller.getOrCreateContext(source)

if (dumpAll || command.dumpTokens) {
// create duplicate scan because sequence can only be iterated once
val tokenStream = Lexer.scan(source.content.reader(), context)
terminal.println("Tokens for ${source.absolutePath}:")
terminal.println("Tokens for ${source.path}:")
terminal.println(TokenPrinter.dump(tokenStream))
// clear the diagnostic messages so that messages aren't duplicated
context.messages.clear()
Expand Down
33 changes: 5 additions & 28 deletions cli/src/main/kotlin/tools/samt/cli/CliFileResolution.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,11 @@ package tools.samt.cli

import tools.samt.common.DiagnosticController
import tools.samt.common.SourceFile
import tools.samt.common.collectSamtFiles
import tools.samt.common.readSamtSource
import java.io.File

internal fun List<String>.readSamtSourceFiles(controller: DiagnosticController): List<SourceFile> {
val files = map { File(it) }.ifEmpty { gatherSamtFiles(controller.workingDirectoryAbsolutePath) }
internal fun List<String>.readSamtSourceFiles(controller: DiagnosticController): List<SourceFile> =
map { File(it) }.ifEmpty { collectSamtFiles(controller.workingDirectory) }
.readSamtSource(controller)

return buildList {
for (file in files) {
if (!file.exists()) {
controller.reportGlobalError("File '${file.canonicalPath}' does not exist")
continue
}

if (!file.canRead()) {
controller.reportGlobalError("File '${file.canonicalPath}' cannot be read, bad file permissions?")
continue
}

if (file.extension != "samt") {
controller.reportGlobalError("File '${file.canonicalPath}' must end in .samt")
continue
}

add(SourceFile(file.canonicalPath, content = file.readText()))
}
}
}

internal fun gatherSamtFiles(directory: String): List<File> {
val dir = File(directory)
return dir.walkTopDown().filter { it.isFile && it.extension == "samt" }.toList()
}
2 changes: 1 addition & 1 deletion cli/src/main/kotlin/tools/samt/cli/CliWrapper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.io.File
import java.net.URL

internal fun wrapper(command: WrapperCommand, terminal: Terminal, controller: DiagnosticController) {
val workingDirectory = File(controller.workingDirectoryAbsolutePath)
val workingDirectory = File(controller.workingDirectory)
val dotSamtDirectory = File(workingDirectory, ".samt")

if (command.init) {
Expand Down
16 changes: 5 additions & 11 deletions cli/src/main/kotlin/tools/samt/cli/DiagnosticFormatter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.github.ajalt.mordant.rendering.TextColors.*
import com.github.ajalt.mordant.rendering.TextStyles.*
import com.github.ajalt.mordant.terminal.Terminal
import tools.samt.common.*
import java.io.File

internal class DiagnosticFormatter(
private val diagnosticController: DiagnosticController,
Expand Down Expand Up @@ -119,11 +118,6 @@ internal class DiagnosticFormatter(
DiagnosticSeverity.Info -> formatTextForSeverity("INFO:", severity, withBold = true)
}

private fun formatFilePathRelativeToWorkingDirectory(filePath: String): String {
val workingDirectory = diagnosticController.workingDirectoryAbsolutePath
return filePath.removePrefix(workingDirectory).removePrefix(File.separator)
}

private fun formatGlobalMessage(message: DiagnosticGlobalMessage): String = buildString {
append(formatSeverityIndicator(message.severity))
append(" ")
Expand All @@ -140,14 +134,14 @@ internal class DiagnosticFormatter(
appendLine()

val errorSourceFilePath = if (message.highlights.isNotEmpty()) {
message.highlights.first().location.source.absolutePath
message.highlights.first().location.source.path
} else {
context.source.absolutePath
context.source.path
}

// -----> <file path>:<location>
append(gray(" ---> "))
append(formatFilePathRelativeToWorkingDirectory(errorSourceFilePath))
append(diagnosticController.workingDirectory.relativize(errorSourceFilePath))
if (message.highlights.isNotEmpty()) {
val firstHighlight = message.highlights.first()
val firstHighlightLocation = firstHighlight.location
Expand All @@ -173,8 +167,8 @@ internal class DiagnosticFormatter(
require(highlights.isNotEmpty())

// group highlights by source file
val mainSourceFileAbsolutePath = highlights.first().location.source.absolutePath
val highlightsBySourceFile = highlights.groupBy { it.location.source.absolutePath }
val mainSourceFileAbsolutePath = highlights.first().location.source.path
val highlightsBySourceFile = highlights.groupBy { it.location.source.path }

// print the highlights for the main source file
val mainSourceFileHighlights = highlightsBySourceFile.getValue(mainSourceFileAbsolutePath)
Expand Down
100 changes: 58 additions & 42 deletions cli/src/test/kotlin/tools/samt/cli/ASTPrinterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import tools.samt.common.SourceFile
import tools.samt.lexer.Lexer
import tools.samt.parser.FileNode
import tools.samt.parser.Parser
import java.net.URI
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
Expand All @@ -14,6 +15,7 @@ class ASTPrinterTest {
fun `correctly formats an AST dump`() {
val fileNode = parse("""
import foo.bar.baz.*
import foo.bar.baz.A as B

package test.stuff

Expand All @@ -22,8 +24,9 @@ class ASTPrinterTest {
age: Integer(0..150)
}

record B {}
enum E { A, B, C }

@Description("This is a service")
service MyService {
testmethod(foo: A): B
}
Expand All @@ -39,52 +42,65 @@ class ASTPrinterTest {
│ ├─IdentifierNode foo <1:8>
│ ├─IdentifierNode bar <1:12>
│ └─IdentifierNode baz <1:16>
├─PackageDeclarationNode <3:1>
│ └─BundleIdentifierNode test.stuff <3:9>
│ ├─IdentifierNode test <3:9>
│ └─IdentifierNode stuff <3:14>
├─RecordDeclarationNode <5:1>
│ ├─IdentifierNode A <5:8>
│ ├─RecordFieldNode <6:3>
│ │ ├─IdentifierNode name <6:3>
│ │ └─CallExpressionNode <6:9>
│ │ ├─BundleIdentifierNode String <6:9>
│ │ │ └─IdentifierNode String <6:9>
│ │ ├─RangeExpressionNode <6:16>
│ │ │ ├─IntegerNode 10 <6:16>
│ │ │ └─IntegerNode 20 <6:20>
│ │ └─CallExpressionNode <6:24>
│ │ ├─BundleIdentifierNode pattern <6:24>
│ │ │ └─IdentifierNode pattern <6:24>
│ │ └─StringNode "hehe" <6:32>
│ └─RecordFieldNode <7:3>
│ ├─IdentifierNode age <7:3>
│ └─CallExpressionNode <7:8>
│ ├─BundleIdentifierNode Integer <7:8>
│ │ └─IdentifierNode Integer <7:8>
│ └─RangeExpressionNode <7:16>
│ ├─IntegerNode 0 <7:16>
│ └─IntegerNode 150 <7:19>
├─RecordDeclarationNode <10:1>
│ └─IdentifierNode B <10:8>
└─ServiceDeclarationNode <12:1>
├─IdentifierNode MyService <12:9>
└─RequestResponseOperationNode <13:3>
├─IdentifierNode testmethod <13:3>
├─OperationParameterNode <13:14>
│ ├─IdentifierNode foo <13:14>
│ └─BundleIdentifierNode A <13:19>
│ └─IdentifierNode A <13:19>
└─BundleIdentifierNode B <13:23>
└─IdentifierNode B <13:23>
├─TypeImportNode <2:1>
│ ├─ImportBundleIdentifierNode foo.bar.baz.A <2:8>
│ │ ├─IdentifierNode foo <2:8>
│ │ ├─IdentifierNode bar <2:12>
│ │ ├─IdentifierNode baz <2:16>
│ │ └─IdentifierNode A <2:20>
│ └─IdentifierNode B <2:25>
├─PackageDeclarationNode <4:1>
│ └─BundleIdentifierNode test.stuff <4:9>
│ ├─IdentifierNode test <4:9>
│ └─IdentifierNode stuff <4:14>
├─RecordDeclarationNode <6:1>
│ ├─IdentifierNode A <6:8>
│ ├─RecordFieldNode <7:3>
│ │ ├─IdentifierNode name <7:3>
│ │ └─CallExpressionNode <7:9>
│ │ ├─BundleIdentifierNode String <7:9>
│ │ │ └─IdentifierNode String <7:9>
│ │ ├─RangeExpressionNode <7:16>
│ │ │ ├─IntegerNode 10 <7:16>
│ │ │ └─IntegerNode 20 <7:20>
│ │ └─CallExpressionNode <7:24>
│ │ ├─BundleIdentifierNode pattern <7:24>
│ │ │ └─IdentifierNode pattern <7:24>
│ │ └─StringNode "hehe" <7:32>
│ └─RecordFieldNode <8:3>
│ ├─IdentifierNode age <8:3>
│ └─CallExpressionNode <8:8>
│ ├─BundleIdentifierNode Integer <8:8>
│ │ └─IdentifierNode Integer <8:8>
│ └─RangeExpressionNode <8:16>
│ ├─IntegerNode 0 <8:16>
│ └─IntegerNode 150 <8:19>
├─EnumDeclarationNode <11:1>
│ ├─IdentifierNode E <11:6>
│ ├─IdentifierNode A <11:10>
│ ├─IdentifierNode B <11:13>
│ └─IdentifierNode C <11:16>
└─ServiceDeclarationNode <14:1>
├─IdentifierNode MyService <14:9>
├─RequestResponseOperationNode <15:3>
│ ├─IdentifierNode testmethod <15:3>
│ ├─OperationParameterNode <15:14>
│ │ ├─IdentifierNode foo <15:14>
│ │ └─BundleIdentifierNode A <15:19>
│ │ └─IdentifierNode A <15:19>
│ └─BundleIdentifierNode B <15:23>
│ └─IdentifierNode B <15:23>
└─AnnotationNode <13:1>
├─IdentifierNode Description <13:2>
└─StringNode "This is a service" <13:14>
""".trimIndent().trim(), dumpWithoutColorCodes.trimIndent().trim())
}

private fun parse(source: String): FileNode {
val filePath = "/tmp/ASTPrinterTest.samt"
val filePath = URI("file:///tmp/ASTPrinterTest.samt")
val sourceFile = SourceFile(filePath, source)
val diagnosticController = DiagnosticController("/tmp")
val diagnosticContext = diagnosticController.createContext(sourceFile)
val diagnosticController = DiagnosticController(URI("file:///tmp"))
val diagnosticContext = diagnosticController.getOrCreateContext(sourceFile)
val stream = Lexer.scan(source.reader(), diagnosticContext)
val fileTree = Parser.parse(sourceFile, stream, diagnosticContext)
assertFalse(diagnosticContext.hasErrors(), "Expected no errors, but had errors")
Expand Down
16 changes: 8 additions & 8 deletions cli/src/test/kotlin/tools/samt/cli/DiagnosticFormatterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ import tools.samt.lexer.Lexer
import tools.samt.parser.EnumDeclarationNode
import tools.samt.parser.FileNode
import tools.samt.parser.Parser
import java.net.URI
import kotlin.io.path.Path
import kotlin.io.path.absolutePathString
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse

class DiagnosticFormatterTest {
@Test
fun `global messages`() {
val controller = DiagnosticController("/tmp")
val controller = DiagnosticController(URI("file:///tmp"))
controller.reportGlobalError("This is a global error")
controller.reportGlobalWarning("This is a global warning")
controller.reportGlobalInfo("This is a global info")
Expand All @@ -40,12 +40,12 @@ class DiagnosticFormatterTest {

@Test
fun `file messages with no highlights`() {
val baseDirectory = Path("/tmp").absolutePathString()
val filePath = Path("/tmp", "test.txt").absolutePathString()
val baseDirectory = Path("/tmp").toUri()
val filePath = Path("/tmp", "test.txt").toUri()
val controller = DiagnosticController(baseDirectory)
val source = ""
val sourceFile = SourceFile(filePath, source)
val context = controller.createContext(sourceFile)
val context = controller.getOrCreateContext(sourceFile)

context.error {
message("some error")
Expand Down Expand Up @@ -480,11 +480,11 @@ class DiagnosticFormatterTest {
}

private fun parse(source: String): Triple<FileNode, DiagnosticContext, DiagnosticController> {
val baseDirectory = Path("/tmp").absolutePathString()
val filePath = Path("/tmp", "DiagnosticFormatterTest.samt").absolutePathString()
val baseDirectory = Path("/tmp").toUri()
val filePath = Path("/tmp", "DiagnosticFormatterTest.samt").toUri()
val sourceFile = SourceFile(filePath, source)
val diagnosticController = DiagnosticController(baseDirectory)
val diagnosticContext = diagnosticController.createContext(sourceFile)
val diagnosticContext = diagnosticController.getOrCreateContext(sourceFile)
val stream = Lexer.scan(source.reader(), diagnosticContext)
val fileTree = Parser.parse(sourceFile, stream, diagnosticContext)
assertFalse(diagnosticContext.hasErrors(), "Expected no errors, but had errors")
Expand Down
7 changes: 4 additions & 3 deletions cli/src/test/kotlin/tools/samt/cli/TokenPrinterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import tools.samt.common.DiagnosticController
import tools.samt.common.SourceFile
import tools.samt.lexer.Lexer
import tools.samt.lexer.Token
import java.net.URI
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
Expand Down Expand Up @@ -46,10 +47,10 @@ class TokenPrinterTest {
}

private fun parse(source: String): Sequence<Token> {
val filePath = "/tmp/TokenPrinterTest.samt"
val filePath = URI("file:///tmp/TokenPrinterTest.samt")
val sourceFile = SourceFile(filePath, source)
val diagnosticController = DiagnosticController("/tmp")
val diagnosticContext = diagnosticController.createContext(sourceFile)
val diagnosticController = DiagnosticController(URI("file:///tmp"))
val diagnosticContext = diagnosticController.getOrCreateContext(sourceFile)
val stream = Lexer.scan(source.reader(), diagnosticContext)
assertFalse(diagnosticContext.hasErrors(), "Expected no errors, but had errors")
return stream
Expand Down
Loading