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,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;
}