Added scala、powershell、groovy prettier plugin

This commit is contained in:
2025-09-14 23:45:01 +08:00
parent 42c7d11c09
commit a83c7139c9
33 changed files with 10591 additions and 240 deletions

View File

@@ -0,0 +1,640 @@
/**
* Declaration visitor methods for class, object, trait, method, and other definitions
*/
import {
formatStatement,
getPrintWidth,
getChildNodes,
getFirstChild,
createIndent,
getNodeImage,
} from "./utils";
import type { PrintContext, CSTNode } from "./utils";
export interface DeclarationVisitor {
visit(node: CSTNode, ctx: PrintContext): string;
visitModifiers(modifiers: CSTNode[], ctx: PrintContext): string;
getIndentation(ctx: PrintContext): string;
}
export class DeclarationVisitorMethods {
private visitor: DeclarationVisitor;
constructor(visitor: DeclarationVisitor) {
this.visitor = visitor;
}
visitClassDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Add class keyword (don't duplicate if already handled by modifiers)
const classToken = getFirstChild(node, "Class");
if (classToken) {
result += getNodeImage(classToken) + " ";
}
// Add class name
const identifierToken = getFirstChild(node, "Identifier");
if (identifierToken) {
result += getNodeImage(identifierToken);
}
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
// Add constructor annotations
const annotations = getChildNodes(node, "annotation");
if (annotations.length > 0) {
result +=
" " +
annotations
.map((ann: CSTNode) => this.visitor.visit(ann, ctx))
.join(" ");
}
const classParameters = getFirstChild(node, "classParameters");
if (classParameters) {
result += this.visitor.visit(classParameters, ctx);
}
const extendsClause = getFirstChild(node, "extendsClause");
if (extendsClause) {
result += " " + this.visitor.visit(extendsClause, ctx);
}
const classBody = getFirstChild(node, "classBody");
if (classBody) {
result += " " + this.visitor.visit(classBody, ctx);
}
return result;
}
visitObjectDefinition(node: CSTNode, ctx: PrintContext): string {
const identifierToken = getFirstChild(node, "Identifier");
let result =
"object " + (identifierToken ? getNodeImage(identifierToken) : "");
const extendsClause = getFirstChild(node, "extendsClause");
if (extendsClause) {
result += " " + this.visitor.visit(extendsClause, ctx);
}
const classBody = getFirstChild(node, "classBody");
if (classBody) {
result += " " + this.visitor.visit(classBody, ctx);
}
return result;
}
visitTraitDefinition(node: CSTNode, ctx: PrintContext): string {
const identifier = getFirstChild(node, "Identifier");
let result = "trait " + (identifier ? getNodeImage(identifier) : "");
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
const extendsClause = getFirstChild(node, "extendsClause");
if (extendsClause) {
result += " " + this.visitor.visit(extendsClause, ctx);
}
const traitBody = getFirstChild(node, "classBody");
if (traitBody) {
result += " " + this.visitor.visit(traitBody, ctx);
}
return result;
}
visitEnumDefinition(node: CSTNode, ctx: PrintContext): string {
const identifierToken = getFirstChild(node, "Identifier");
let result =
"enum " + (identifierToken ? getNodeImage(identifierToken) : "");
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
const classParameters = getFirstChild(node, "classParameters");
if (classParameters) {
result += this.visitor.visit(classParameters, ctx);
}
const extendsClause = getFirstChild(node, "extendsClause");
if (extendsClause) {
result += " " + this.visitor.visit(extendsClause, ctx);
}
result += " {\n";
const enumCases = getChildNodes(node, "enumCase");
if (enumCases.length > 0) {
const indent = this.visitor.getIndentation(ctx);
const cases = enumCases.map(
(c: CSTNode) => indent + this.visitor.visit(c, ctx),
);
result += cases.join("\n");
}
result += "\n}";
return result;
}
visitEnumCase(node: CSTNode, ctx: PrintContext): string {
const identifierToken = getFirstChild(node, "Identifier");
let result =
"case " + (identifierToken ? getNodeImage(identifierToken) : "");
const classParameters = getFirstChild(node, "classParameters");
if (classParameters) {
result += this.visitor.visit(classParameters, ctx);
}
const extendsClause = getFirstChild(node, "extendsClause");
if (extendsClause) {
result += " " + this.visitor.visit(extendsClause, ctx);
}
return result;
}
visitExtensionDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "extension";
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
const identifierToken = getFirstChild(node, "Identifier");
const typeNode = getFirstChild(node, "type");
result +=
" (" + (identifierToken ? getNodeImage(identifierToken) : "") + ": ";
if (typeNode) {
result += this.visitor.visit(typeNode, ctx);
}
result += ") {\n";
const extensionMembers = getChildNodes(node, "extensionMember");
if (extensionMembers.length > 0) {
const members = extensionMembers.map(
(m: CSTNode) => " " + this.visitor.visit(m, ctx),
);
result += members.join("\n");
}
result += "\n}";
return result;
}
visitExtensionMember(node: CSTNode, ctx: PrintContext): string {
const modifierNodes = getChildNodes(node, "modifier");
const modifiers = this.visitor.visitModifiers(modifierNodes, ctx);
const defDefinition = getFirstChild(node, "defDefinition");
const definition = defDefinition
? this.visitor.visit(defDefinition, ctx)
: "";
return modifiers ? modifiers + " " + definition : definition;
}
visitValDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "val ";
// Handle pattern or identifier
const pattern = getFirstChild(node, "pattern");
const identifierToken = getFirstChild(node, "Identifier");
if (pattern) {
result += this.visitor.visit(pattern, ctx);
} else if (identifierToken) {
result += getNodeImage(identifierToken);
}
const colonToken = getFirstChild(node, "Colon");
if (colonToken) {
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += ": " + this.visitor.visit(typeNode, ctx);
}
}
const equalsToken = getFirstChild(node, "Equals");
if (equalsToken) {
const expression = getFirstChild(node, "expression");
if (expression) {
result += " = " + this.visitor.visit(expression, ctx);
}
}
return formatStatement(result, ctx);
}
visitVarDefinition(node: CSTNode, ctx: PrintContext): string {
const identifierToken = getFirstChild(node, "Identifier");
let result =
"var " + (identifierToken ? getNodeImage(identifierToken) : "");
const colonToken = getFirstChild(node, "Colon");
if (colonToken) {
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += ": " + this.visitor.visit(typeNode, ctx);
}
}
const expression = getFirstChild(node, "expression");
if (expression) {
result += " = " + this.visitor.visit(expression, ctx);
}
return formatStatement(result, ctx);
}
visitDefDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "def ";
const identifierToken = getFirstChild(node, "Identifier");
const thisToken = getFirstChild(node, "This");
if (identifierToken) {
result += getNodeImage(identifierToken);
} else if (thisToken) {
result += "this";
}
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
const parameterLists = getFirstChild(node, "parameterLists");
if (parameterLists) {
result += this.visitor.visit(parameterLists, ctx);
}
const colonToken = getFirstChild(node, "Colon");
if (colonToken) {
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += ": " + this.visitor.visit(typeNode, ctx);
}
}
const equalsToken = getFirstChild(node, "Equals");
if (equalsToken) {
const expression = getFirstChild(node, "expression");
if (expression) {
result += " = " + this.visitor.visit(expression, ctx);
}
return formatStatement(result, ctx);
}
return result;
}
visitGivenDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "given";
const identifierToken = getFirstChild(node, "Identifier");
if (identifierToken) {
// Named given with potential parameters: given name[T](using ord: Type): Type
result += " " + getNodeImage(identifierToken);
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
const parameterLists = getFirstChild(node, "parameterLists");
if (parameterLists) {
result += this.visitor.visit(parameterLists, ctx);
}
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += ": " + this.visitor.visit(typeNode, ctx);
}
} else {
// Anonymous given: given Type = expression
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += " " + this.visitor.visit(typeNode, ctx);
}
}
const equalsToken = getFirstChild(node, "Equals");
if (equalsToken) {
const expression = getFirstChild(node, "expression");
if (expression) {
result += " = " + this.visitor.visit(expression, ctx);
}
}
return result;
}
visitTypeDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Handle opaque types
const opaqueToken = getFirstChild(node, "Opaque");
if (opaqueToken) {
result += "opaque ";
}
const identifierToken = getFirstChild(node, "Identifier");
result += "type " + (identifierToken ? getNodeImage(identifierToken) : "");
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += " = " + this.visitor.visit(typeNode, ctx);
}
return result;
}
visitAuxiliaryConstructor(node: CSTNode, ctx: PrintContext): string {
let result = "def this";
// CST uses "parameterList" (singular) for auxiliary constructors
const parameterList = getFirstChild(node, "parameterList");
if (parameterList) {
result += this.visitor.visit(parameterList, ctx);
}
const expression = getFirstChild(node, "expression");
if (expression) {
result += " = " + this.visitor.visit(expression, ctx);
}
return result;
}
visitClassParameters(node: CSTNode, ctx: PrintContext): string {
const params = getChildNodes(node, "classParameter");
if (params.length === 0) {
return "()";
}
const paramStrings = params.map((p: CSTNode) => this.visitor.visit(p, ctx));
const printWidth = getPrintWidth(ctx);
// Check if single line is appropriate
const singleLine = `(${paramStrings.join(", ")})`;
// Use single line if it fits within printWidth and is reasonably short
if (
params.length === 1 &&
singleLine.length <= Math.min(printWidth * 0.6, 40)
) {
return singleLine;
}
// Use multi-line format for multiple parameters or long single parameter
const indent = this.visitor.getIndentation(ctx);
// Format each parameter on its own line without trailing comma for class parameters
const indentedParams = paramStrings.map((param: string) => indent + param);
return `(\n${indentedParams.join(",\n")}\n)`;
}
visitClassParameter(node: CSTNode, ctx: PrintContext): string {
let result = "";
const modifierNodes = getChildNodes(node, "modifier");
if (modifierNodes.length > 0) {
const modifiers = this.visitor.visitModifiers(modifierNodes, ctx);
result += modifiers + " ";
}
const valToken = getFirstChild(node, "Val");
const varToken = getFirstChild(node, "Var");
if (valToken) {
result += "val ";
} else if (varToken) {
result += "var ";
}
const identifierToken = getFirstChild(node, "Identifier");
if (identifierToken) {
result += getNodeImage(identifierToken);
}
result += ": ";
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += this.visitor.visit(typeNode, ctx);
}
const equalsToken = getFirstChild(node, "Equals");
if (equalsToken) {
const expression = getFirstChild(node, "expression");
if (expression) {
result += " = " + this.visitor.visit(expression, ctx);
}
}
return result;
}
visitParameterLists(node: CSTNode, ctx: PrintContext): string {
const parameterLists = getChildNodes(node, "parameterList");
return parameterLists
.map((list: CSTNode) => this.visitor.visit(list, ctx))
.join("");
}
visitParameterList(node: CSTNode, ctx: PrintContext): string {
const params = getChildNodes(node, "parameter");
if (params.length === 0) {
return "()";
}
const paramStrings = params.map((p: CSTNode) => this.visitor.visit(p, ctx));
return "(" + paramStrings.join(", ") + ")";
}
visitParameter(node: CSTNode, ctx: PrintContext): string {
let result = "";
const usingToken = getFirstChild(node, "Using");
const implicitToken = getFirstChild(node, "Implicit");
if (usingToken) {
result += "using ";
} else if (implicitToken) {
result += "implicit ";
}
const identifierToken = getFirstChild(node, "Identifier");
if (identifierToken) {
result += getNodeImage(identifierToken);
}
result += ": ";
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += this.visitor.visit(typeNode, ctx);
}
const equalsToken = getFirstChild(node, "Equals");
if (equalsToken) {
const expression = getFirstChild(node, "expression");
if (expression) {
result += " = " + this.visitor.visit(expression, ctx);
}
}
return result;
}
visitTypeParameters(node: CSTNode, ctx: PrintContext): string {
const params = getChildNodes(node, "typeParameter");
if (params.length === 0) {
return "";
}
const paramStrings = params.map((p: CSTNode) => this.visitor.visit(p, ctx));
return "[" + paramStrings.join(", ") + "]";
}
visitTypeParameter(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Handle variance annotations
const plusToken = getFirstChild(node, "Plus");
const minusToken = getFirstChild(node, "Minus");
if (plusToken) {
result += "+";
} else if (minusToken) {
result += "-";
}
const identifierToken = getFirstChild(node, "Identifier");
if (identifierToken) {
result += getNodeImage(identifierToken);
}
// Add bounds
const subtypeOfToken = getFirstChild(node, "SubtypeOf");
const supertypeOfToken = getFirstChild(node, "SupertypeOf");
const typeNodes = getChildNodes(node, "type");
if (subtypeOfToken && typeNodes.length > 0) {
result += " <: " + this.visitor.visit(typeNodes[0], ctx);
}
if (supertypeOfToken && typeNodes.length > 1) {
result += " >: " + this.visitor.visit(typeNodes[1], ctx);
} else if (supertypeOfToken && typeNodes.length === 1 && !subtypeOfToken) {
result += " >: " + this.visitor.visit(typeNodes[0], ctx);
}
return result;
}
visitExtendsClause(node: CSTNode, ctx: PrintContext): string {
const typeNodes = getChildNodes(node, "type");
if (typeNodes.length === 0) {
return "";
}
let result = "extends " + this.visitor.visit(typeNodes[0], ctx);
const withToken = getFirstChild(node, "With");
if (withToken && typeNodes.length > 1) {
const withTypes = typeNodes
.slice(1)
.map((t: CSTNode) => this.visitor.visit(t, ctx));
result += " with " + withTypes.join(" with ");
}
return result;
}
visitClassBody(node: CSTNode, ctx: PrintContext): string {
const classMembers = getChildNodes(node, "classMember");
if (classMembers.length === 0) {
return "{}";
}
// Increase indent level for class members
const nestedCtx = {
...ctx,
indentLevel: ctx.indentLevel + 1,
};
const members = classMembers.map((m: CSTNode) =>
this.visitor.visit(m, nestedCtx),
);
const indent = createIndent(1, ctx);
return "{\n" + members.map((m: string) => indent + m).join("\n") + "\n}";
}
visitClassMember(node: CSTNode, ctx: PrintContext): string {
// Handle different types of class members
const defDefinition = getFirstChild(node, "defDefinition");
if (defDefinition) {
return this.visitor.visit(defDefinition, ctx);
}
const auxiliaryConstructor = getFirstChild(node, "auxiliaryConstructor");
if (auxiliaryConstructor) {
return this.visitor.visit(auxiliaryConstructor, ctx);
}
const valDefinition = getFirstChild(node, "valDefinition");
if (valDefinition) {
return this.visitor.visit(valDefinition, ctx);
}
const varDefinition = getFirstChild(node, "varDefinition");
if (varDefinition) {
return this.visitor.visit(varDefinition, ctx);
}
const classDefinition = getFirstChild(node, "classDefinition");
if (classDefinition) {
return this.visitor.visit(classDefinition, ctx);
}
const objectDefinition = getFirstChild(node, "objectDefinition");
if (objectDefinition) {
return this.visitor.visit(objectDefinition, ctx);
}
const traitDefinition = getFirstChild(node, "traitDefinition");
if (traitDefinition) {
return this.visitor.visit(traitDefinition, ctx);
}
const typeDefinition = getFirstChild(node, "typeDefinition");
if (typeDefinition) {
return this.visitor.visit(typeDefinition, ctx);
}
const definition = getFirstChild(node, "definition");
if (definition) {
return this.visitor.visit(definition, ctx);
}
return "";
}
}

