UNPKG

@devaloop/prettier-plugin-devalang

Version:

Prettier plugin for Devalang formatting

222 lines (168 loc) 5.88 kB
import { type AstPath, type Doc, doc } from "prettier"; import { Expression } from "./types/expression"; import { Node } from "./types/node"; import { ArrowCallStatement } from "./interfaces/statement"; import { parseBlock } from "./parser"; const { hardline, indent, join, group } = doc.builders; /** * Prints an expression by its type. * @param expr Expression * @returns string */ const printExpression = (expr: Expression): string => { switch (expr.type) { case "Identifier": return expr.name; case "NumberLiteral": return expr.value.toString(); case "StringLiteral": return `"${expr.value}"`; case "BooleanLiteral": return expr.value ? "true" : "false"; case "ObjectLiteral": if (!expr.properties || expr.properties.length === 0) { return ""; } return `{ ${expr.properties .map((p: any) => `${p.key ? p.key + ':' : ''} ${printExpression(p.value)}`) .join(", ")} }`; case "ObjectProperty": return `${expr.key}: ${printExpression(expr.value)}`; case "SynthReference": return `synth ${expr.name}`; default: throw new Error(`Unsupported expression type: ${(expr as any).type}`); } }; /** * Prints a node matching its type. * @param path AstPath<Node> * @param options any * @param print (path: AstPath<Node>) => Doc * @returns Doc */ export const print = ( path: AstPath<Node>, options: any, print: (path: AstPath<Node>) => Doc ): Doc => { const node = path.node ? path.node : path.getValue(); // NOTE: See the parsed AST in the console for debugging purposes. if (node.type === "Program") { console.log("\nPrettier AST :"); console.dir(node, { depth: null, colors: true }); console.log(); } switch (node.type) { case "Program": { const printedNodes = path.map(print, "body"); const parts: Doc[] = []; for (let i = 0; i < node.body.length; i++) { const printed = printedNodes[i]; if (printed === "" || printed === undefined) continue; parts.push(printed); const nextNode = node.body[i + 1]; if (!nextNode || nextNode.type === "BlankLine") continue; parts.push(hardline); } while (parts.length > 0 && parts[parts.length - 1] === hardline) { parts.pop(); } return parts; } case "BpmDeclaration": return `bpm ${node.identifier}`; case "BankDeclaration": return `bank ${node.identifier}`; case "LetDeclaration": return `let ${node.name} = ${printExpression(node.value)}`; case "Loop": { const parts: Doc[] = parseBlock(node.body); return group([ `loop ${node.iterator.value}:`, indent([hardline, ...parts]) ]); } case "Trigger": return `${node.name} ${node.args.length && node.duration ? node.duration.value + " " : node.duration ? node.duration.value : ""}${printArguments(node.args)}`; case "ImportStatement": return `@import { ${node.identifiers.join(", ")} } from "${node.from}"`; case "ExportStatement": return `@export { ${node.identifiers.join(", ")} }`; case "LoadSample": return `@load "${node.path}" as ${node.alias}`; case "Group": { const rawChildren = node.body; const parts: Doc[] = parseBlock(rawChildren); return ["group ", node.name, ":", indent([hardline, ...parts])]; } case "Call": return `call ${node.identifier}`; case "Sleep": return `sleep ${node.value}`; case "Spawn": return `spawn ${node.identifier}`; case "If": { const parts: Doc[] = []; // if parts.push(`if ${node.condition}:`); parts.push(indent([hardline, ...parseBlock(node.body)])); // else ifs for (const elseIf of node.elseIfs ?? []) { parts.push(hardline, `else if ${elseIf.condition}:`); parts.push(indent([hardline, ...parseBlock(elseIf.body)])); } // else if (node.alternate && node.alternate.length > 0) { parts.push(hardline, "else:"); parts.push(indent([hardline, ...parseBlock(node.alternate)])); } return group(parts); } case "Comment": return node.value; case "Unknown": return node.value; case "BlankLine": return hardline; case "ArrowCall": return printArrowCall(node); default: throw new Error(`Unsupported node type: ${(node as any).type}`); } }; const printArrowCall = (call: ArrowCallStatement): Doc => { const target = call.target; const func = call.method; const args: Expression[] = call.args .filter((arg) => !(arg.type === "ObjectLiteral" && arg.properties.length === 0)); if (args.length === 0) { return `${target} -> ${func}()`; } return group([ `${target} -> ${func}(`, join(", ", args.map(printExpression)), ")" ]); }; const printArguments = (args: Expression[]): string => { if (args.length === 0) return ""; const isAllObjectProps = args.every(arg => arg.type === "ObjectProperty"); if (isAllObjectProps) { const props = args as any[]; const printedProps = props .map((p) => `${p.key}: ${printExpression(p.value)}`) .join(", "); return printedProps ? `{ ${printedProps} }` : ""; } if (args.length === 1 && args[0].type === "Identifier") { return args[0].name; } return args.map(printExpression).join(", "); }; export const embed = undefined; export const insertPragma = undefined; export const massageAstNode = undefined; export const hasPragma = undefined; export const preprocess = undefined; export const astFormat = "devalang";