mirror of
https://github.com/fluencelabs/aqua.git
synced 2025-04-24 14:32:13 +00:00
feat(language-server): Add types for types in LSP API (#1078)
This commit is contained in:
parent
9423ffc509
commit
3cd31c5827
29
integration-tests/lsp-aqua/types.aqua
Normal file
29
integration-tests/lsp-aqua/types.aqua
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
aqua Types
|
||||||
|
|
||||||
|
alias Top: ⊤
|
||||||
|
alias Bottom: ⊥
|
||||||
|
|
||||||
|
alias Number: u32
|
||||||
|
alias String: string
|
||||||
|
alias Array: []string
|
||||||
|
alias Stream: *string
|
||||||
|
alias Option: ?string
|
||||||
|
|
||||||
|
data Struct:
|
||||||
|
a: Number
|
||||||
|
b: String
|
||||||
|
c: Array
|
||||||
|
d: Option
|
||||||
|
|
||||||
|
service Srv("srv"):
|
||||||
|
noop(srvArg: string)
|
||||||
|
|
||||||
|
ability Ability:
|
||||||
|
a: Number
|
||||||
|
b: String
|
||||||
|
|
||||||
|
func nilArrow():
|
||||||
|
Srv.noop("")
|
||||||
|
|
||||||
|
func fullArrow(a: string, b: u32) -> string, u32:
|
||||||
|
<- a, b
|
@ -18,16 +18,14 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc & npm run compile-aqua",
|
"build": "tsc & npm run compile-aqua",
|
||||||
"test": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles",
|
"test": "NODE_OPTIONS=--experimental-vm-modules jest --detectOpenHandles",
|
||||||
|
"test:lsp": "NODE_OPTIONS=--experimental-vm-modules jest src/__test__/lsp-types.spec.ts --detectOpenHandles",
|
||||||
"examples": "jest",
|
"examples": "jest",
|
||||||
"pubsub": "node -r ts-node/register src/pubsub.ts",
|
|
||||||
"exec": "npm run compile-aqua && npm run prettify-compiled && node -r ts-node/register src/index.ts",
|
"exec": "npm run compile-aqua && npm run prettify-compiled && node -r ts-node/register src/index.ts",
|
||||||
"run": "node -r ts-node/register src/index.ts",
|
"run": "node -r ts-node/register src/index.ts",
|
||||||
"compile-aqua": "node --loader ts-node/esm ./src/compile.ts",
|
"compile-aqua": "node --loader ts-node/esm ./src/compile.ts",
|
||||||
"compile-aqua:air": "aqua -i ./aqua/ -o ./compiled-air -a",
|
"compile-aqua:air": "aqua -i ./aqua/ -o ./compiled-air -a",
|
||||||
"prettify-compiled": "prettier --write src/compiled",
|
"prettify-compiled": "prettier --write src/compiled",
|
||||||
"prettify": "prettier --write src",
|
"prettify": "prettier --write src"
|
||||||
"aqua": "aqua",
|
|
||||||
"do": "aqua dist deploy --addr /dns4/kras-04.fluence.dev/tcp/19001/wss/p2p/12D3KooWFEwNWcHqi9rtsmDhsYcDbRUCDXH84RC4FW6UfsFWaoHi --config-path deploy.json --service tsOracle"
|
|
||||||
},
|
},
|
||||||
"prettier": {},
|
"prettier": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
103
integration-tests/src/__test__/lsp-types.spec.ts
Normal file
103
integration-tests/src/__test__/lsp-types.spec.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
describe("Testing LSP types", () => {
|
||||||
|
it("dummy", async () => {
|
||||||
|
expect(true).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// FIXME: these tests work only with LSP ESM build (ModuleKind.ESModule in build.sbt)
|
||||||
|
/*
|
||||||
|
import {
|
||||||
|
AbilityType,
|
||||||
|
AquaLSP, ArrayType, BottomType,
|
||||||
|
OptionType,
|
||||||
|
ScalarType, StreamType, StructType,
|
||||||
|
TopType,
|
||||||
|
Type, ServiceType, ArrowType
|
||||||
|
} from '@fluencelabs/aqua-language-server-api/aqua-lsp-api';
|
||||||
|
|
||||||
|
describe("Testing LSP types", () => {
|
||||||
|
it("check types in aqua file", async () => {
|
||||||
|
const compiled = await AquaLSP.compile("lsp-aqua/types.aqua", {})
|
||||||
|
const types = compiled.tokens.map(ti => ti.type)
|
||||||
|
|
||||||
|
const isTop = (type: Type): type is TopType => type.tag === "top";
|
||||||
|
const tops = types.filter(isTop)
|
||||||
|
expect(tops).toHaveLength(1)
|
||||||
|
|
||||||
|
const isBottom = (type: Type): type is BottomType => type.tag === "bottom";
|
||||||
|
const bottom = types.filter(isBottom)
|
||||||
|
expect(bottom).toHaveLength(1)
|
||||||
|
|
||||||
|
const isScalar = (type: Type): type is ScalarType => type.tag === "scalar";
|
||||||
|
const scalars = types.filter(isScalar)
|
||||||
|
expect(scalars).toHaveLength(8)
|
||||||
|
scalars.forEach(sc => expect(sc.name).toBeDefined())
|
||||||
|
|
||||||
|
const isArray = (type: Type): type is ArrayType => type.tag === "array";
|
||||||
|
const arrays = types.filter(isArray)
|
||||||
|
expect(arrays).toHaveLength(2)
|
||||||
|
arrays.forEach(sc => expect(sc.element).toBeDefined())
|
||||||
|
|
||||||
|
const isOption = (type: Type): type is OptionType => type.tag === "option";
|
||||||
|
const options = types.filter(isOption)
|
||||||
|
expect(options).toHaveLength(2)
|
||||||
|
options.forEach(sc => expect(sc.element).toBeDefined())
|
||||||
|
|
||||||
|
const isStream = (type: Type): type is StreamType => type.tag === "stream";
|
||||||
|
const streams = types.filter(isStream)
|
||||||
|
expect(streams).toHaveLength(1)
|
||||||
|
streams.forEach(sc => expect(sc.element).toBeDefined())
|
||||||
|
|
||||||
|
const isAbility = (type: Type): type is AbilityType => type.tag === "ability";
|
||||||
|
const abilities = types.filter(isAbility)
|
||||||
|
expect(abilities).toHaveLength(1)
|
||||||
|
abilities.forEach((sc) => {
|
||||||
|
expect(sc.name).toBeDefined()
|
||||||
|
expect(sc.fields).toBeDefined()
|
||||||
|
expect(Object.entries(sc.fields)).toHaveLength(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isStruct = (type: Type): type is StructType => type.tag === "struct";
|
||||||
|
const structs = types.filter(isStruct)
|
||||||
|
expect(structs).toHaveLength(1)
|
||||||
|
structs.forEach((sc) => {
|
||||||
|
expect(sc.name).toBeDefined()
|
||||||
|
expect(sc.fields).toBeDefined()
|
||||||
|
expect(Object.entries(sc.fields)).toHaveLength(4)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isService = (type: Type): type is ServiceType => type.tag === "service";
|
||||||
|
const services = types.filter(isService)
|
||||||
|
expect(services).toHaveLength(1)
|
||||||
|
services.forEach((sc) => {
|
||||||
|
expect(sc.name).toBeDefined()
|
||||||
|
expect(sc.fields).toBeDefined()
|
||||||
|
expect(Object.entries(sc.fields)).toHaveLength(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isArrow = (type: Type): type is ArrowType => type.tag === "arrow";
|
||||||
|
const arrows = types.filter(isArrow)
|
||||||
|
expect(arrows).toHaveLength(3)
|
||||||
|
|
||||||
|
const fullArrow = arrows[0]
|
||||||
|
const argA = fullArrow.domain.args["a"]
|
||||||
|
expect(argA).toEqual({name: "string", tag: "scalar"})
|
||||||
|
|
||||||
|
const argB = fullArrow.domain.args["b"]
|
||||||
|
expect(argB).toEqual({name: "u32", tag: "scalar"})
|
||||||
|
|
||||||
|
const codomain = fullArrow.codomain.types
|
||||||
|
expect(codomain[0]).toEqual({name: "string", tag: "scalar"})
|
||||||
|
expect(codomain[1]).toEqual({name: "u32", tag: "scalar"})
|
||||||
|
|
||||||
|
const nilArrow = arrows[1]
|
||||||
|
expect(nilArrow.domain.args).toEqual({})
|
||||||
|
expect(nilArrow.codomain.types).toEqual([])
|
||||||
|
|
||||||
|
const srvNoopArrow = arrows[2]
|
||||||
|
expect(srvNoopArrow.domain.args).toEqual({srvArg: {name: "string", tag: "scalar"}})
|
||||||
|
expect(srvNoopArrow.codomain.types).toEqual([])
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})*/
|
@ -4,7 +4,7 @@ import aqua.parser.lift.FileSpan
|
|||||||
|
|
||||||
import scala.scalajs.js
|
import scala.scalajs.js
|
||||||
import scala.scalajs.js.annotation.JSExportAll
|
import scala.scalajs.js.annotation.JSExportAll
|
||||||
import scala.scalajs.js.{UndefOr, undefined}
|
import scala.scalajs.js.{undefined, UndefOr}
|
||||||
|
|
||||||
@JSExportAll
|
@JSExportAll
|
||||||
case class CompilationResult(
|
case class CompilationResult(
|
||||||
@ -16,7 +16,7 @@ case class CompilationResult(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@JSExportAll
|
@JSExportAll
|
||||||
case class ExprInfoJs(location: TokenLocation, `type`: String)
|
case class ExprInfoJs(location: TokenLocation, `type`: TypeJs)
|
||||||
|
|
||||||
@JSExportAll
|
@JSExportAll
|
||||||
case class TokenLocation(name: String, startLine: Int, startCol: Int, endLine: Int, endCol: Int)
|
case class TokenLocation(name: String, startLine: Int, startCol: Int, endLine: Int, endCol: Int)
|
||||||
|
@ -84,8 +84,8 @@ object ResultHelper extends Logging {
|
|||||||
private def tokensToJs(tokens: List[DefinitionInfo[FileSpan.F]]): js.Array[ExprInfoJs] =
|
private def tokensToJs(tokens: List[DefinitionInfo[FileSpan.F]]): js.Array[ExprInfoJs] =
|
||||||
tokens.flatMap { ti =>
|
tokens.flatMap { ti =>
|
||||||
TokenLocation.fromSpan(ti.token.unit._1).map { tl =>
|
TokenLocation.fromSpan(ti.token.unit._1).map { tl =>
|
||||||
val typeName = ti.`type`.show
|
val typeDef = TypeJs.fromType(ti.`type`)
|
||||||
ExprInfoJs(tl, typeName)
|
ExprInfoJs(tl, typeDef)
|
||||||
}
|
}
|
||||||
}.toJSArray
|
}.toJSArray
|
||||||
|
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
package aqua.lsp
|
||||||
|
|
||||||
|
import aqua.types.*
|
||||||
|
|
||||||
|
import scala.scalajs.js.Dictionary
|
||||||
|
import scala.scalajs.js.JSConverters.*
|
||||||
|
import scalajs.js
|
||||||
|
|
||||||
|
sealed trait TypeJs extends js.Object {
|
||||||
|
val tag: String
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScalarTypeJs(val name: String) extends TypeJs {
|
||||||
|
val tag: String = "scalar"
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArrayTypeJs(val element: TypeJs) extends TypeJs {
|
||||||
|
val tag: String = "array"
|
||||||
|
}
|
||||||
|
|
||||||
|
class OptionTypeJs(val element: TypeJs) extends TypeJs {
|
||||||
|
val tag: String = "option"
|
||||||
|
}
|
||||||
|
|
||||||
|
class StreamTypeJs(val element: TypeJs) extends TypeJs {
|
||||||
|
val tag: String = "stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
class StreamMapTypeJs(val element: TypeJs) extends TypeJs {
|
||||||
|
val tag: String = "streammap"
|
||||||
|
}
|
||||||
|
|
||||||
|
class CanonStreamTypeJs(val element: TypeJs) extends TypeJs {
|
||||||
|
val tag: String = "canon"
|
||||||
|
}
|
||||||
|
|
||||||
|
class AbilityTypeJs(val name: String, val fields: js.Dictionary[TypeJs]) extends TypeJs {
|
||||||
|
val tag: String = "ability"
|
||||||
|
}
|
||||||
|
|
||||||
|
class StructTypeJs(val name: String, val fields: js.Dictionary[TypeJs]) extends TypeJs {
|
||||||
|
val tag: String = "struct"
|
||||||
|
}
|
||||||
|
|
||||||
|
class ServiceTypeJs(val name: String, val fields: js.Dictionary[TypeJs]) extends TypeJs {
|
||||||
|
val tag: String = "service"
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ProductType extends TypeJs
|
||||||
|
|
||||||
|
class LabeledConsTypeJs(val args: js.Dictionary[TypeJs]) extends TypeJs {
|
||||||
|
val tag: String = "labeled"
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnlabeledConsTypeJs(val types: js.Array[TypeJs]) extends TypeJs {
|
||||||
|
val tag: String = "unlabeled"
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArrowTypeJs(val domain: LabeledConsTypeJs, val codomain: UnlabeledConsTypeJs) extends TypeJs {
|
||||||
|
val tag: String = "arrow"
|
||||||
|
}
|
||||||
|
|
||||||
|
class NilTypeJs extends TypeJs {
|
||||||
|
val tag: String = "nil"
|
||||||
|
}
|
||||||
|
|
||||||
|
class BottomTypeJs extends TypeJs {
|
||||||
|
val tag: String = "bottom"
|
||||||
|
}
|
||||||
|
|
||||||
|
class TopTypeJs extends TypeJs {
|
||||||
|
val tag: String = "top"
|
||||||
|
}
|
||||||
|
|
||||||
|
object TypeJs {
|
||||||
|
|
||||||
|
def typeList(types: Iterable[(String, Type)]): Dictionary[TypeJs] =
|
||||||
|
js.Dictionary(types.map { case (n, t) =>
|
||||||
|
(n, TypeJs.fromType(t))
|
||||||
|
}.toSeq: _*)
|
||||||
|
|
||||||
|
def fromType(t: Type): TypeJs = {
|
||||||
|
t match
|
||||||
|
case ScalarType(name) => new ScalarTypeJs(name)
|
||||||
|
case LiteralType(_, name) => new ScalarTypeJs(name)
|
||||||
|
case ArrayType(el) => new ArrayTypeJs(fromType(el))
|
||||||
|
case OptionType(el) => new OptionTypeJs(fromType(el))
|
||||||
|
case StreamType(el) => new StreamTypeJs(fromType(el))
|
||||||
|
case StreamMapType(el) => new StreamMapTypeJs(fromType(el))
|
||||||
|
case CanonStreamType(el) => new CanonStreamTypeJs(fromType(el))
|
||||||
|
case StructType(name, fields) => new StructTypeJs(name, typeList(fields.toSortedMap))
|
||||||
|
case AbilityType(name, fields) => new AbilityTypeJs(name, typeList(fields.toSortedMap))
|
||||||
|
case ServiceType(name, fields) => new ServiceTypeJs(name, typeList(fields.toSortedMap))
|
||||||
|
case lct: LabeledConsType => new LabeledConsTypeJs(typeList(lct.toLabelledList()))
|
||||||
|
case uct: UnlabeledConsType => new UnlabeledConsTypeJs(uct.toList.map(fromType).toJSArray)
|
||||||
|
case ArrowType(domain, codomain) =>
|
||||||
|
ArrowTypeJs(
|
||||||
|
new LabeledConsTypeJs(typeList(domain.toLabelledList())),
|
||||||
|
new UnlabeledConsTypeJs(codomain.toList.map(fromType).toJSArray)
|
||||||
|
)
|
||||||
|
case TopType => new TopTypeJs()
|
||||||
|
case BottomType => new BottomTypeJs()
|
||||||
|
case NilType => new NilTypeJs()
|
||||||
|
}
|
||||||
|
}
|
@ -1,48 +1,141 @@
|
|||||||
|
export interface ScalarType {
|
||||||
|
name: string,
|
||||||
|
tag: "scalar"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArrayType {
|
||||||
|
element: Type,
|
||||||
|
tag: "array"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OptionType {
|
||||||
|
element: Type,
|
||||||
|
tag: "option"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StreamType {
|
||||||
|
element: Type,
|
||||||
|
tag: "stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StreamMapType {
|
||||||
|
element: Type,
|
||||||
|
tag: "streammap"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CanonStreamType {
|
||||||
|
element: Type,
|
||||||
|
tag: "canon"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AbilityType {
|
||||||
|
name: string,
|
||||||
|
fields: Record<string, Type>,
|
||||||
|
tag: "ability"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StructType {
|
||||||
|
name: string,
|
||||||
|
fields: Record<string, Type>,
|
||||||
|
tag: "struct"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServiceType {
|
||||||
|
name: string,
|
||||||
|
fields: Record<string, Type>,
|
||||||
|
tag: "service"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LabeledConsType {
|
||||||
|
args: Record<string, Type>,
|
||||||
|
tag: "labeled"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UnlabeledConsType {
|
||||||
|
types: Type[],
|
||||||
|
tag: "unlabeled"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArrowType {
|
||||||
|
domain: LabeledConsType
|
||||||
|
codomain: UnlabeledConsType,
|
||||||
|
tag: "arrow"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NilType {
|
||||||
|
tag: "nil"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BottomType {
|
||||||
|
tag: "bottom"
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TopType {
|
||||||
|
tag: "top"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Type =
|
||||||
|
ScalarType
|
||||||
|
| ArrayType
|
||||||
|
| OptionType
|
||||||
|
| StreamType
|
||||||
|
| StreamMapType
|
||||||
|
| CanonStreamType
|
||||||
|
| AbilityType
|
||||||
|
| StructType
|
||||||
|
| ServiceType
|
||||||
|
| LabeledConsType
|
||||||
|
| UnlabeledConsType
|
||||||
|
| ArrowType
|
||||||
|
| NilType
|
||||||
|
| TopType
|
||||||
|
| BottomType
|
||||||
|
|
||||||
export interface TokenLocation {
|
export interface TokenLocation {
|
||||||
name: string;
|
name: string;
|
||||||
startLine: number;
|
startLine: number;
|
||||||
startCol: number;
|
startCol: number;
|
||||||
endLine: number;
|
endLine: number;
|
||||||
endCol: number;
|
endCol: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenInfo {
|
export interface TokenInfo {
|
||||||
location: TokenLocation;
|
location: TokenLocation;
|
||||||
type: string;
|
type: Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenLink {
|
export interface TokenLink {
|
||||||
current: TokenLocation;
|
current: TokenLocation;
|
||||||
definition: TokenLocation;
|
definition: TokenLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenImport {
|
export interface TokenImport {
|
||||||
current: TokenLocation;
|
current: TokenLocation;
|
||||||
path: string;
|
path: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ErrorInfo {
|
export interface ErrorInfo {
|
||||||
infoType: "error";
|
infoType: "error";
|
||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
message: string;
|
message: string;
|
||||||
location: string | null;
|
location: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WarningInfo {
|
export interface WarningInfo {
|
||||||
infoType: "warning";
|
infoType: "warning";
|
||||||
start: number;
|
start: number;
|
||||||
end: number;
|
end: number;
|
||||||
message: string;
|
message: string;
|
||||||
location: string | null;
|
location: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompilationResult {
|
export interface CompilationResult {
|
||||||
errors: ErrorInfo[];
|
errors: ErrorInfo[];
|
||||||
warnings: WarningInfo[];
|
warnings: WarningInfo[];
|
||||||
locations: TokenLink[];
|
locations: TokenLink[];
|
||||||
importLocations: TokenImport[];
|
importLocations: TokenImport[];
|
||||||
tokens: TokenInfo[];
|
tokens: TokenInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -59,7 +152,7 @@ export interface CompilationResult {
|
|||||||
export type Imports = Record<string, Record<string, string[]>>;
|
export type Imports = Record<string, Record<string, string[]>>;
|
||||||
|
|
||||||
export class Compiler {
|
export class Compiler {
|
||||||
compile(path: string, imports: Imports): Promise<CompilationResult>;
|
compile(path: string, imports: Imports): Promise<CompilationResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export var AquaLSP: Compiler;
|
export var AquaLSP: Compiler;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user