View File

@@ -0,0 +1,836 @@
/**
* Expression visitor methods for handling various expression types
*/
import {
formatStringLiteral,
getChildNodes,
getFirstChild,
createIndent,
getNodeImage,
} from "./utils";
import type { PrintContext, CSTNode } from "./utils";
export interface ExpressionVisitor {
visit(node: CSTNode, ctx: PrintContext): string;
}
export class ExpressionVisitorMethods {
private visitor: ExpressionVisitor;
constructor(visitor: ExpressionVisitor) {
this.visitor = visitor;
}
visitExpression(node: CSTNode, ctx: PrintContext): string {
// Handle PartialFunction literals: { case ... }
const partialFunctionLiteral = getFirstChild(
node,
"partialFunctionLiteral",
);
if (partialFunctionLiteral) {
return this.visitor.visit(partialFunctionLiteral, ctx);
}
// Handle lambda expressions with parameter list: (x: Int, y: Int) => x + y
const parameterList = getFirstChild(node, "parameterList");
const arrow = getChildNodes(node, "Arrow");
if (parameterList && arrow.length > 0) {
const expression = getFirstChild(node, "expression");
return (
this.visitor.visit(parameterList, ctx) +
" => " +
(expression ? this.visitor.visit(expression, ctx) : "")
);
}
// Handle block lambda expressions: { x => ... }
const leftBrace = getChildNodes(node, "LeftBrace");
const identifier = getChildNodes(node, "Identifier");
const arrowNodes = getChildNodes(node, "Arrow");
if (
leftBrace.length > 0 &&
identifier.length > 0 &&
arrowNodes.length > 0
) {
let result = "{ " + getNodeImage(identifier[0]) + " =>";
const statements: string[] = [];
// Create nested context for lambda body
const nestedCtx = {
...ctx,
indentLevel: ctx.indentLevel + 1,
};
// Add statements (val/var/def definitions)
const blockStatements = getChildNodes(node, "blockStatement");
if (blockStatements.length > 0) {
statements.push(
...blockStatements.map((stmt: CSTNode) =>
this.visitor.visit(stmt, nestedCtx),
),
);
}
// Add final expression
const finalExpression = getFirstChild(node, "expression");
if (finalExpression) {
statements.push(this.visitor.visit(finalExpression, nestedCtx));
}
if (statements.length === 0) {
result += " }";
} else if (statements.length === 1) {
// Single expression - keep on same line if short
const stmt = statements[0];
if (stmt.length < 50) {
result += " " + stmt + " }";
} else {
const indent = createIndent(1, ctx);
result += "\n" + indent + stmt + "\n}";
}
} else {
// Multiple statements - use multiple lines
const indent = createIndent(1, ctx);
const indentedStmts = statements.map((stmt) => indent + stmt);
result += "\n" + indentedStmts.join("\n") + "\n}";
}
return result;
}
// Handle polymorphic function literal: [T] => (x: T) => x
const polymorphicFunctionLiteral = getFirstChild(
node,
"polymorphicFunctionLiteral",
);
if (polymorphicFunctionLiteral) {
return this.visitor.visit(polymorphicFunctionLiteral, ctx);
}
// Handle simple lambda expressions: x => x * 2
const simpleIdentifier = getChildNodes(node, "Identifier");
const simpleArrow = getChildNodes(node, "Arrow");
if (simpleIdentifier.length > 0 && simpleArrow.length > 0) {
const expression = getFirstChild(node, "expression");
return (
getNodeImage(simpleIdentifier[0]) +
" => " +
(expression ? this.visitor.visit(expression, ctx) : "")
);
}
// Handle assignmentOrInfixExpression
const assignmentOrInfixExpression = getFirstChild(
node,
"assignmentOrInfixExpression",
);
if (assignmentOrInfixExpression) {
return this.visitor.visit(assignmentOrInfixExpression, ctx);
}
// Handle regular expressions (fallback for older structure)
const postfixExpressions = getChildNodes(node, "postfixExpression");
if (postfixExpressions.length > 0) {
let result = this.visitor.visit(postfixExpressions[0], ctx);
const infixOperators = getChildNodes(node, "infixOperator");
if (infixOperators.length > 0) {
for (let i = 0; i < infixOperators.length; i++) {
result +=
" " +
this.visitor.visit(infixOperators[i], ctx) +
" " +
(postfixExpressions[i + 1]
? this.visitor.visit(postfixExpressions[i + 1], ctx)
: "");
}
}
return result;
}
return "";
}
visitPostfixExpression(node: CSTNode, ctx: PrintContext): string {
const primaryExpression = getFirstChild(node, "primaryExpression");
let result = primaryExpression
? this.visitor.visit(primaryExpression, ctx)
: "";
// Handle method calls and member access
const dots = getChildNodes(node, "Dot");
if (dots.length > 0) {
const identifiers = getChildNodes(node, "Identifier");
for (let i = 0; i < dots.length; i++) {
result += ".";
// Handle member access or method call
// Identifiers after the first one correspond to members after dots
if (identifiers.length > i) {
result += getNodeImage(identifiers[i]);
}
// Add arguments if this is a method call
const leftParens = getChildNodes(node, "LeftParen");
if (leftParens.length > i) {
result += "(";
// Find expressions for this argument list
const startIdx = i * 10; // Rough heuristic for argument grouping
const expressions = getChildNodes(node, "expression");
const relevantExpressions = expressions.slice(
startIdx,
startIdx + 10,
);
if (relevantExpressions.length > 0) {
const args = relevantExpressions.map((e: CSTNode) =>
this.visitor.visit(e, ctx),
);
result += args.join(", ");
}
result += ")";
}
}
}
// Handle type arguments
const leftBrackets = getChildNodes(node, "LeftBracket");
if (leftBrackets.length > 0) {
result += "[";
const types = getChildNodes(node, "type");
if (types.length > 0) {
const typeStrings = types.map((t: CSTNode) =>
this.visitor.visit(t, ctx),
);
result += typeStrings.join(", ");
}
result += "]";
}
// Handle match expressions
const matchTokens = getChildNodes(node, "Match");
if (matchTokens.length > 0) {
result += " match {\n";
const caseClauses = getChildNodes(node, "caseClause");
if (caseClauses.length > 0) {
const cases = caseClauses.map(
(c: CSTNode) => " " + this.visitor.visit(c, ctx),
);
result += cases.join("\n");
result += "\n";
}
result += "}";
}
// Handle method application without dot
const methodLeftParens = getChildNodes(node, "LeftParen");
const methodDots = getChildNodes(node, "Dot");
if (methodLeftParens.length > 0 && methodDots.length === 0) {
result += "(";
const methodExpressions = getChildNodes(node, "expression");
if (methodExpressions.length > 0) {
const args = methodExpressions.map((e: CSTNode) =>
this.visitor.visit(e, ctx),
);
result += args.join(", ");
}
result += ")";
}
// Handle block lambda expressions: method { param => ... }
const leftBrace = getChildNodes(node, "LeftBrace");
const arrowNodes = getChildNodes(node, "Arrow");
const identifiers = getChildNodes(node, "Identifier");
if (
leftBrace.length > 0 &&
arrowNodes.length > 0 &&
identifiers.length > 1
) {
// The lambda parameter is the second identifier (first is method name)
const lambdaParam = getNodeImage(identifiers[1]);
result += " { " + lambdaParam + " =>";
// Create nested context for lambda body
const nestedCtx = {
...ctx,
indentLevel: ctx.indentLevel + 1,
};
// Process block statements
const blockStatements = getChildNodes(node, "blockStatement");
const statements: string[] = [];
for (const stmt of blockStatements) {
statements.push(this.visitor.visit(stmt, nestedCtx));
}
if (statements.length === 0) {
result += " }";
} else if (statements.length === 1) {
// Single statement - keep on same line if short
const stmt = statements[0];
if (stmt.length < 50) {
result += " " + stmt + " }";
} else {
const indent = createIndent(1, ctx);
result += "\n" + indent + stmt + "\n}";
}
} else {
// Multiple statements - use multiple lines
const indent = createIndent(1, ctx);
const indentedStmts = statements.map((stmt) => indent + stmt);
result += "\n" + indentedStmts.join("\n") + "\n}";
}
}
return result;
}
visitPrimaryExpression(node: CSTNode, ctx: PrintContext): string {
const literal = getFirstChild(node, "literal");
if (literal) {
return this.visitor.visit(literal, ctx);
}
const identifier = getFirstChild(node, "Identifier");
if (identifier) {
return getNodeImage(identifier);
}
const thisToken = getChildNodes(node, "This");
if (thisToken.length > 0) {
return "this";
}
const partialFunctionLiteral = getFirstChild(
node,
"partialFunctionLiteral",
);
if (partialFunctionLiteral) {
return this.visitor.visit(partialFunctionLiteral, ctx);
}
const newExpression = getFirstChild(node, "newExpression");
if (newExpression) {
return this.visitor.visit(newExpression, ctx);
}
const forExpression = getFirstChild(node, "forExpression");
if (forExpression) {
return this.visitor.visit(forExpression, ctx);
}
const ifExpression = getFirstChild(node, "ifExpression");
if (ifExpression) {
return this.visitor.visit(ifExpression, ctx);
}
const whileExpression = getFirstChild(node, "whileExpression");
if (whileExpression) {
return this.visitor.visit(whileExpression, ctx);
}
const tryExpression = getFirstChild(node, "tryExpression");
if (tryExpression) {
return this.visitor.visit(tryExpression, ctx);
}
const exclamation = getChildNodes(node, "Exclamation");
if (exclamation.length > 0) {
// Handle negation operator
const postfixExpression = getFirstChild(node, "postfixExpression");
if (postfixExpression) {
const result = this.visitor.visit(postfixExpression, ctx);
return "!" + result;
}
return "!";
}
const bitwiseTilde = getChildNodes(node, "BitwiseTilde");
if (bitwiseTilde.length > 0) {
// Handle bitwise complement operator
const postfixExpression = getFirstChild(node, "postfixExpression");
return (
"~" +
(postfixExpression ? this.visitor.visit(postfixExpression, ctx) : "")
);
}
const leftParen = getChildNodes(node, "LeftParen");
if (leftParen.length > 0) {
const expression = getFirstChild(node, "expression");
const assignmentOrInfixExpression = getFirstChild(
node,
"assignmentOrInfixExpression",
);
// Try expression first, then assignmentOrInfixExpression
const content = expression
? this.visitor.visit(expression, ctx)
: assignmentOrInfixExpression
? this.visitor.visit(assignmentOrInfixExpression, ctx)
: "";
return "(" + content + ")";
}
const blockExpression = getFirstChild(node, "blockExpression");
if (blockExpression) {
return this.visitor.visit(blockExpression, ctx);
}
const quoteExpression = getFirstChild(node, "quoteExpression");
if (quoteExpression) {
return this.visitor.visit(quoteExpression, ctx);
}
const spliceExpression = getFirstChild(node, "spliceExpression");
if (spliceExpression) {
return this.visitor.visit(spliceExpression, ctx);
}
return "";
}
visitAssignmentOrInfixExpression(node: CSTNode, ctx: PrintContext): string {
const postfixExpressions = getChildNodes(node, "postfixExpression");
let result =
postfixExpressions.length > 0
? this.visitor.visit(postfixExpressions[0], ctx)
: "";
// Handle assignment operators (including named arguments)
const equals = getChildNodes(node, "Equals");
const plusEquals = getChildNodes(node, "PlusEquals");
const minusEquals = getChildNodes(node, "MinusEquals");
const starEquals = getChildNodes(node, "StarEquals");
const slashEquals = getChildNodes(node, "SlashEquals");
const percentEquals = getChildNodes(node, "PercentEquals");
const sbtAssign = getChildNodes(node, "SbtAssign");
const operator =
equals[0] ||
plusEquals[0] ||
minusEquals[0] ||
starEquals[0] ||
slashEquals[0] ||
percentEquals[0] ||
sbtAssign[0];
if (operator) {
result += " " + getNodeImage(operator) + " ";
const expressions = getChildNodes(node, "expression");
if (expressions.length > 0) {
result += this.visitor.visit(expressions[0], ctx);
}
}
// Handle infix operators
const infixOperators = getChildNodes(node, "infixOperator");
if (infixOperators.length > 0) {
for (let i = 0; i < infixOperators.length; i++) {
result += " " + this.visitor.visit(infixOperators[i], ctx) + " ";
if (postfixExpressions.length > i + 1) {
result += this.visitor.visit(postfixExpressions[i + 1], ctx);
}
}
}
return result;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
visitInfixOperator(node: CSTNode, _ctx: PrintContext): string {
// Handle all possible infix operators
const operators = [
"Plus",
"Minus",
"Star",
"Slash",
"Percent",
"DoubleStar",
"LeftShift",
"RightShift",
"UnsignedRightShift",
"BitwiseAnd",
"BitwiseOr",
"BitwiseXor",
"EqualsEquals",
"NotEquals",
"LessThan",
"LessThanOrEqual",
"GreaterThan",
"GreaterThanOrEqual",
"LogicalAnd",
"LogicalOr",
"DoublePercent",
"Ask",
"To",
"Until",
"PrependOp",
"AppendOp",
"ConcatOp",
"RightArrow",
];
for (const op of operators) {
const tokens = getChildNodes(node, op);
if (tokens.length > 0) {
return getNodeImage(tokens[0]);
}
}
// Fallback to identifier for custom operators
const identifiers = getChildNodes(node, "Identifier");
if (identifiers.length > 0) {
return getNodeImage(identifiers[0]);
}
return "";
}
visitLiteral(node: CSTNode, ctx: PrintContext): string {
// Handle all possible literal types
const literalTypes = [
"StringLiteral",
"InterpolatedStringLiteral",
"IntegerLiteral",
"NumberLiteral",
"FloatLiteral",
"BooleanLiteral",
"True",
"False",
"CharLiteral",
"NullLiteral",
"Null",
"ScientificNumber",
];
for (const literalType of literalTypes) {
const tokens = getChildNodes(node, literalType);
if (tokens.length > 0) {
const tokenImage = getNodeImage(tokens[0]);
// Apply singleQuote formatting to string literals
if (tokenImage.startsWith('"') || tokenImage.startsWith("'")) {
return formatStringLiteral(tokenImage, ctx);
}
return tokenImage;
}
}
return "";
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
visitQualifiedIdentifier(node: CSTNode, _ctx: PrintContext): string {
const identifiers = getChildNodes(node, "Identifier");
if (identifiers.length === 0) {
return "";
}
let result = getNodeImage(identifiers[0]);
const dots = getChildNodes(node, "Dot");
if (dots.length > 0) {
// Handle mixed identifiers and type keywords
const types = getChildNodes(node, "Type");
for (let i = 0; i < dots.length; i++) {
result += ".";
// Determine which token comes next (identifier or type keyword)
if (i + 1 < identifiers.length) {
result += getNodeImage(identifiers[i + 1]);
} else if (types.length > 0) {
// Use the type keyword (e.g., "type" for .type syntax)
result += getNodeImage(types[0]);
}
}
}
return result;
}
visitNewExpression(node: CSTNode, ctx: PrintContext): string {
const typeNode = getFirstChild(node, "type");
let result = "new " + (typeNode ? this.visitor.visit(typeNode, ctx) : "");
const leftParens = getChildNodes(node, "LeftParen");
if (leftParens.length > 0) {
result += "(";
const expressions = getChildNodes(node, "expression");
if (expressions.length > 0) {
const args = expressions.map((e: CSTNode) =>
this.visitor.visit(e, ctx),
);
result += args.join(", ");
}
result += ")";
}
return result;
}
visitIfExpression(node: CSTNode, ctx: PrintContext): string {
const expressions = getChildNodes(node, "expression");
if (expressions.length < 2) {
return "if";
}
let result = "if (";
result += this.visitor.visit(expressions[0], ctx);
result += ") ";
result += this.visitor.visit(expressions[1], ctx);
const elseTokens = getChildNodes(node, "Else");
if (elseTokens.length > 0 && expressions.length > 2) {
result += " else ";
result += this.visitor.visit(expressions[2], ctx);
}
return result;
}
visitWhileExpression(node: CSTNode, ctx: PrintContext): string {
const expressions = getChildNodes(node, "expression");
if (expressions.length < 2) {
return "while";
}
let result = "while (";
result += this.visitor.visit(expressions[0], ctx);
result += ") ";
result += this.visitor.visit(expressions[1], ctx);
return result;
}
visitTryExpression(node: CSTNode, ctx: PrintContext): string {
const expressions = getChildNodes(node, "expression");
if (expressions.length === 0) {
return "try";
}
let result = "try ";
result += this.visitor.visit(expressions[0], ctx);
const catchTokens = getChildNodes(node, "Catch");
if (catchTokens.length > 0) {
result += " catch {\n";
const caseClauses = getChildNodes(node, "caseClause");
if (caseClauses.length > 0) {
const cases = caseClauses.map(
(c: CSTNode) => " " + this.visitor.visit(c, ctx),
);
result += cases.join("\n");
}
result += "\n}";
}
const finallyTokens = getChildNodes(node, "Finally");
if (finallyTokens.length > 0) {
result += " finally ";
// If there's a catch block, expression[1] is the finally expression
// Otherwise, expression[1] would be the finally expression (no catch)
const finallyExprIndex = catchTokens.length > 0 ? 1 : 1;
if (expressions.length > finallyExprIndex) {
result += this.visitor.visit(expressions[finallyExprIndex], ctx);
}
}
return result;
}
visitForExpression(node: CSTNode, ctx: PrintContext): string {
let result = "for ";
const leftParens = getChildNodes(node, "LeftParen");
const leftBraces = getChildNodes(node, "LeftBrace");
const generators = getChildNodes(node, "generator");
if (leftParens.length > 0) {
result += "(";
if (generators.length > 0) {
const gens = generators.map((g: CSTNode) => this.visitor.visit(g, ctx));
result += gens.join("; ");
}
result += ")";
} else if (leftBraces.length > 0) {
result += "{\n";
if (generators.length > 0) {
const gens = generators.map(
(g: CSTNode) => " " + this.visitor.visit(g, ctx),
);
result += gens.join("\n");
}
result += "\n}";
}
const yieldTokens = getChildNodes(node, "Yield");
if (yieldTokens.length > 0) {
result += " yield ";
} else {
result += " ";
}
const expressions = getChildNodes(node, "expression");
if (expressions.length > 0) {
result += this.visitor.visit(expressions[0], ctx);
}
return result;
}
visitGenerator(node: CSTNode, ctx: PrintContext): string {
const patterns = getChildNodes(node, "pattern");
const expressions = getChildNodes(node, "expression");
if (patterns.length === 0 || expressions.length === 0) {
return "";
}
let result = this.visitor.visit(patterns[0], ctx);
result += " <- " + this.visitor.visit(expressions[0], ctx);
const ifTokens = getChildNodes(node, "If");
if (ifTokens.length > 0) {
for (let i = 0; i < ifTokens.length; i++) {
if (expressions.length > i + 1) {
result += " if " + this.visitor.visit(expressions[i + 1], ctx);
}
}
}
return result;
}
visitCaseClause(node: CSTNode, ctx: PrintContext): string {
const patterns = getChildNodes(node, "pattern");
const expressions = getChildNodes(node, "expression");
if (patterns.length === 0) {
return "case";
}
let result = "case " + this.visitor.visit(patterns[0], ctx);
const ifTokens = getChildNodes(node, "If");
if (ifTokens.length > 0 && expressions.length > 0) {
result += " if " + this.visitor.visit(expressions[0], ctx);
}
const expressionIndex = ifTokens.length > 0 ? 1 : 0;
if (expressions.length > expressionIndex) {
result += " => " + this.visitor.visit(expressions[expressionIndex], ctx);
}
return result;
}
visitBlockExpression(node: CSTNode, ctx: PrintContext): string {
const blockStatements = getChildNodes(node, "blockStatement");
const expressions = getChildNodes(node, "expression");
if (blockStatements.length === 0 && expressions.length === 0) {
return "{}";
}
let result = "{\n";
const statements: string[] = [];
// Create nested context for block contents
const nestedCtx = {
...ctx,
indentLevel: ctx.indentLevel + 1,
};
if (blockStatements.length > 0) {
statements.push(
...blockStatements.map((stmt: CSTNode) =>
this.visitor.visit(stmt, nestedCtx),
),
);
}
if (expressions.length > 0) {
statements.push(this.visitor.visit(expressions[0], nestedCtx));
}
const indent = createIndent(1, ctx);
result += statements.map((stmt) => indent + stmt).join("\n");
result += "\n}";
return result;
}
visitPartialFunctionLiteral(node: CSTNode, ctx: PrintContext): string {
const caseClauses = getChildNodes(node, "caseClause");
if (caseClauses.length === 0) {
return "{}";
}
// Single case - try to format on one line if short
if (caseClauses.length === 1) {
const caseStr = this.visitor.visit(caseClauses[0], ctx);
if (caseStr.length < 50) {
return `{ ${caseStr} }`;
}
}
// Multi-line format for long cases or multiple cases
let result = "{\n";
const cases = caseClauses.map(
(c: CSTNode) => " " + this.visitor.visit(c, ctx),
);
result += cases.join("\n");
result += "\n}";
return result;
}
visitAssignmentStatement(node: CSTNode, ctx: PrintContext): string {
const identifiers = getChildNodes(node, "Identifier");
if (identifiers.length === 0) {
return "";
}
let result = getNodeImage(identifiers[0]);
// Find the assignment operator
const equals = getChildNodes(node, "Equals");
const plusEquals = getChildNodes(node, "PlusEquals");
const minusEquals = getChildNodes(node, "MinusEquals");
const starEquals = getChildNodes(node, "StarEquals");
const slashEquals = getChildNodes(node, "SlashEquals");
const percentEquals = getChildNodes(node, "PercentEquals");
const sbtAssign = getChildNodes(node, "SbtAssign");
const operator =
equals[0] ||
plusEquals[0] ||
minusEquals[0] ||
starEquals[0] ||
slashEquals[0] ||
percentEquals[0] ||
sbtAssign[0];
if (operator) {
result += " " + getNodeImage(operator) + " ";
const expressions = getChildNodes(node, "expression");
if (expressions.length > 0) {
result += this.visitor.visit(expressions[0], ctx);
}
}
return result;
}
}

View File

@@ -0,0 +1,433 @@
/**
* Scala 3 specific visitor methods for modern language features
*/
import { getChildNodes, getFirstChild, getNodeImage } from "./utils";
import type { PrintContext, CSTNode } from "./utils";
export interface Scala3Visitor {
visit(node: CSTNode, ctx: PrintContext): string;
getIndentation(ctx: PrintContext): string;
visitModifiers(modifiers: CSTNode[], ctx: PrintContext): string;
}
export class Scala3VisitorMethods {
private visitor: Scala3Visitor;
constructor(visitor: Scala3Visitor) {
this.visitor = visitor;
}
// Quote and splice expressions for macros
visitQuoteExpression(node: CSTNode, ctx: PrintContext): string {
const expression = getFirstChild(node, "expression");
return (
"'{ " + (expression ? this.visitor.visit(expression, ctx) : "") + " }"
);
}
visitSpliceExpression(node: CSTNode, ctx: PrintContext): string {
const expression = getFirstChild(node, "expression");
return (
"${ " + (expression ? this.visitor.visit(expression, ctx) : "") + " }"
);
}
// Polymorphic function literals
visitPolymorphicFunctionLiteral(node: CSTNode, ctx: PrintContext): string {
let result = "[";
const polymorphicTypeParams = getChildNodes(
node,
"polymorphicTypeParameter",
);
if (polymorphicTypeParams.length > 0) {
const parameters = polymorphicTypeParams.map((param: CSTNode) =>
this.visitor.visit(param, ctx),
);
result += parameters.join(", ");
}
result += "] => ";
const expression = getFirstChild(node, "expression");
result += expression ? this.visitor.visit(expression, ctx) : "";
return result;
}
// Polymorphic function types
visitPolymorphicFunctionType(node: CSTNode, ctx: PrintContext): string {
let result = "[";
const polymorphicTypeParams = getChildNodes(
node,
"polymorphicTypeParameter",
);
if (polymorphicTypeParams.length > 0) {
const parameters = polymorphicTypeParams.map((param: CSTNode) =>
this.visitor.visit(param, ctx),
);
result += parameters.join(", ");
}
result += "] => ";
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += this.visitor.visit(typeNode, ctx);
}
return result;
}
visitPolymorphicTypeParameter(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Add variance annotation if present
const plusTokens = getChildNodes(node, "Plus");
const minusTokens = getChildNodes(node, "Minus");
if (plusTokens.length > 0) {
result += "+";
} else if (minusTokens.length > 0) {
result += "-";
}
const identifiers = getChildNodes(node, "Identifier");
if (identifiers.length > 0) {
result += getNodeImage(identifiers[0]);
}
// Handle type bounds
const subtypeOf = getChildNodes(node, "SubtypeOf");
const supertypeOf = getChildNodes(node, "SupertypeOf");
const typeNode = getFirstChild(node, "type");
if (subtypeOf.length > 0 && typeNode) {
result += " <: " + this.visitor.visit(typeNode, ctx);
}
if (supertypeOf.length > 0 && typeNode) {
result += " >: " + this.visitor.visit(typeNode, ctx);
}
return result;
}
// Enum definitions
visitEnumDefinition(node: CSTNode, ctx: PrintContext): string {
const identifiers = getChildNodes(node, "Identifier");
let result =
"enum " + (identifiers.length > 0 ? getNodeImage(identifiers[0]) : "");
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
const classParameters = getFirstChild(node, "classParameters");
if (classParameters) {
result += this.visitor.visit(classParameters, ctx);
}
const extendsClause = getFirstChild(node, "extendsClause");
if (extendsClause) {
result += " " + this.visitor.visit(extendsClause, ctx);
}
result += " {\n";
const enumCases = getChildNodes(node, "enumCase");
if (enumCases.length > 0) {
const indent = this.visitor.getIndentation(ctx);
const cases = enumCases.map(
(c: CSTNode) => indent + this.visitor.visit(c, ctx),
);
result += cases.join("\n");
}
result += "\n}";
return result;
}
visitEnumCase(node: CSTNode, ctx: PrintContext): string {
const identifiers = getChildNodes(node, "Identifier");
let result =
"case " + (identifiers.length > 0 ? getNodeImage(identifiers[0]) : "");
const classParameters = getFirstChild(node, "classParameters");
if (classParameters) {
result += this.visitor.visit(classParameters, ctx);
}
const extendsClause = getFirstChild(node, "extendsClause");
if (extendsClause) {
result += " " + this.visitor.visit(extendsClause, ctx);
}
return result;
}
// Extension methods
visitExtensionDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "extension";
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
const identifiers = getChildNodes(node, "Identifier");
const typeNode = getFirstChild(node, "type");
result +=
" (" +
(identifiers.length > 0 ? getNodeImage(identifiers[0]) : "") +
": ";
if (typeNode) {
result += this.visitor.visit(typeNode, ctx);
}
result += ") {\n";
const extensionMembers = getChildNodes(node, "extensionMember");
if (extensionMembers.length > 0) {
const members = extensionMembers.map(
(m: CSTNode) => " " + this.visitor.visit(m, ctx),
);
result += members.join("\n");
}
result += "\n}";
return result;
}
visitExtensionMember(node: CSTNode, ctx: PrintContext): string {
const modifierNodes = getChildNodes(node, "modifier");
const modifiers = this.visitor.visitModifiers(modifierNodes, ctx);
const defDefinition = getFirstChild(node, "defDefinition");
const definition = defDefinition
? this.visitor.visit(defDefinition, ctx)
: "";
return modifiers ? modifiers + " " + definition : definition;
}
// Given definitions
visitGivenDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "given";
const identifiers = getChildNodes(node, "Identifier");
if (identifiers.length > 0) {
// Named given with potential parameters: given name[T](using ord: Type): Type
result += " " + getNodeImage(identifiers[0]);
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
const parameterLists = getFirstChild(node, "parameterLists");
if (parameterLists) {
result += this.visitor.visit(parameterLists, ctx);
}
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += ": " + this.visitor.visit(typeNode, ctx);
}
} else {
// Anonymous given: given Type = expression
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += " " + this.visitor.visit(typeNode, ctx);
}
}
const equalsTokens = getChildNodes(node, "Equals");
if (equalsTokens.length > 0) {
const expression = getFirstChild(node, "expression");
if (expression) {
result += " = " + this.visitor.visit(expression, ctx);
}
}
return result;
}
// Type definitions including opaque types
visitTypeDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Handle opaque types
const opaqueTokens = getChildNodes(node, "Opaque");
if (opaqueTokens.length > 0) {
result += "opaque ";
}
const identifiers = getChildNodes(node, "Identifier");
result +=
"type " + (identifiers.length > 0 ? getNodeImage(identifiers[0]) : "");
const typeParameters = getFirstChild(node, "typeParameters");
if (typeParameters) {
result += this.visitor.visit(typeParameters, ctx);
}
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += " = " + this.visitor.visit(typeNode, ctx);
}
return result;
}
// Export clauses and expressions
visitExportClause(node: CSTNode, ctx: PrintContext): string {
const exportExpression = getFirstChild(node, "exportExpression");
return (
"export " +
(exportExpression ? this.visitor.visit(exportExpression, ctx) : "")
);
}
visitExportExpression(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Build the export path
const identifiers = getChildNodes(node, "Identifier");
const dots = getChildNodes(node, "Dot");
const underscores = getChildNodes(node, "Underscore");
const givens = getChildNodes(node, "Given");
const leftBraces = getChildNodes(node, "LeftBrace");
// Add first identifier
if (identifiers.length > 0) {
result = getNodeImage(identifiers[0]);
}
// Process remaining parts
let identifierIndex = 1;
for (let i = 0; i < dots.length; i++) {
result += ".";
// Check what follows this dot
if (underscores.length > 0 && i === dots.length - 1) {
// Wildcard export
result += "_";
} else if (givens.length > 0 && i === dots.length - 1) {
// Given export
result += "given";
} else if (leftBraces.length > 0 && i === dots.length - 1) {
// Multiple export selectors
result += "{";
const exportSelectors = getChildNodes(node, "exportSelector");
if (exportSelectors.length > 0) {
const selectors = exportSelectors.map((sel: CSTNode) =>
this.visitor.visit(sel, ctx),
);
result += selectors.join(", ");
}
result += "}";
} else if (identifierIndex < identifiers.length) {
// Next identifier in path
result += getNodeImage(identifiers[identifierIndex]);
identifierIndex++;
}
}
return result;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
visitExportSelector(node: CSTNode, _ctx: PrintContext): string {
const underscores = getChildNodes(node, "Underscore");
const identifiers = getChildNodes(node, "Identifier");
const givens = getChildNodes(node, "Given");
const arrows = getChildNodes(node, "Arrow");
// Handle wildcard export
if (underscores.length > 0 && identifiers.length === 0) {
return "_";
}
// Handle given export
if (givens.length > 0 && identifiers.length === 0) {
return "given";
}
let result = "";
// Handle regular identifiers
if (identifiers.length > 0) {
result = getNodeImage(identifiers[0]);
}
// Handle given with specific identifiers: given SpecificType
if (givens.length > 0 && identifiers.length > 0) {
result = "given " + getNodeImage(identifiers[0]);
}
if (arrows.length > 0) {
result += " => ";
if (underscores.length > 0) {
result += "_";
} else if (identifiers.length > 1) {
result += getNodeImage(identifiers[1]);
}
}
return result;
}
// Context function types
visitContextFunctionType(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Handle parenthesized types
const leftParens = getChildNodes(node, "LeftParen");
if (leftParens.length > 0) {
const tupleType = getFirstChild(node, "tupleTypeOrParenthesized");
if (tupleType) {
result += "(" + this.visitor.visit(tupleType, ctx) + ")";
}
} else {
// Handle simple types
const simpleType = getFirstChild(node, "simpleType");
if (simpleType) {
result += this.visitor.visit(simpleType, ctx);
}
}
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += " ?=> " + this.visitor.visit(typeNode, ctx);
}
return result;
}
// Inline and transparent modifiers
visitInlineModifier(): string {
return "inline";
}
visitTransparentModifier(): string {
return "transparent";
}
// Using clauses
visitUsingClause(node: CSTNode, ctx: PrintContext): string {
let result = "using ";
const identifiers = getChildNodes(node, "Identifier");
if (identifiers.length > 0) {
result += getNodeImage(identifiers[0]);
}
const colonTokens = getChildNodes(node, "Colon");
if (colonTokens.length > 0) {
const typeNode = getFirstChild(node, "type");
if (typeNode) {
result += ": " + this.visitor.visit(typeNode, ctx);
}
}
return result;
}
}

