Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect, describe, beforeEach } from "vitest";
import { CommonExpressions } from "@player-lang/static-xlrs";
import { symbolDisplayToString } from "@xlr-lib/xlr-utils";
import { symbolDisplayToString } from "../utils";
import { ExpressionLanguageService } from "../service";

describe("language-service", () => {
Expand Down
2 changes: 1 addition & 1 deletion lsp/typescript-expression-plugin/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
Logger,
} from "typescript-template-language-service-decorator";
import type { FunctionType, TSManifest, NodeType } from "@xlr-lib/xlr";
import { createTSDocString } from "@xlr-lib/xlr-utils";
import { createTSDocString } from "./utils";
import { XLRSDK } from "@xlr-lib/xlr-sdk";
import type { ExpressionNode } from "@player-ui/player";
import { parseExpression } from "@player-ui/player";
Expand Down
234 changes: 233 additions & 1 deletion lsp/typescript-expression-plugin/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,241 @@
import type { TextSpan } from "typescript";
import ts from "typescript/lib/tsserverlibrary";
import type { TextSpan, SymbolDisplayPart } from "typescript";
import type { Position } from "vscode-languageserver-types";
import type { ExpressionNode, NodeLocation } from "@player-ui/player";
import type { NodeType } from "@xlr-lib/xlr";
import { isPrimitiveTypeNode } from "@xlr-lib/xlr-utils";
import { parseTree } from "jsonc-parser";
import type { Node } from "jsonc-parser";

const { SymbolDisplayPartKind } = ts;

/** Like `.join()` but for arrays */
function insertBetweenElements<T>(array: Array<T>, separator: T): T[] {
return array.reduce((acc, item, index) => {
if (index === 0) {
return [item];
}

return [...acc, separator, item];
}, [] as T[]);
}

/** Convert the TS SymbolDisplayParts into a single string */
export function symbolDisplayToString(
displayParts: Array<SymbolDisplayPart>,
): string {
return ts.displayPartsToString(displayParts);
}

/** Generate a documentation string for a given XLR node */
export function createTSDocString(node: NodeType): Array<SymbolDisplayPart> {
if (node.type === "ref") {
return [
{
text: node.ref,
kind: SymbolDisplayPartKind.keyword as any,
},
];
}

if (node.type === "or" || node.type === "and") {
const items = node.type === "and" ? node.and : node.or;

return insertBetweenElements(
items.map((subnode) => createTSDocString(subnode)),
[
{
kind: SymbolDisplayPartKind.punctuation as any,
text: node.type === "and" ? " & " : " | ",
},
],
).flat();
}

if (node.type === "function") {
return [
{
kind: SymbolDisplayPartKind.keyword as any,
text: "function",
},
{
kind: SymbolDisplayPartKind.space as any,
text: " ",
},
...(node.name
? [{ text: node.name, kind: SymbolDisplayPartKind.methodName }]
: []),
{
kind: SymbolDisplayPartKind.punctuation as any,
text: "(",
},
...insertBetweenElements(
node.parameters.map((p) => {
if (p.name) {
return [
{
kind: SymbolDisplayPartKind.parameterName as any,
text: p.name,
},
{
kind: SymbolDisplayPartKind.punctuation as any,
text: p.optional ? "?" : "",
},
{
kind: SymbolDisplayPartKind.punctuation as any,
text: ": ",
},
...createTSDocString(p.type),
];
}

return createTSDocString(p.type);
}),
[
{
kind: SymbolDisplayPartKind.punctuation as any,
text: ", ",
},
],
).flat(),
{
kind: SymbolDisplayPartKind.punctuation as any,
text: ")",
},
...(node.returnType
? [
{
kind: SymbolDisplayPartKind.punctuation as any,
text: ": ",
},
...createTSDocString(node.returnType),
]
: []),
];
}

if (node.type === "tuple") {
return [
{
kind: SymbolDisplayPartKind.punctuation as any,
text: "[",
},
...insertBetweenElements(
node.elementTypes.map((t) => {
if (t.name) {
return [
{
kind: SymbolDisplayPartKind.propertyName as any,
text: t.name,
},
{
kind: SymbolDisplayPartKind.punctuation as any,
text: ": ",
},
...createTSDocString(t.type),
];
}

return createTSDocString(t.type);
}),
[
{
kind: SymbolDisplayPartKind.punctuation as any,
text: ", ",
},
],
).flat(),
{
kind: SymbolDisplayPartKind.punctuation as any,
text: "]",
},
];
}

if (node.type === "array") {
return [
{
kind: SymbolDisplayPartKind.interfaceName as any,
text: "Array",
},
{
kind: SymbolDisplayPartKind.punctuation as any,
text: "<",
},
...createTSDocString(node.elementType),
{
kind: SymbolDisplayPartKind.punctuation as any,
text: ">",
},
];
}

if (node.type === "record") {
return [
{
kind: SymbolDisplayPartKind.interfaceName as any,
text: "Record",
},
{
kind: SymbolDisplayPartKind.punctuation as any,
text: "<",
},
...createTSDocString(node.keyType),
{
kind: SymbolDisplayPartKind.punctuation as any,
text: ", ",
},
...createTSDocString(node.valueType),
{
kind: SymbolDisplayPartKind.punctuation as any,
text: ">",
},
];
}

if (
(node.type === "string" ||
node.type === "boolean" ||
node.type === "number") &&
node.const !== undefined
) {
return [
{
kind: SymbolDisplayPartKind.keyword as any,
text:
typeof node.const === "string"
? `"${node.const}"`
: String(node.const),
},
];
}

if (isPrimitiveTypeNode(node) && node.type !== "null") {
return [
{
kind: SymbolDisplayPartKind.keyword as any,
text: node.type,
},
];
}

if (node.type === "object" && node.name) {
return [
{
kind: SymbolDisplayPartKind.interfaceName as any,
text: node.name,
},
];
}

return [
{
kind: SymbolDisplayPartKind.localName as any,
text: node.type,
},
];
}

/** Check if the vscode position overlaps with the expression location */
export function isInRange(position: Position, location: NodeLocation) {
return (
Expand Down
62 changes: 27 additions & 35 deletions pnpm-lock.yaml

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