View File

@@ -0,0 +1,658 @@
/**
* Statement visitor methods for import/export, package, and other statements
*/
import { getChildNodes, getFirstChild, getNodeImage } from "./utils";
import type { PrintContext, CSTNode } from "./utils";
export interface StatementVisitor {
visit(node: CSTNode, ctx: PrintContext): string;
}
export class StatementVisitorMethods {
private visitor: StatementVisitor;
constructor(visitor: StatementVisitor) {
this.visitor = visitor;
}
visitPackageClause(node: CSTNode, ctx: PrintContext): string {
const qualifiedIdentifier = getFirstChild(node, "qualifiedIdentifier");
return (
"package " +
(qualifiedIdentifier ? this.visitor.visit(qualifiedIdentifier, ctx) : "")
);
}
visitImportClause(node: CSTNode, ctx: PrintContext): string {
const importExpression = getFirstChild(node, "importExpression");
return (
"import " +
(importExpression ? this.visitor.visit(importExpression, ctx) : "")
);
}
visitImportExpression(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Build the import path
const identifiers = getChildNodes(node, "Identifier");
const dots = getChildNodes(node, "Dot");
// Add first identifier
if (identifiers.length > 0) {
result = getNodeImage(identifiers[0]);
}
// Process remaining parts
let identifierIndex = 1;
for (let i = 0; i < dots.length; i++) {
result += ".";
// Check what follows this dot
const underscores = getChildNodes(node, "Underscore");
const leftBraces = getChildNodes(node, "LeftBrace");
if (underscores.length > 0 && i === dots.length - 1) {
// Wildcard import
result += "_";
} else if (leftBraces.length > 0 && i === dots.length - 1) {
// Multiple import selectors
result += "{";
const importSelectors = getChildNodes(node, "importSelector");
if (importSelectors.length > 0) {
const selectors = importSelectors.map((sel: CSTNode) =>
this.visitor.visit(sel, ctx),
);
result += selectors.join(", ");
}
result += "}";
} else if (identifierIndex < identifiers.length) {
// Next identifier in path
result += getNodeImage(identifiers[identifierIndex]);
identifierIndex++;
}
}
return result;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
visitImportSelector(node: CSTNode, _ctx: PrintContext): string {
// Handle wildcard import
const underscores = getChildNodes(node, "Underscore");
const identifiers = getChildNodes(node, "Identifier");
if (underscores.length > 0 && identifiers.length === 0) {
return "_";
}
let result = "";
if (identifiers.length > 0) {
result = getNodeImage(identifiers[0]);
}
const arrows = getChildNodes(node, "Arrow");
if (arrows.length > 0) {
result += " => ";
const selectorUnderscores = getChildNodes(node, "Underscore");
if (selectorUnderscores.length > 0) {
result += "_";
} else if (identifiers.length > 1) {
result += getNodeImage(identifiers[1]);
}
}
return result;
}
visitExportClause(node: CSTNode, ctx: PrintContext): string {
const exportExpression = getFirstChild(node, "exportExpression");
return (
"export " +
(exportExpression ? this.visitor.visit(exportExpression, ctx) : "")
);
}
visitExportExpression(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Build the export path
const identifiers = getChildNodes(node, "Identifier");
const dots = getChildNodes(node, "Dot");
// Add first identifier
if (identifiers.length > 0) {
result = getNodeImage(identifiers[0]);
}
// Process remaining parts
let identifierIndex = 1;
for (let i = 0; i < dots.length; i++) {
result += ".";
// Check what follows this dot
const underscores = getChildNodes(node, "Underscore");
const givens = getChildNodes(node, "Given");
if (underscores.length > 0 && i === dots.length - 1) {
// Wildcard export
result += "_";
} else if (givens.length > 0 && i === dots.length - 1) {
// Given export
result += "given";
} else if (
getChildNodes(node, "LeftBrace").length > 0 &&
i === dots.length - 1
) {
// Multiple export selectors
result += "{";
const exportSelectors = getChildNodes(node, "exportSelector");
if (exportSelectors.length > 0) {
const selectors = exportSelectors.map((sel: CSTNode) =>
this.visitor.visit(sel, ctx),
);
result += selectors.join(", ");
}
result += "}";
} else if (identifierIndex < identifiers.length) {
// Next identifier in path
result += getNodeImage(identifiers[identifierIndex]);
identifierIndex++;
}
}
return result;
}
visitExportSelector(node: CSTNode): string {
const underscores = getChildNodes(node, "Underscore");
const identifiers = getChildNodes(node, "Identifier");
const givens = getChildNodes(node, "Given");
// Handle wildcard export
if (underscores.length > 0 && identifiers.length === 0) {
return "_";
}
// Handle given export
if (givens.length > 0 && identifiers.length === 0) {
return "given";
}
let result = "";
// Handle regular identifiers
if (identifiers.length > 0) {
result = getNodeImage(identifiers[0]);
}
// Handle given with specific identifiers: given SpecificType
if (givens.length > 0 && identifiers.length > 0) {
result = "given " + getNodeImage(identifiers[0]);
}
const arrows = getChildNodes(node, "Arrow");
if (arrows.length > 0) {
result += " => ";
const arrowUnderscores = getChildNodes(node, "Underscore");
if (arrowUnderscores.length > 0) {
result += "_";
} else if (identifiers.length > 1) {
result += getNodeImage(identifiers[1]);
}
}
return result;
}
visitTopLevelDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Handle modifiers (including 'case')
const modifiers = getChildNodes(node, "modifier");
if (modifiers.length > 0) {
const modifierStr = this.visitModifiers(modifiers, ctx);
result += modifierStr + " ";
}
// Handle definitions at top level
const definition = getFirstChild(node, "definition");
if (definition) {
result += this.visitor.visit(definition, ctx);
return result;
}
// Handle class definitions
const classDefinition = getFirstChild(node, "classDefinition");
if (classDefinition) {
result += this.visitor.visit(classDefinition, ctx);
return result;
}
// Handle object definitions
const objectDefinition = getFirstChild(node, "objectDefinition");
if (objectDefinition) {
result += this.visitor.visit(objectDefinition, ctx);
return result;
}
// Handle trait definitions
const traitDefinition = getFirstChild(node, "traitDefinition");
if (traitDefinition) {
result += this.visitor.visit(traitDefinition, ctx);
return result;
}
// Handle val definitions
const valDefinition = getFirstChild(node, "valDefinition");
if (valDefinition) {
result += this.visitor.visit(valDefinition, ctx);
return result;
}
// Handle var definitions
const varDefinition = getFirstChild(node, "varDefinition");
if (varDefinition) {
result += this.visitor.visit(varDefinition, ctx);
return result;
}
// Handle def definitions
const defDefinition = getFirstChild(node, "defDefinition");
if (defDefinition) {
result += this.visitor.visit(defDefinition, ctx);
return result;
}
// Handle enum definitions (Scala 3)
const enumDefinition = getFirstChild(node, "enumDefinition");
if (enumDefinition) {
result += this.visitor.visit(enumDefinition, ctx);
return result;
}
// Handle extension definitions (Scala 3)
const extensionDefinition = getFirstChild(node, "extensionDefinition");
if (extensionDefinition) {
result += this.visitor.visit(extensionDefinition, ctx);
return result;
}
// Handle given definitions (Scala 3)
const givenDefinition = getFirstChild(node, "givenDefinition");
if (givenDefinition) {
result += this.visitor.visit(givenDefinition, ctx);
return result;
}
// Handle type definitions (including opaque types)
const typeDefinition = getFirstChild(node, "typeDefinition");
if (typeDefinition) {
result += this.visitor.visit(typeDefinition, ctx);
return result;
}
// Handle assignment statements
const assignmentStatement = getFirstChild(node, "assignmentStatement");
if (assignmentStatement) {
result += this.visitor.visit(assignmentStatement, ctx);
return result;
}
// Handle expressions
const expression = getFirstChild(node, "expression");
if (expression) {
result += this.visitor.visit(expression, ctx);
return result;
}
return result;
}
visitBlockStatement(node: CSTNode, ctx: PrintContext): string {
const valDefinition = getFirstChild(node, "valDefinition");
if (valDefinition) {
return this.visitor.visit(valDefinition, ctx);
}
const varDefinition = getFirstChild(node, "varDefinition");
if (varDefinition) {
return this.visitor.visit(varDefinition, ctx);
}
const defDefinition = getFirstChild(node, "defDefinition");
if (defDefinition) {
return this.visitor.visit(defDefinition, ctx);
}
const assignmentStatement = getFirstChild(node, "assignmentStatement");
if (assignmentStatement) {
return this.visitor.visit(assignmentStatement, ctx);
}
const expression = getFirstChild(node, "expression");
if (expression) {
return this.visitor.visit(expression, ctx);
}
return "";
}
visitCompilationUnit(node: CSTNode, ctx: PrintContext): string {
const parts: string[] = [];
// Add package clause if it exists
const packageClause = getFirstChild(node, "packageClause");
if (packageClause) {
parts.push(this.visitor.visit(packageClause, ctx));
}
// Add empty line after package
if (parts.length > 0) {
parts.push("");
}
// Add import clauses
const importClauses = getChildNodes(node, "importClause");
if (importClauses.length > 0) {
importClauses.forEach((importNode: CSTNode) => {
parts.push(this.visitor.visit(importNode, ctx));
});
}
// Add empty line after imports
if (importClauses.length > 0) {
parts.push("");
}
// Add export clauses
const exportClauses = getChildNodes(node, "exportClause");
if (exportClauses.length > 0) {
exportClauses.forEach((exportNode: CSTNode) => {
parts.push(this.visitor.visit(exportNode, ctx));
});
}
// Don't add empty line after exports unless there are subsequent elements
if (exportClauses.length > 0) {
// Only add empty line if there are other elements after exports
const topLevelDefinitions = getChildNodes(node, "topLevelDefinition");
const topLevelStatements = getChildNodes(node, "topLevelStatement");
const expressions = getChildNodes(node, "expression");
const hasSubsequentElements =
topLevelDefinitions.length > 0 ||
topLevelStatements.length > 0 ||
expressions.length > 0;
if (hasSubsequentElements) {
parts.push("");
}
}
// Add top-level definitions
const topLevelDefinitions = getChildNodes(node, "topLevelDefinition");
if (topLevelDefinitions.length > 0) {
topLevelDefinitions.forEach((def: CSTNode) => {
parts.push(this.visitor.visit(def, ctx));
});
}
// Add top-level statements
const topLevelStatements = getChildNodes(node, "topLevelStatement");
if (topLevelStatements.length > 0) {
topLevelStatements.forEach((stmt: CSTNode) => {
parts.push(this.visitor.visit(stmt, ctx));
});
}
// Add top-level expressions
const expressions = getChildNodes(node, "expression");
if (expressions.length > 0) {
expressions.forEach((expr: CSTNode) => {
parts.push(this.visitor.visit(expr, ctx));
});
}
// Join parts and ensure proper file formatting
if (parts.length === 0) return "";
if (parts.length === 1) return parts[0] + "\n";
// For multiple parts, join with newlines and add trailing newline
return parts.join("\n") + "\n";
}
visitAnnotations(annotations: CSTNode[], ctx: PrintContext): string {
return annotations.map((ann) => this.visitor.visit(ann, ctx)).join(" ");
}
visitAnnotation(node: CSTNode, ctx: PrintContext): string {
const qualifiedIdentifier = getFirstChild(node, "qualifiedIdentifier");
let result =
"@" +
(qualifiedIdentifier ? this.visitor.visit(qualifiedIdentifier, ctx) : "");
// Handle multiple parameter lists: @Inject() or @Inject()(val x: Type)
const leftParens = getChildNodes(node, "LeftParen");
if (leftParens.length > 0) {
const annotationArguments = getChildNodes(node, "annotationArgument");
let argIndex = 0;
// Process each parameter list
for (let i = 0; i < leftParens.length; i++) {
result += "(";
// Determine how many arguments are in this parameter list
// We need to group arguments by parameter list
const argsInThisList: CSTNode[] = [];
// For simplicity, distribute arguments evenly across parameter lists
// In practice, this should be based on actual parsing structure
const argsPerList = Math.ceil(
annotationArguments.length / leftParens.length,
);
const endIndex = Math.min(
argIndex + argsPerList,
annotationArguments.length,
);
for (let j = argIndex; j < endIndex; j++) {
argsInThisList.push(annotationArguments[j]);
}
argIndex = endIndex;
if (argsInThisList.length > 0) {
const args = argsInThisList.map((arg: CSTNode) =>
this.visitor.visit(arg, ctx),
);
result += args.join(", ");
}
result += ")";
}
}
return result;
}
visitAnnotationArgument(node: CSTNode, ctx: PrintContext): string {
const valTokens = getChildNodes(node, "Val");
const varTokens = getChildNodes(node, "Var");
const identifiers = getChildNodes(node, "Identifier");
const colons = getChildNodes(node, "Colon");
const equals = getChildNodes(node, "Equals");
const expressions = getChildNodes(node, "expression");
const types = getChildNodes(node, "type");
// Parameter declaration: val x: Type or var y: Type
if (
(valTokens.length > 0 || varTokens.length > 0) &&
identifiers.length > 0 &&
colons.length > 0 &&
types.length > 0
) {
let result = valTokens.length > 0 ? "val " : "var ";
result += getNodeImage(identifiers[0]);
result += ": ";
result += this.visitor.visit(types[0], ctx);
// Optional default value
if (equals.length > 0 && expressions.length > 0) {
result += " = " + this.visitor.visit(expressions[0], ctx);
}
return result;
}
// Named argument: name = value
else if (
identifiers.length > 0 &&
equals.length > 0 &&
expressions.length > 0
) {
return (
getNodeImage(identifiers[0]) +
" = " +
this.visitor.visit(expressions[0], ctx)
);
}
// Positional argument
else if (expressions.length > 0) {
return this.visitor.visit(expressions[0], ctx);
}
return "";
}
visitModifiers(modifiers: CSTNode[], ctx: PrintContext): string {
return modifiers.map((mod) => this.visitor.visit(mod, ctx)).join(" ");
}
visitDefinition(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Handle annotations
const annotations = getChildNodes(node, "annotation");
if (annotations.length > 0) {
const annotationStr = this.visitAnnotations(annotations, ctx);
result += annotationStr + " ";
}
// Handle modifiers
const modifiers = getChildNodes(node, "modifier");
if (modifiers.length > 0) {
const modifierStr = this.visitModifiers(modifiers, ctx);
result += modifierStr + " ";
}
// Handle specific definition types
const classDefinition = getFirstChild(node, "classDefinition");
if (classDefinition) {
result += this.visitor.visit(classDefinition, ctx);
} else {
const objectDefinition = getFirstChild(node, "objectDefinition");
if (objectDefinition) {
result += this.visitor.visit(objectDefinition, ctx);
} else {
const traitDefinition = getFirstChild(node, "traitDefinition");
if (traitDefinition) {
result += this.visitor.visit(traitDefinition, ctx);
} else {
const enumDefinition = getFirstChild(node, "enumDefinition");
if (enumDefinition) {
result += this.visitor.visit(enumDefinition, ctx);
} else {
const extensionDefinition = getFirstChild(
node,
"extensionDefinition",
);
if (extensionDefinition) {
result += this.visitor.visit(extensionDefinition, ctx);
} else {
const valDefinition = getFirstChild(node, "valDefinition");
if (valDefinition) {
result += this.visitor.visit(valDefinition, ctx);
} else {
const varDefinition = getFirstChild(node, "varDefinition");
if (varDefinition) {
result += this.visitor.visit(varDefinition, ctx);
} else {
const defDefinition = getFirstChild(node, "defDefinition");
if (defDefinition) {
result += this.visitor.visit(defDefinition, ctx);
} else {
const givenDefinition = getFirstChild(
node,
"givenDefinition",
);
if (givenDefinition) {
result += this.visitor.visit(givenDefinition, ctx);
} else {
const typeDefinition = getFirstChild(
node,
"typeDefinition",
);
if (typeDefinition) {
result += this.visitor.visit(typeDefinition, ctx);
} else {
const assignmentStatement = getFirstChild(
node,
"assignmentStatement",
);
if (assignmentStatement) {
result += this.visitor.visit(
assignmentStatement,
ctx,
);
}
}
}
}
}
}
}
}
}
}
}
return result;
}
visitPattern(node: CSTNode, ctx: PrintContext): string {
const identifiers = getChildNodes(node, "Identifier");
if (identifiers.length > 0) {
return getNodeImage(identifiers[0]);
}
const underscores = getChildNodes(node, "Underscore");
if (underscores.length > 0) {
return "_";
}
const literal = getFirstChild(node, "literal");
if (literal) {
return this.visitor.visit(literal, ctx);
}
const leftParens = getChildNodes(node, "LeftParen");
if (leftParens.length > 0) {
// Tuple pattern or parenthesized pattern
const patterns = getChildNodes(node, "pattern");
if (patterns.length > 1) {
const patternResults = patterns.map((p: CSTNode) =>
this.visitor.visit(p, ctx),
);
return "(" + patternResults.join(", ") + ")";
} else if (patterns.length === 1) {
return "(" + this.visitor.visit(patterns[0], ctx) + ")";
}
}
const patterns = getChildNodes(node, "pattern");
if (patterns.length > 0) {
// Constructor pattern or other complex patterns
return this.visitor.visit(patterns[0], ctx);
}
return "";
}
}

View File

@@ -0,0 +1,474 @@
/**
* Type-related visitor methods for handling type expressions, type parameters, and type systems
*/
import { getChildNodes, getFirstChild, getNodeImage } from "./utils";
import type { PrintContext, CSTNode } from "./utils";
export interface TypeVisitor {
visit(node: CSTNode, ctx: PrintContext): string;
}
export class TypeVisitorMethods {
private visitor: TypeVisitor;
constructor(visitor: TypeVisitor) {
this.visitor = visitor;
}
visitType(node: CSTNode, ctx: PrintContext): string {
const matchType = getFirstChild(node, "matchType");
return matchType ? this.visitor.visit(matchType, ctx) : "";
}
visitMatchType(node: CSTNode, ctx: PrintContext): string {
const unionType = getFirstChild(node, "unionType");
let result = unionType ? this.visitor.visit(unionType, ctx) : "";
const matchTokens = getChildNodes(node, "Match");
if (matchTokens.length > 0) {
result += " match {";
const matchTypeCases = getChildNodes(node, "matchTypeCase");
if (matchTypeCases.length > 0) {
for (const caseNode of matchTypeCases) {
result += "\n " + this.visitor.visit(caseNode, ctx);
}
result += "\n";
}
result += "}";
}
return result;
}
visitMatchTypeCase(node: CSTNode, ctx: PrintContext): string {
const types = getChildNodes(node, "type");
if (types.length >= 2) {
const leftType = this.visitor.visit(types[0], ctx);
const rightType = this.visitor.visit(types[1], ctx);
return `case ${leftType} => ${rightType}`;
}
return "";
}
visitUnionType(node: CSTNode, ctx: PrintContext): string {
const types = getChildNodes(node, "intersectionType");
if (types.length === 1) {
return this.visitor.visit(types[0], ctx);
}
const typeStrings = types.map((t: CSTNode) => this.visitor.visit(t, ctx));
return typeStrings.join(" | ");
}
visitIntersectionType(node: CSTNode, ctx: PrintContext): string {
const types = getChildNodes(node, "baseType");
if (types.length === 1) {
return this.visitor.visit(types[0], ctx);
}
const typeStrings = types.map((t: CSTNode) => this.visitor.visit(t, ctx));
return typeStrings.join(" & ");
}
visitContextFunctionType(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Handle parenthesized types
const leftParen = getChildNodes(node, "LeftParen");
if (leftParen.length > 0) {
const tupleType = getFirstChild(node, "tupleTypeOrParenthesized");
if (tupleType) {
result += "(" + this.visitor.visit(tupleType, ctx) + ")";
}
} else {
// Handle simple types
const simpleType = getFirstChild(node, "simpleType");
if (simpleType) {
result += this.visitor.visit(simpleType, ctx);
}
}
const type = getFirstChild(node, "type");
if (type) {
result += " ?=> " + this.visitor.visit(type, ctx);
}
return result;
}
visitBaseType(node: CSTNode, ctx: PrintContext): string {
// Handle type lambda: [X] =>> F[X]
const typeLambda = getFirstChild(node, "typeLambda");
if (typeLambda) {
return this.visitor.visit(typeLambda, ctx);
}
// Handle polymorphic function type: [T] => T => T
const polymorphicFunctionType = getFirstChild(
node,
"polymorphicFunctionType",
);
if (polymorphicFunctionType) {
return this.visitor.visit(polymorphicFunctionType, ctx);
}
// Handle context function type: String ?=> Int
const contextFunctionType = getFirstChild(node, "contextFunctionType");
if (contextFunctionType) {
return this.visitor.visit(contextFunctionType, ctx);
}
// Handle dependent function type: (x: Int) => Vector[x.type]
const dependentFunctionType = getFirstChild(node, "dependentFunctionType");
if (dependentFunctionType) {
return this.visitor.visit(dependentFunctionType, ctx);
}
// Handle parenthesized types or tuple types: (String | Int) or (A, B)
const leftParen = getChildNodes(node, "LeftParen");
const tupleType = getFirstChild(node, "tupleTypeOrParenthesized");
if (leftParen.length > 0 && tupleType) {
return "(" + this.visitor.visit(tupleType, ctx) + ")";
}
// Handle simple types with array notation
const simpleType = getFirstChild(node, "simpleType");
let result = "";
if (simpleType) {
result = this.visitor.visit(simpleType, ctx);
} else {
// Handle direct token cases like Array, List, etc.
if ("children" in node && node.children) {
const children = node.children;
for (const [key, tokens] of Object.entries(children)) {
if (
Array.isArray(tokens) &&
tokens.length > 0 &&
"image" in tokens[0]
) {
// Check if this is a type name token (not brackets or keywords)
if (
!["LeftBracket", "RightBracket", "typeArgument"].includes(key)
) {
result = getNodeImage(tokens[0]);
break;
}
}
}
}
}
if (!result) {
return "";
}
// Handle array types like Array[String]
const leftBrackets = getChildNodes(node, "LeftBracket");
const typeArguments = getChildNodes(node, "typeArgument");
for (let i = 0; i < leftBrackets.length && i < typeArguments.length; i++) {
result += "[" + this.visitor.visit(typeArguments[i], ctx) + "]";
}
return result;
}
visitTupleTypeOrParenthesized(node: CSTNode, ctx: PrintContext): string {
const types = getChildNodes(node, "type");
if (types.length === 1) {
return this.visitor.visit(types[0], ctx);
}
const typeStrings = types.map((t: CSTNode) => this.visitor.visit(t, ctx));
return typeStrings.join(", ");
}
visitSimpleType(node: CSTNode, ctx: PrintContext): string {
const qualifiedId = getFirstChild(node, "qualifiedIdentifier");
if (!qualifiedId) {
return "";
}
let result = this.visitor.visit(qualifiedId, ctx);
// Handle type parameters like List[Int] or Kind Projector like Map[String, *]
const leftBrackets = getChildNodes(node, "LeftBracket");
if (leftBrackets.length > 0) {
const typeArgs = getChildNodes(node, "typeArgument");
const typeStrings = typeArgs.map((t: CSTNode) =>
this.visitor.visit(t, ctx),
);
result += "[" + typeStrings.join(", ") + "]";
}
return result;
}
visitTypeArgument(node: CSTNode, ctx: PrintContext): string {
// Handle Kind Projector notation: *
const star = getChildNodes(node, "Star");
if (star.length > 0) {
return "*";
}
// Handle regular type
const type = getFirstChild(node, "type");
if (type) {
return this.visitor.visit(type, ctx);
}
// Handle type argument union structure
const typeArgumentUnion = getFirstChild(node, "typeArgumentUnion");
if (typeArgumentUnion) {
return this.visitor.visit(typeArgumentUnion, ctx);
}
// Handle direct type tokens like Array[t] within type arguments
if ("children" in node && node.children) {
const children = node.children;
let result = "";
// Find the type name token
for (const [key, tokens] of Object.entries(children)) {
if (
Array.isArray(tokens) &&
tokens.length > 0 &&
"image" in tokens[0]
) {
if (!["LeftBracket", "RightBracket", "typeArgument"].includes(key)) {
result = getNodeImage(tokens[0]);
break;
}
}
}
if (result) {
// Handle type parameters like Array[t] within type arguments
const leftBrackets = getChildNodes(node, "LeftBracket");
const typeArguments = getChildNodes(node, "typeArgument");
for (
let i = 0;
i < leftBrackets.length && i < typeArguments.length;
i++
) {
result += "[" + this.visitor.visit(typeArguments[i], ctx) + "]";
}
return result;
}
}
return "";
}
visitTypeLambda(node: CSTNode, ctx: PrintContext): string {
let result = "[";
const parameters = getChildNodes(node, "typeLambdaParameter");
if (parameters.length > 0) {
const parameterStrings = parameters.map((param: CSTNode) =>
this.visitor.visit(param, ctx),
);
result += parameterStrings.join(", ");
}
result += "] =>> ";
const type = getFirstChild(node, "type");
if (type) {
result += this.visitor.visit(type, ctx);
}
return result;
}
visitTypeLambdaParameter(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Add variance annotation if present
const plus = getChildNodes(node, "Plus");
const minus = getChildNodes(node, "Minus");
if (plus.length > 0) {
result += "+";
} else if (minus.length > 0) {
result += "-";
}
const identifiers = getChildNodes(node, "Identifier");
if (identifiers.length > 0) {
result += getNodeImage(identifiers[0]);
}
const subtypeOf = getChildNodes(node, "SubtypeOf");
const supertypeOf = getChildNodes(node, "SupertypeOf");
const type = getFirstChild(node, "type");
if (subtypeOf.length > 0 && type) {
result += " <: " + this.visitor.visit(type, ctx);
} else if (supertypeOf.length > 0 && type) {
result += " >: " + this.visitor.visit(type, ctx);
}
return result;
}
visitDependentFunctionType(node: CSTNode, ctx: PrintContext): string {
let result = "(";
const parameters = getChildNodes(node, "dependentParameter");
if (parameters.length > 0) {
const parameterStrings = parameters.map((param: CSTNode) =>
this.visitor.visit(param, ctx),
);
result += parameterStrings.join(", ");
}
result += ") => ";
const type = getFirstChild(node, "type");
if (type) {
result += this.visitor.visit(type, ctx);
}
return result;
}
visitDependentParameter(node: CSTNode, ctx: PrintContext): string {
const identifiers = getChildNodes(node, "Identifier");
if (identifiers.length === 0) {
return "";
}
let result = getNodeImage(identifiers[0]);
const type = getFirstChild(node, "type");
if (type) {
result += ": " + this.visitor.visit(type, ctx);
}
return result;
}
visitPolymorphicFunctionType(node: CSTNode, ctx: PrintContext): string {
let result = "[";
const parameters = getChildNodes(node, "polymorphicTypeParameter");
if (parameters.length > 0) {
const parameterStrings = parameters.map((param: CSTNode) =>
this.visitor.visit(param, ctx),
);
result += parameterStrings.join(", ");
}
result += "] => ";
const type = getFirstChild(node, "type");
if (type) {
result += this.visitor.visit(type, ctx);
}
return result;
}
visitPolymorphicTypeParameter(node: CSTNode, ctx: PrintContext): string {
let result = "";
// Handle variance annotation
const plus = getChildNodes(node, "Plus");
const minus = getChildNodes(node, "Minus");
if (plus.length > 0) {
result += "+";
} else if (minus.length > 0) {
result += "-";
}
const identifiers = getChildNodes(node, "Identifier");
if (identifiers.length > 0) {
result += getNodeImage(identifiers[0]);
}
// Handle type bounds
const subtypeOf = getChildNodes(node, "SubtypeOf");
const supertypeOf = getChildNodes(node, "SupertypeOf");
const type = getFirstChild(node, "type");
if (subtypeOf.length > 0 && type) {
result += " <: " + this.visitor.visit(type, ctx);
}
if (supertypeOf.length > 0 && type) {
result += " >: " + this.visitor.visit(type, ctx);
}
return result;
}
visitTypeArgumentUnion(node: CSTNode, ctx: PrintContext): string {
const typeArgumentIntersections = getChildNodes(
node,
"typeArgumentIntersection",
);
if (typeArgumentIntersections.length === 1) {
return this.visitor.visit(typeArgumentIntersections[0], ctx);
}
// Handle union types with | operator
if (typeArgumentIntersections.length > 1) {
const typeStrings = typeArgumentIntersections.map((t: CSTNode) =>
this.visitor.visit(t, ctx),
);
return typeStrings.join(" | ");
}
return "";
}
visitTypeArgumentIntersection(node: CSTNode, ctx: PrintContext): string {
const typeArgumentSimples = getChildNodes(node, "typeArgumentSimple");
if (typeArgumentSimples.length === 1) {
return this.visitor.visit(typeArgumentSimples[0], ctx);
}
// Handle intersection types with & operator
if (typeArgumentSimples.length > 1) {
const typeStrings = typeArgumentSimples.map((t: CSTNode) =>
this.visitor.visit(t, ctx),
);
return typeStrings.join(" & ");
}
return "";
}
visitTypeArgumentSimple(node: CSTNode, ctx: PrintContext): string {
const qualifiedIdentifier = getFirstChild(node, "qualifiedIdentifier");
if (qualifiedIdentifier) {
let result = this.visitor.visit(qualifiedIdentifier, ctx);
// Handle type parameters like List[*] within type arguments
const leftBrackets = getChildNodes(node, "LeftBracket");
if (leftBrackets.length > 0) {
const typeArgs = getChildNodes(node, "typeArgument");
const typeStrings = typeArgs.map((t: CSTNode) =>
this.visitor.visit(t, ctx),
);
result += "[" + typeStrings.join(", ") + "]";
}
return result;
}
// Handle simple type structures like List[*]
const simpleType = getFirstChild(node, "simpleType");
if (simpleType) {
return this.visitor.visit(simpleType, ctx);
}
// Handle base type structures
const baseType = getFirstChild(node, "baseType");
if (baseType) {
return this.visitor.visit(baseType, ctx);
}
// Handle other type argument patterns
const identifier = getFirstChild(node, "Identifier");
if (identifier) {
return getNodeImage(identifier);
}
return "";
}
}

View File

@@ -0,0 +1,295 @@
import type { ScalaCstNode, IToken } from "../scala-parser";
/**
* ビジターパターンで使用する共有ユーティリティとフォーマットヘルパー
*/
export interface PrettierOptions {
printWidth?: number;
tabWidth?: number;
useTabs?: boolean;
semi?: boolean;
singleQuote?: boolean;
trailingComma?: "all" | "multiline" | "none";
scalaLineWidth?: number; // Deprecated, for backward compatibility
}
// CST要素ードまたはトークンのユニオン型
export type CSTNode = ScalaCstNode | IToken;
export type PrintContext = {
path: unknown;
options: PrettierOptions;
print: (node: CSTNode) => string;
indentLevel: number;
};
/**
* nullチェック付きでードの子要素に安全にアクセス
* @param node - 対象ノード
* @returns 子要素のマップ
*/
export function getChildren(node: CSTNode): Record<string, CSTNode[]> {
if ("children" in node && node.children) {
return node.children as Record<string, CSTNode[]>;
}
return {};
}
/**
* キーで指定した子ノードを安全に取得
* @param node - 対象ノード
* @param key - 子ノードのキー
* @returns 子ノードの配列
*/
export function getChildNodes(node: CSTNode, key: string): CSTNode[] {
return getChildren(node)[key] || [];
}
/**
* キーで指定した最初の子ノードを安全に取得
* @param node - 対象ノード
* @param key - 子ノードのキー
* @returns 最初の子ードまたはundefined
*/
export function getFirstChild(node: CSTNode, key: string): CSTNode | undefined {
const children = getChildNodes(node, key);
return children.length > 0 ? children[0] : undefined;
}
/**
* ードのimageプロパティを安全に取得
* @param node - 対象ノード
* @returns imageプロパティまたは空文字列
*/
export function getNodeImage(node: CSTNode): string {
if ("image" in node && node.image) {
return node.image;
}
return "";
}
/**
* nullまたはundefinedの可能性があるードのimageを安全に取得
* @param node - 対象ードnull/undefined可
* @returns imageプロパティまたは空文字列
*/
export function getNodeImageSafe(node: CSTNode | undefined | null): string {
if (node && "image" in node && node.image) {
return node.image;
}
return "";
}
/**
* 有効なprintWidthを取得scalafmt互換性をサポート
* @param ctx - 印刷コンテキスト
* @returns 有効な行幅
*/
export function getPrintWidth(ctx: PrintContext): number {
// PrettierのprintWidthを使用scalafmtのmaxColumn互換
if (ctx.options.printWidth) {
return ctx.options.printWidth;
}
// 後方互換性のため非推奨のscalaLineWidthにフォールバック
if (ctx.options.scalaLineWidth) {
// 開発環境で非推奨警告を表示
if (process.env.NODE_ENV !== "production") {
console.warn(
"scalaLineWidth is deprecated. Use printWidth instead for scalafmt compatibility.",
);
}
return ctx.options.scalaLineWidth;
}
// デフォルト値
return 80;
}
/**
* 有効なtabWidthを取得scalafmt互換性をサポート
* @param ctx - 印刷コンテキスト
* @returns 有効なタブ幅
*/
export function getTabWidth(ctx: PrintContext): number {
// PrettierのtabWidthを使用scalafmtのindent.main互換
if (ctx.options.tabWidth) {
return ctx.options.tabWidth;
}
// デフォルト値
return 2;
}
/**
* セミコロンのフォーマットを処理Prettierのsemiオプションをサポート
* @param statement - ステートメント文字列
* @param ctx - 印刷コンテキスト
* @returns フォーマット済みのステートメント
*/
export function formatStatement(statement: string, ctx: PrintContext): string {
// Prettierのsemiオプションを使用
// プラグインはScala用にデフォルトsemi=falseを設定するが、明示的なユーザー選択を尊重
const useSemi = ctx.options.semi === true;
// 既存の末尾セミコロンを削除
const cleanStatement = statement.replace(/;\s*$/, "");
// リクエストされた場合セミコロンを追加
if (useSemi) {
return cleanStatement + ";";
}
return cleanStatement;
}
/**
* 文字列クォートのフォーマットを処理PrettierのsingleQuoteオプションをサポート
* @param content - 文字列リテラルの内容
* @param ctx - 印刷コンテキスト
* @returns フォーマット済みの文字列
*/
export function formatStringLiteral(
content: string,
ctx: PrintContext,
): string {
// PrettierのsingleQuoteオプションを使用
const useSingleQuote = ctx.options.singleQuote === true;
// 文字列補間をスキップs"、f"、raw"などで始まる)
if (content.match(/^[a-zA-Z]"/)) {
return content; // 補間文字列は変更しない
}
// 内容を抽出
let innerContent = content;
if (content.startsWith('"') && content.endsWith('"')) {
innerContent = content.slice(1, -1);
} else if (content.startsWith("'") && content.endsWith("'")) {
innerContent = content.slice(1, -1);
} else {
return content; // Not a string literal
}
// Choose target quote based on option
const targetQuote = useSingleQuote ? "'" : '"';
// Handle escaping if necessary
if (targetQuote === "'") {
// Converting to single quotes: escape single quotes, unescape double quotes
innerContent = innerContent.replace(/\\"/g, '"').replace(/'/g, "\\'");
} else {
// Converting to double quotes: escape double quotes, unescape single quotes
innerContent = innerContent.replace(/\\'/g, "'").replace(/"/g, '\\"');
}
return targetQuote + innerContent + targetQuote;
}
/**
* Helper method to handle indentation using configured tab width
*/
export function createIndent(level: number, ctx: PrintContext): string {
const tabWidth = getTabWidth(ctx);
const useTabs = ctx.options.useTabs === true;
if (useTabs) {
return "\t".repeat(level);
} else {
return " ".repeat(level * tabWidth);
}
}
/**
* Helper method to handle trailing comma formatting
*/
export function formatTrailingComma(
elements: string[],
ctx: PrintContext,
isMultiline: boolean = false,
): string {
if (elements.length === 0) return "";
const trailingComma = ctx.options.trailingComma;
if (
trailingComma === "all" ||
(trailingComma === "multiline" && isMultiline)
) {
return elements.join(", ") + ",";
}
return elements.join(", ");
}
/**
* Attach original comments to the formatted result
*/
export function attachOriginalComments(
result: string,
originalComments: CSTNode[],
): string {
if (!originalComments || originalComments.length === 0) {
return result;
}
const lines = result.split("\n");
const commentMap = new Map<number, string[]>();
// Group comments by line number
originalComments.forEach((comment) => {
const line = ("startLine" in comment && comment.startLine) || 1;
if (!commentMap.has(line)) {
commentMap.set(line, []);
}
let commentText = "";
if ("image" in comment && comment.image) {
commentText = comment.image;
} else if ("value" in comment && comment.value) {
commentText = String(comment.value);
}
if (commentText) {
commentMap.get(line)!.push(commentText);
}
});
// Insert comments into lines
const resultLines: string[] = [];
lines.forEach((line, index) => {
const lineNumber = index + 1;
if (commentMap.has(lineNumber)) {
const comments = commentMap.get(lineNumber)!;
comments.forEach((comment) => {
resultLines.push(comment);
});
}
resultLines.push(line);
});
return resultLines.join("\n");
}
/**
* Format method or class parameters with proper line breaks
*/
export function formatParameterList(
parameters: CSTNode[],
ctx: PrintContext,
visitFn: (param: CSTNode, ctx: PrintContext) => string,
): string {
if (parameters.length === 0) return "";
const paramStrings = parameters.map((param) => visitFn(param, ctx));
const printWidth = getPrintWidth(ctx);
const joined = paramStrings.join(", ");
// If the line is too long, break into multiple lines
if (joined.length > printWidth * 0.7) {
const indent = createIndent(1, ctx);
return "\n" + indent + paramStrings.join(",\n" + indent) + "\n";
}
return joined;
}