diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0f4f2cf..f181520 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -39,6 +39,7 @@ "@codemirror/view": "^6.38.2", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", + "@prettier/plugin-xml": "^3.4.2", "codemirror": "^6.0.2", "codemirror-lang-elixir": "^4.0.0", "colors-named": "^1.0.2", @@ -46,6 +47,7 @@ "franc-min": "^6.2.0", "hsl-matcher": "^1.2.4", "java-parser": "^3.0.1", + "jinx-rust": "^0.1.6", "jsox": "^1.2.123", "lezer": "^0.13.5", "linguist-languages": "^9.0.0", @@ -54,6 +56,7 @@ "pinia": "^3.0.3", "pinia-plugin-persistedstate": "^4.5.0", "prettier": "^3.6.2", + "prettier-plugin-rust": "^0.1.9", "remarkable": "^2.0.1", "sass": "^1.92.1", "sql-formatter": "^15.6.9", @@ -1854,6 +1857,18 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@prettier/plugin-xml": { + "version": "3.4.2", + "resolved": "https://registry.npmmirror.com/@prettier/plugin-xml/-/plugin-xml-3.4.2.tgz", + "integrity": "sha512-/UyNlHfkuLXG6Ed85KB0WBF283xn2yavR+UtRibBRUcvEJId2DSLdGXwJ/cDa1X++SWDPzq3+GSFniHjkNy7yg==", + "license": "MIT", + "dependencies": { + "@xml-tools/parser": "^1.0.11" + }, + "peerDependencies": { + "prettier": "^3.0.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.29", "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", @@ -2730,6 +2745,24 @@ "dev": true, "license": "MIT" }, + "node_modules/@xml-tools/parser": { + "version": "1.0.11", + "resolved": "https://registry.npmmirror.com/@xml-tools/parser/-/parser-1.0.11.tgz", + "integrity": "sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==", + "license": "Apache-2.0", + "dependencies": { + "chevrotain": "7.1.1" + } + }, + "node_modules/@xml-tools/parser/node_modules/chevrotain": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/chevrotain/-/chevrotain-7.1.1.tgz", + "integrity": "sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==", + "license": "Apache-2.0", + "dependencies": { + "regexp-to-ast": "0.5.0" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", @@ -4821,6 +4854,12 @@ "lodash": "4.17.21" } }, + "node_modules/jinx-rust": { + "version": "0.1.6", + "resolved": "https://registry.npmmirror.com/jinx-rust/-/jinx-rust-0.1.6.tgz", + "integrity": "sha512-qP+wtQL1PrDDFwtPKhNGtjWOmijCrKdfUHWTV2G/ikxfjrh+cjdvkQTmny9RAsVF0jiui9m+F0INWu4cuRcZeQ==", + "license": "MIT" + }, "node_modules/jiti": { "version": "2.4.2", "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.4.2.tgz", @@ -5785,6 +5824,31 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-plugin-rust": { + "version": "0.1.9", + "resolved": "https://registry.npmmirror.com/prettier-plugin-rust/-/prettier-plugin-rust-0.1.9.tgz", + "integrity": "sha512-n1DTTJQaHMdnoG/+nKUvBm3EKsMVWsYES2UPCiOPiZdBrmuAO/pX++m7L3+Hz3uuhtddpH0HRKHB2F3jbtJBOQ==", + "license": "MIT", + "dependencies": { + "jinx-rust": "0.1.6", + "prettier": "^2.7.1" + } + }, + "node_modules/prettier-plugin-rust/node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmmirror.com/process/-/process-0.11.10.tgz", @@ -5977,6 +6041,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/regexp-to-ast": { + "version": "0.5.0", + "resolved": "https://registry.npmmirror.com/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz", + "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==", + "license": "MIT" + }, "node_modules/remarkable": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/remarkable/-/remarkable-2.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2196178..9ace55a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,6 +43,7 @@ "@codemirror/view": "^6.38.2", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", + "@prettier/plugin-xml": "^3.4.2", "codemirror": "^6.0.2", "codemirror-lang-elixir": "^4.0.0", "colors-named": "^1.0.2", @@ -50,6 +51,7 @@ "franc-min": "^6.2.0", "hsl-matcher": "^1.2.4", "java-parser": "^3.0.1", + "jinx-rust": "^0.1.6", "jsox": "^1.2.123", "lezer": "^0.13.5", "linguist-languages": "^9.0.0", @@ -58,6 +60,7 @@ "pinia": "^3.0.3", "pinia-plugin-persistedstate": "^4.5.0", "prettier": "^3.6.2", + "prettier-plugin-rust": "^0.1.9", "remarkable": "^2.0.1", "sass": "^1.92.1", "sql-formatter": "^15.6.9", diff --git a/frontend/src/utils/prettier/plugins/rust/format/comments.ts b/frontend/src/utils/prettier/plugins/rust/format/comments.ts new file mode 100644 index 0000000..f6d9719 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/format/comments.ts @@ -0,0 +1,824 @@ +import { CommentOrDocComment, LocArray, Node, NodeType, NodeWithBodyOrCases } from "jinx-rust"; +import { + end, + getBodyOrCases, + getLastParameter, + hasOuterAttributes, + isInner, + is_Attribute, + is_AttributeOrDocComment, + is_BlockCommentKind, + is_BlockCommentNode, + is_Comment, + is_CommentOrDocComment, + is_ExpressionWithBodyOrCases, + is_ExternSpecifier, + is_FlowControlExpression, + is_FunctionDeclaration, + is_FunctionNode, + is_IfBlockExpression, + is_LineCommentKind, + is_LineCommentNode, + is_LocArray, + is_MacroRule, + is_NodeWithBodyOrCases, + is_ReassignmentNode, + is_StatementNode, + is_StructLiteralProperty, + is_StructLiteralPropertySpread, + nisAnyOf, + ownStart, + start, +} from "jinx-rust/utils"; +import { is_CallExpression_or_CallLikeMacroInvocation } from "../transform"; +import { Narrow, assert, exit, iLast, last_of, maybe_last_of } from "../utils/common"; +import { is_MemberAccessLike, is_xVariableEqualishLike } from "./core"; +import { + AnyComment, + CustomOptions, + DCM, + Doc, + MutatedAttribute, + NodeWithComments, + PrettierCommentInfo, + breakParent, + cursor, + hardline, + indent, + join, + line, + lineSuffix, + literalline, +} from "./external"; +import { assertPathAtNode, canAttachComment, getAllComments, getContext, getNode, getOptions, pathCallEach } from "./plugin"; +import { shouldPrintOuterAttributesAbove } from "./styling"; + +function addCommentHelper(node: Node, comment: AnyComment, leading = false, trailing = false) { + __DEV__: assert(!handled(comment)); + ((node as NodeWithComments).comments ??= []).push(comment); + (comment.leading = leading), (comment.trailing = trailing), (comment.printed = false); +} + +function addLeadingComment(node: Node, comment: AnyComment) { + addCommentHelper(node, comment, true); +} +function addDanglingComment(node: Node, comment: AnyComment, marker: DCM) { + addCommentHelper(node, comment); + comment.marker = marker; +} +function addTrailingComment(node: Node, comment: AnyComment) { + addCommentHelper(node, comment, false, true); +} +export function setPrettierIgnoreTarget(node: Node, comment: AnyComment) { + __DEV__: Narrow(node), assert(isPrettierIgnoreComment(comment) || isPrettierIgnoreAttribute(comment)); + comment.unignore = true; + node.prettierIgnore = true; +} + +function hasComments(node: T): node is NodeWithComments { + return "comments" in node && node.comments.length > 0; +} + +export function printDanglingComments(enclosingNode: Node, sameIndent: boolean, marker?: DCM) { + if (hasComments(enclosingNode)) { + const printed: Doc[] = []; + pathCallEach(enclosingNode, "comments", (comment) => { + if (isDangling(comment) && (!marker || comment.marker === marker)) { + printed.push(printComment(comment)); + } + }); + if (printed.length > 0) { + return sameIndent // + ? join(hardline, printed) + : indent([hardline, join(hardline, printed)]); + } + } + return ""; +} + +export function needsHardlineAfterDanglingComment(node: Node) { + if (!hasComment(node)) return false; + const lastDanglingComment = maybe_last_of(getComments(node, CF.Dangling)); + return lastDanglingComment && is_LineCommentNode(lastDanglingComment); +} +export function setDidPrintComment(comment: AnyComment) { + comment.printed = true; +} + +function printComment(comment: AnyComment) { + __DEV__: assertPathAtNode("printComment", comment); + __DEV__: assert(handled(comment), `Assertion failed: Comment was not printed at ${comment.loc.url()}`, comment); + setDidPrintComment(comment); + return getContext().options.printer.printComment!(getContext().path as any, getOptions()); +} + +export function isPreviousLineEmpty(node: Node) { + let index = start(node) - 1; + index = skipSpaces(index, true) as number; + index = skipNewline(index, true) as number; + index = skipSpaces(index, true) as number; + return index !== skipNewline(index, true); +} +export function hasBreaklineBefore(node: Node) { + return hasNewline(start(node) - 1, true); +} + +export function hasBreaklineAfter(node: Node) { + return hasNewline(end(node)); +} + +export function printCommentsSeparately(ignored?: Set) { + const node = getNode(); + __DEV__: Narrow(node); + + const leading: Doc[] = []; + const trailing: Doc[] = []; + let hasTrailingLineComment = false; + let hadLeadingBlockComment = false; + + if ("comments" in node) { + pathCallEach(node, "comments", (comment) => { + if (ignored?.has(comment)) { + return; + } else if (isLeading(comment)) { + leading.push(printLeadingComment(comment)); + } else if (isTrailing(comment)) { + trailing.push(printTrailingComment(comment)); + } + }); + } + + if (node === getOptions().cursorNode) { + leading.unshift(cursor); + trailing.push(cursor); + } + + return (leading.length | trailing.length) > 0 ? { leading, trailing } : ({ leading: "", trailing: "" } as const); + + function printLeadingComment(comment: AnyComment) { + if (is_Attribute(comment) && !comment.inner) { + const printed = printComment(comment); + return [printed, " "]; + } + hadLeadingBlockComment ||= is_BlockCommentKind(comment) && hasBreaklineBefore(comment); + return [ + printComment(comment), + is_BlockCommentKind(comment) + ? hasBreaklineAfter(comment) // + ? hadLeadingBlockComment + ? hardline + : line + : " " + : hardline, + hasNewline(skipNewline(skipSpaces(end(comment)))) ? hardline : "", + ]; + } + + function printTrailingComment(comment: AnyComment) { + const printed = printComment(comment); + return hasBreaklineBefore(comment) + ? lineSuffix([hardline, isPreviousLineEmpty(comment) ? hardline : "", printed]) + : is_BlockCommentNode(comment) + ? [" ", printed] + : lineSuffix([" ", printed, hasTrailingLineComment === (hasTrailingLineComment = true) ? hardline : breakParent]); + } +} + +export function getPostLeadingComment(comment: AnyComment) { + // console.log(comment.loc.url()); + // is_BlockCommentKind(comment) + // ? hasBreaklineAfter(comment) // + // ? hasBreaklineBefore(comment) + // ? hardline + // : line + // : " " + // : hardline, + return hasNewline(skipNewline(skipSpaces(end(comment)))) ? hardline : ""; +} + +export function withComments(node: Node, printed: D, ignored?: Set): D | Doc[] { + __DEV__: assertPathAtNode("withComments", node); + const { leading, trailing } = printCommentsSeparately(ignored); + return leading || trailing ? [...leading!, printed, ...trailing!] : printed; + // return needsOuterParens(node) ? group(["(", indent([softline, parts]), softline, ")"]) : parts; + // return parts; +} +export function getComments(node: Node, ...args: Parameters): AnyComment[] { + __DEV__: Narrow(node); + // if (!node || !node.comments) return []; + // if (args.length === 0) return node.comments; + // return args.length > 0 ? node.comments.filter(getCommentTestFunction(...args)) : node.comments; + return node && node.comments // + ? args.length > 0 + ? node.comments.filter(getCommentTestFunction(...args)) + : node.comments + : []; +} + +export function getFirstComment(node: Node, flags: CF, fn?: (comment: AnyComment) => boolean): AnyComment | undefined { + const r = getComments(node, flags | CF.First, fn); + return r.length === 0 ? undefined : r[0]; +} + +export function escapeComments(flags: number, fn?: (comment: AnyComment) => boolean) { + const comments = getAllComments().filter(getCommentTestFunction(flags, fn)) as AnyComment[]; + comments.forEach(setDidPrintComment); + return new Set(comments); +} + +export const enum CF { + Leading = 1 << 1, + Trailing = 1 << 2, + Dangling = 1 << 3, + Block = 1 << 4, + Line = 1 << 5, + PrettierIgnore = 1 << 6, + First = 1 << 7, + Last = 1 << 8, +} +export function isPrettierIgnoreComment(comment: AnyComment) { + return is_Comment(comment) && /^\s*prettier-ignore\s*/.test(comment.value) && !comment.unignore; +} +export function isPrettierIgnoreAttribute(node: Node): node is MutatedAttribute { + return is_Attribute(node) && /^\s*rustfmt::skip\s*$/.test(node.value); +} +function getCommentTestFunction(flags: CF, fn?: (comment: AnyComment) => boolean) { + return function (comment: AnyComment, index: number, comments: AnyComment[]) { + __DEV__: Narrow(flags), assert(handled(comment)); + return !( + (flags & CF.Leading && !isLeading(comment)) || + (flags & CF.Trailing && !isTrailing(comment)) || + (flags & CF.Dangling && !isDangling(comment)) || + (flags & CF.Block && !is_BlockCommentKind(comment)) || + (flags & CF.Line && !is_LineCommentKind(comment)) || + (flags & CF.First && index !== 0) || + (flags & CF.Last && !iLast(index, comments)) || + (flags & CF.PrettierIgnore && !(isPrettierIgnoreComment(comment) || isPrettierIgnoreAttribute(comment))) || + (fn && !fn(comment)) + ); + }; +} + +export function hasComment(node: Node, flags: number = 0, fn?: (comment: AnyComment) => boolean) { + if ("comments" in node && node.comments!.length > 0) { + return flags || fn ? (node.comments as AnyComment[]).some(getCommentTestFunction(flags, fn)) : true; + } + return false; +} +export function hasNewlineInRange(leftIndex: number, rightIndex: number) { + __DEV__: assert(leftIndex <= rightIndex); + const text = getContext().options.originalText; + for (var i = leftIndex; i < rightIndex; ++i) if (text.charCodeAt(i) === 10) return true; + return false; +} +export function isNextLineEmpty(node: Node) { + return isNextLineEmptyAfterIndex(end(node)); +} +export function isNextLineEmptyAfterIndex(index: number | false) { + let oldIdx: number | false = -1; + let idx: number | false = index; + while (idx !== oldIdx) { + oldIdx = idx; + idx = skipToLineEnd(idx); + idx = skipBlockComment(idx); + idx = skipSpaces(idx); + idx = skipParens(idx); + } + idx = skipLineComment(idx); + idx = skipParens(idx); + idx = skipNewline(idx); + idx = skipParens(idx); + return idx !== false && hasNewline(idx); +} +export function hasNewline(index: number | false, backwards = false) { + if (index === false) return false; + const i = skipSpaces(index, backwards); + return i !== false && i !== skipNewline(i, backwards); +} +function skipLineComment(index: number | false) { + if (index === false) return false; + const { commentSpans, originalText } = getContext().options; + if (commentSpans.has(index) && originalText.charCodeAt(index + 1) === 47 /** "/" */) + return skipEverythingButNewLine(commentSpans.get(index)!); + return index; +} +function skipBlockComment(index: number | false) { + if (index === false) return false; + const { commentSpans, originalText } = getContext().options; + if (commentSpans.has(index) && originalText.charCodeAt(index + 1) === 42 /** "*" */) return commentSpans.get(index)!; + return index; +} +const [skipSpaces, skipToLineEnd, skipEverythingButNewLine] = [/[ \t]/, /[,; \t]/, /[^\r\n]/].map(function (re) { + return function (index: number | false, backwards = false) { + if (index === false) return false; + const { originalText: text } = getContext().options; + let cursor = index; + while (cursor >= 0 && cursor < text.length) { + if (re.test(text.charAt(cursor))) backwards ? cursor-- : cursor++; + else return cursor; + } + return cursor === -1 || cursor === text.length ? cursor : false; + }; +}); + +function skipNewline(index: number | false, backwards = false) { + if (index === false) return false; + const { originalText } = getContext().options; + const atIndex = originalText.charCodeAt(index); + if (backwards) { + if (originalText.charCodeAt(index - 1) === 13 && atIndex === 10) return index - 2; + if (atIndex === 10) return index - 1; + } else { + if (atIndex === 13 && originalText.charCodeAt(index + 1) === 10) return index + 2; + if (atIndex === 10) return index + 1; + } + return index; +} + +function skipParens(index: number | false, backwards = false) { + return index; + // if (index === false) return false; + // const { parensPositions } = getContext().options; + // while (parensPositions.has(index)) backwards ? index-- : index++; + // return index; +} + +export function getNextNonSpaceNonCommentCharacterIndex(node: Node) { + return getNextNonSpaceNonCommentCharacterIndexWithStartIndex(end(node)); +} +function getNextNonSpaceNonCommentCharacterIndexWithStartIndex(i: number) { + let oldIdx = -1; + let nextIdx = i; + while (nextIdx !== oldIdx) { + oldIdx = nextIdx; + nextIdx = skipSpaces(nextIdx) as number; + nextIdx = skipBlockComment(nextIdx) as number; + nextIdx = skipLineComment(nextIdx) as number; + nextIdx = skipNewline(nextIdx) as number; + nextIdx = skipParens(nextIdx) as number; + } + return nextIdx; +} +export function getNextNonSpaceNonCommentCharacter(node: Node) { + return getContext().options.originalText.charAt(getNextNonSpaceNonCommentCharacterIndex(node)); +} + +interface CommentContext { + comment: AnyComment; + precedingNode: Node | undefined; + enclosingNode: Node | undefined; + followingNode: Node | undefined; + text: string; + options: CustomOptions; + ast: Node; + isLastComment: boolean; +} + +function handled(comment: AnyComment) { + return "printed" in comment; +} +function handleCommon(ctx: CommentContext): boolean { + { + const { comment, precedingNode, enclosingNode, followingNode } = ctx; + if (!enclosingNode) { + ctx.enclosingNode = ctx.comment.loc.src.program; + } else if (enclosingNode && is_NodeWithBodyOrCases(enclosingNode)) { + const body = getBodyOrCases(enclosingNode); + if (body) { + if (is_ExpressionWithBodyOrCases(enclosingNode) && enclosingNode.label) { + if (ctx.precedingNode === enclosingNode.label) { + ctx.precedingNode = undefined; + } + if (followingNode === enclosingNode.label) { + ctx.followingNode = undefined; + } + } + if (comment.loc.isBefore(body)) { + if (followingNode && body.loc.contains(followingNode)) { + ctx.followingNode = undefined; + } + if (!ctx.precedingNode && !ctx.followingNode) { + addLeadingComment(enclosingNode, comment); + return true; + } + } else if (comment.loc.isAfter(body)) { + if (precedingNode && body.loc.contains(precedingNode)) { + ctx.precedingNode = undefined; + } + if (!ctx.precedingNode && !ctx.followingNode) { + addTrailingComment(enclosingNode, comment); + return true; + } + } else if (body.loc.contains(comment)) { + if (precedingNode && !body.loc.contains(precedingNode)) { + ctx.precedingNode = undefined; + } + if (followingNode && !body.loc.contains(followingNode)) { + ctx.followingNode = undefined; + } + } + } + } + } + for (const fn of [ + handleMixedInOuterAttributeComments, + handleAttributeComments, + handleDanglingComments, + handleFunctionComments, + handleMacroRuleComments, + handleStructLiteralComments, + handleVariableDeclaratorComments, + handleIfBlockExpressionComments, + handleMemberExpressionComments, + handleStatementComments, + handleFlowControlComments, + handleBadComments, + ]) { + fn(ctx); + if (handled(ctx.comment)) { + // console.log(ctx.comment.loc.url(), fn.name); + return true; + } + } + + const { precedingNode, followingNode, comment } = ctx; + + if (isStartOfLine(comment)) { + if (followingNode) { + addLeadingComment(followingNode, comment); + } else if (precedingNode) { + addTrailingComment(precedingNode, comment); + } else { + exit.never(ctx); + } + } else if (isEndOfLine(comment)) { + if (precedingNode) { + addTrailingComment(precedingNode, comment); + } else if (followingNode) { + addLeadingComment(followingNode, comment); + } else { + exit.never(ctx); + } + } else { + if (precedingNode && followingNode) { + return false; + } else if (precedingNode) { + addTrailingComment(precedingNode, comment); + } else if (followingNode) { + addLeadingComment(followingNode, comment); + } else { + exit.never(ctx); + } + } + return handled(ctx.comment); +} +export function handleOwnLineComment(ctx: CommentContext) { + return handleCommon(ctx); +} +export function handleEndOfLineComment(ctx: CommentContext) { + const { precedingNode, enclosingNode, comment } = ctx; + if ( + // handleCallExpressionComments + precedingNode && + enclosingNode && + is_CallExpression_or_CallLikeMacroInvocation(enclosingNode) && + enclosingNode.arguments.length > 0 && + precedingNode === (enclosingNode.typeArguments ? last_of(enclosingNode.typeArguments) : enclosingNode.callee) + ) { + addLeadingComment(enclosingNode.arguments[0], comment); + return true; + } else if ( + // handlePropertyComments + enclosingNode && + is_StructLiteralProperty(enclosingNode) + ) { + addLeadingComment(enclosingNode, comment); + return true; + } else { + return handleCommon(ctx); + } +} + +export function handleRemainingComment(ctx: CommentContext) { + return handleCommon(ctx); +} + +function handleStructLiteralComments({ enclosingNode, followingNode, comment }: CommentContext) { + if (enclosingNode && is_StructLiteralPropertySpread(enclosingNode) && followingNode === enclosingNode.expression) { + addLeadingComment(enclosingNode, comment); + } +} + +function handleVariableDeclaratorComments({ enclosingNode, followingNode, comment }: CommentContext) { + if ( + enclosingNode && + (is_xVariableEqualishLike(enclosingNode) || is_ReassignmentNode(enclosingNode)) && + followingNode && + (is_BlockCommentKind(comment) || + nisAnyOf(followingNode, [ + NodeType.StructLiteral, + NodeType.StructPattern, + NodeType.TupleLiteral, + NodeType.TypeTuple, + NodeType.TuplePattern, + NodeType.ArrayLiteral, + NodeType.ArrayPattern, + NodeType.SizedArrayLiteral, + NodeType.TypeSizedArray, + ])) + ) { + addLeadingComment(followingNode, comment); + } +} + +function handleMixedInOuterAttributeComments({ precedingNode, enclosingNode, followingNode, comment }: CommentContext) { + if (enclosingNode && hasOuterAttributes(enclosingNode) && end(comment) <= ownStart(enclosingNode)) { + if (isPrettierIgnoreComment(comment) || isPrettierIgnoreAttribute(comment)) { + setPrettierIgnoreTarget(enclosingNode, comment); + } + if (isEndOfLine(comment)) { + __DEV__: assert(!!precedingNode && is_Attribute(precedingNode), "", precedingNode); + if (shouldPrintOuterAttributesAbove(enclosingNode)) { + // #[attr] // comment + // node + addTrailingComment(precedingNode, comment); + } else { + // #[attr] /* comment */ node + addLeadingComment(followingNode || enclosingNode, comment); + } + } else { + // __DEV__: assert(isStartOfLine(comment)); + if (followingNode && end(followingNode) <= ownStart(enclosingNode)) { + addLeadingComment(followingNode, comment); + } else if (precedingNode && enclosingNode.loc.contains(precedingNode)) { + addTrailingComment(precedingNode, comment); + } else { + addLeadingComment(enclosingNode, comment); + } + } + } +} +function handleAttributeComments({ precedingNode, enclosingNode, followingNode, comment, ast }: CommentContext) { + if (is_AttributeOrDocComment(comment)) { + if ( + comment.inner && + enclosingNode && + is_FunctionDeclaration(enclosingNode) && + (!followingNode || !is_StatementNode(followingNode)) && + (!precedingNode || !is_StatementNode(precedingNode)) + ) { + if (enclosingNode.body) { + if (canAttachCommentInLocArray(enclosingNode.body)) { + addDanglingComment(enclosingNode, comment, DCM["body"]); + } else { + addLeadingComment(enclosingNode.body[0], comment); + } + } else { + addLeadingComment(enclosingNode, comment); + } + } else { + // if (comment.loc.url().startsWith("tests/samples/macro/attr.rs") && getContext().options.danglingAttributes.includes(comment)) { + // // debugger; + // console.log({ + // comment: comment.loc.url(), + // precedingNode: precedingNode?.loc.url(), + // enclosingNode: enclosingNode?.loc.url(), + // followingNode: followingNode?.loc.url(), + // }); + // } + if (followingNode) { + addLeadingComment(followingNode, comment); + } else if (enclosingNode) { + for (var key in DCM) + if (key in enclosingNode) { + addDanglingComment(enclosingNode, comment, key as DCM); + return; + } + } else { + addDanglingComment(ast, comment, DCM["body"]); + } + } + } +} + +function handleBadComments({ precedingNode, enclosingNode, followingNode, ast, comment }: CommentContext) { + if (!enclosingNode) { + // console.log(comment.loc.url()); + if (followingNode) { + addLeadingComment(followingNode, comment); + } else if (precedingNode) { + addTrailingComment(precedingNode, comment); + } else { + addDanglingComment(enclosingNode || ast, comment, DCM["body"]); + } + } else if (!precedingNode && !followingNode) { + if (enclosingNode && enclosingNode !== ast) { + addLeadingComment(enclosingNode, comment); + } else { + addDanglingComment(ast, comment, DCM["body"]); + } + } +} +function is_ABI_Comment({ precedingNode, enclosingNode, comment }: CommentContext) { + return ( + is_CommentOrDocComment(comment) && + ((precedingNode && is_ExternSpecifier(precedingNode)) || (enclosingNode && is_ExternSpecifier(enclosingNode))) + ); +} +function handleFlowControlComments({ precedingNode, enclosingNode, followingNode, comment }: CommentContext) { + if (enclosingNode && is_FlowControlExpression(enclosingNode)) { + if (!precedingNode && (isOwnLine(comment) || isEndOfLine(comment)) && !followingNode) { + addLeadingComment(enclosingNode, comment); + } + } +} +function handleFunctionComments(ctx: CommentContext) { + const { precedingNode, enclosingNode, followingNode, comment } = ctx; + if (enclosingNode && is_FunctionNode(enclosingNode)) { + if ( + is_FunctionDeclaration(enclosingNode) && + ((!is_ABI_Comment(ctx) && comment.loc.isBefore(enclosingNode.generics || enclosingNode.id)) || + (enclosingNode.generics && comment.loc.isBetween(enclosingNode.generics, enclosingNode.parameters))) + ) { + addLeadingComment(enclosingNode, comment); + } else if ( + !enclosingNode.returnType && + comment.loc.isBetween( + enclosingNode.parameters, + is_FunctionDeclaration(enclosingNode) ? enclosingNode.body! : enclosingNode.expression + ) + ) { + if (is_FunctionDeclaration(enclosingNode)) { + addCommentToBlock(enclosingNode, comment); + } else { + addLeadingComment(enclosingNode.expression, comment); + } + } else if ( + precedingNode && // + enclosingNode.parameters.loc.contains(comment) + ) { + if (precedingNode === getLastParameter(enclosingNode)) { + addTrailingComment(precedingNode, comment); + } + } else if ( + followingNode && + isStartOfLine(comment) && + comment.loc.isAfter(enclosingNode.parameters) && + (!is_FunctionDeclaration(enclosingNode) || !enclosingNode.whereBounds || comment.loc.isAfter(enclosingNode.whereBounds!)) && + (!enclosingNode.returnType || comment.loc.isAfter(enclosingNode.returnType)) && + followingNode === (is_FunctionDeclaration(enclosingNode) ? enclosingNode.body?.[0] : enclosingNode.expression) + ) { + addLeadingComment(followingNode, comment); + } + } +} +function handleMacroRuleComments(ctx: CommentContext) { + const { precedingNode, enclosingNode, followingNode, comment } = ctx; + if (enclosingNode && is_MacroRule(enclosingNode)) { + if (enclosingNode.transform.loc.contains(comment)) { + __DEV__: assert(enclosingNode.transform.length > 0); + if (!precedingNode || !enclosingNode.transform.loc.contains(precedingNode)) { + __DEV__: assert(!!followingNode && enclosingNode.transform.loc.contains(followingNode)); + addLeadingComment(followingNode, comment); + } + } else if (enclosingNode.match.loc.contains(comment)) { + __DEV__: assert(enclosingNode.match.length > 0); + if (!followingNode || !enclosingNode.match.loc.contains(followingNode)) { + __DEV__: assert(!!precedingNode && enclosingNode.match.loc.contains(precedingNode)); + addTrailingComment(precedingNode!, comment); + } + } + } +} + +function handleStatementComments(ctx: CommentContext) { + const { precedingNode, comment } = ctx; + if (isEndOfLine(comment) && precedingNode && (is_StatementNode(precedingNode) || precedingNode.loc.sliceText().endsWith(";"))) { + addTrailingComment(precedingNode, comment); + } +} + +function addCommentToBlock(block: NodeWithBodyOrCases, comment: AnyComment) { + const body = getBodyOrCases(block); + __DEV__: assert(!!body); + if (body.length > 0) { + addLeadingComment(body![0], comment); + } else { + addDanglingComment(block, comment, DCM["body"]); + } +} + +function handleIfBlockExpressionComments(ctx: CommentContext) { + const { comment, enclosingNode } = ctx; + if (enclosingNode && is_IfBlockExpression(enclosingNode)) { + const { condition, body, else: else_ } = enclosingNode; + if (comment.loc.isBefore(condition)) { + addLeadingComment(condition, comment); + } else if (comment.loc.isBetween(condition, body)) { + addTrailingComment(condition, comment); + } else if (else_ && comment.loc.isBetween(body, else_)) { + if (is_IfBlockExpression(else_)) { + addLeadingComment(else_.condition, comment); + } else { + addCommentToBlock(else_, comment); + } + } + } +} + +function handleMemberExpressionComments({ comment, precedingNode, enclosingNode }: CommentContext) { + if (enclosingNode && is_MemberAccessLike(enclosingNode)) { + if (isStartOfLine(comment) || !precedingNode) addLeadingComment(enclosingNode, comment); + else addTrailingComment(precedingNode, comment); + return true; + } + + return false; +} + +function handleDanglingComments({ comment, enclosingNode }: CommentContext) { + if (enclosingNode) { + for (var key in DCM) { + if (key in enclosingNode) { + var arr: LocArray = enclosingNode[key]; + if (is_LocArray(arr) && canAttachCommentInLocArray(arr) && arr.loc.contains(comment)) { + addDanglingComment(enclosingNode, comment, key as DCM); + return; + } + } + } + } +} + +function canAttachCommentInLocArray(arr: LocArray) { + return arr.length === 0 || arr.every((node) => !canAttachComment(node)); +} + +function isOwnLine(comment: AnyComment) { + return isStartOfLine(comment) && hasBreaklineAfter(comment); +} +function isStartOfLine(comment: AnyComment) { + return comment.placement === "ownLine"; +} +function isEndOfLine(comment: AnyComment) { + return comment.placement === "endOfLine"; +} +export function isDangling(comment: AnyComment) { + __DEV__: assert(handled(comment)); + return !comment.leading && !comment.trailing; +} +export function isLeading(comment: AnyComment) { + __DEV__: assert(handled(comment)); + return comment.leading && !comment.trailing; +} +export function isTrailing(comment: AnyComment) { + __DEV__: assert(handled(comment)); + return !comment.leading && comment.trailing; +} + +export function print_comment(comment: CommentOrDocComment) { + __DEV__: Narrow(comment); + + const doc = is_BlockCommentNode(comment) + ? isIndentableBlockComment(comment.value) + ? [ + (!handled(comment) || isTrailing(comment)) && !hasBreaklineBefore(comment) ? hardline : "", + getCommentStart(comment), + ...comment.value.split(/\n/g).map((line, i, a) => + i === 0 // + ? [line.trimEnd(), hardline] + : !iLast(i, a) + ? [" " + line.trim(), hardline] + : " " + line.trimStart() + ), + "*/", + ] + : [ + getCommentStart(comment), // + join(literalline, comment.value.split(/\n/g)), + "*/", + ] + : [getCommentStart(comment), comment.value.trimEnd()]; + + return handled(comment) && isDangling(comment) // + ? [doc, getPostLeadingComment(comment)] + : doc; + + function getCommentStart(comment: CommentOrDocComment) { + return is_Comment(comment) + ? is_BlockCommentKind(comment) + ? "/*" + : "//" + : is_BlockCommentKind(comment) + ? isInner(comment) + ? "/*!" + : "/**" + : isInner(comment) + ? "//!" + : "///"; + } + function isIndentableBlockComment(value: string) { + const lines = `*${value}*`.split(/\n/g); + return lines.length > 1 && lines.every((line) => /^\s*\*/.test(line)); + } +} diff --git a/frontend/src/utils/prettier/plugins/rust/format/complexity.ts b/frontend/src/utils/prettier/plugins/rust/format/complexity.ts new file mode 100644 index 0000000..a5922e8 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/format/complexity.ts @@ -0,0 +1,282 @@ +import { + ForLtParametersBody, + FunctionSpread, + GenericParameterDeclaration, + MaybeGenericArgsTarget, + MissingNode, + Node, + NodeType, + TypeBound, + TypeBoundsConstaint, + TypeCallArgument, + TypeNamespaceTargetNoSelector, + TypeNode, +} from "jinx-rust"; +import { + getAstPath, + getOwnChildAstPath, + is_BareTypeTraitBound, + is_FunctionSpread, + is_LetScrutinee, + is_Literal, + is_MissingNode, + is_TypeBoundsStandaloneNode, + is_TypeFunctionNode, + is_TypeNode, + is_VariableDeclarationNode, +} from "jinx-rust/utils"; +import { exit, has_key_defined, last_of, spliceAll } from "../utils/common"; +import { canBreak } from "./external"; +import { getContext, getNode, getOptions, getPrintFn } from "./plugin"; + +let DEPTH = 0; +const ANCESTRY: Node[] = []; +const LONE_SHORT_ARGUMENT_THRESHOLD_RATE = 0.25; + +export function withCheckContext(fn: () => R): R { + if (0 === DEPTH) { + return fn(); + } else { + DEPTH = 0; + const prev = spliceAll(ANCESTRY); + try { + return fn(); + } finally { + DEPTH = ANCESTRY.push(...prev); + } + } +} + +export function is_short(str: string) { + return str.length <= LONE_SHORT_ARGUMENT_THRESHOLD_RATE * getOptions().printWidth; +} +function print(target: Node) { + const current = getNode(); + const keys: (string | number)[] = [...getAstPath(ANCESTRY[0], getNode())]; + for (let i = 1; i < ANCESTRY.length; i++) keys.push(...getOwnChildAstPath(ANCESTRY[i - 1], ANCESTRY[i])); + keys.push(...getOwnChildAstPath(last_of(ANCESTRY), target)); + try { + return getContext().path.call(() => getPrintFn(target)(), ...keys); + } catch (e) { + console.log({ current, target, keys, ANCESTRY }); + throw e; + } +} + +function IsSimpleFunction(fn: (node: T) => boolean): (node: T) => boolean { + return function (node: T) { + if (0 !== DEPTH && node === ANCESTRY[DEPTH - 1]) { + return fn(node); + } + + if (DEPTH >= 2) { + return isShortBasic(node); + } + + try { + return fn((ANCESTRY[DEPTH++] = node) as any); + } finally { + ANCESTRY.length = --DEPTH; + } + } as any; +} + +function HasComplexFunction(fn: (node: T) => boolean): (node: T) => boolean { + return function (node: T) { + if (0 !== DEPTH && node === ANCESTRY[DEPTH - 1]) { + return fn(node); + } + + if (DEPTH >= 2) { + return !isShortBasic(node); + } + + try { + return fn((ANCESTRY[DEPTH++] = node) as any); + } finally { + ANCESTRY.length = --DEPTH; + } + } as any; +} + +const isShortBasic = (node: Node) => { + switch (node.nodeType) { + case NodeType.MissingNode: + return true; + case NodeType.Identifier: + case NodeType.Index: + case NodeType.LtIdentifier: + case NodeType.LbIdentifier: + case NodeType.McIdentifier: + return is_short(node.name); + case NodeType.Literal: + return is_short(node.value) && !/\n/.test(node.value); + } + return false; +}; + +export const isSimpleType = IsSimpleFunction((node): boolean => { + switch (node.nodeType) { + case NodeType.MissingNode: + case NodeType.FunctionSpread: + return true; + case NodeType.MacroInvocation: + return false; + case NodeType.Identifier: + case NodeType.TypeNever: + case NodeType.TypeInferred: + return true; + case NodeType.TypePath: + return isShortBasic(node.segment) && (!node.namespace || isSimpleType(node.namespace)); + case NodeType.TypeCall: + return isSimpleType(node.typeCallee) && !hasComplexTypeArguments(node); + case NodeType.ExpressionTypeSelector: + return isSimpleType(node.typeTarget) && (!node.typeExpression || isSimpleType(node.typeExpression)); + case NodeType.TypeDynBounds: + return !hasComplexTypeBounds(node); + case NodeType.TypeImplBounds: + return !hasComplexTypeBounds(node); + case NodeType.TypeFnPointer: { + const param = node.parameters[0]; + return ( + (!node.extern || !node.extern.abi || isShortBasic(node.extern.abi)) && + !hasComplexLtParameters(node) && + (node.parameters.length === 0 || + (node.parameters.length === 1 && + (is_FunctionSpread(param) || + (!is_TypeFunctionNode(param.typeAnnotation) && isSimpleType(param.typeAnnotation))))) && + (!node.returnType || isSimpleType(node.returnType)) + ); + } + case NodeType.TypeFunction: + return isSimpleType(node.callee) && node.parameters.every(isSimpleType) && (!node.returnType || isSimpleType(node.returnType)); + case NodeType.TypeSizedArray: + return isSimpleType(node.typeExpression) && isShortBasic(node.sizeExpression); + case NodeType.TypeSlice: + return isSimpleType(node.typeExpression); + case NodeType.TypeTuple: + return node.items.length === 0 || (node.items.length === 1 && isSimpleType(node.items[0])); + case NodeType.TypeReference: + case NodeType.TypeDereferenceMut: + case NodeType.TypeDereferenceConst: + case NodeType.TypeParenthesized: + return isSimpleType(node.typeExpression); + default: + __DEV__: exit.never(node); + return false; + } +}); + +export const hasComplexTypeBounds = HasComplexFunction>((node) => { + return !!node.typeBounds && node.typeBounds.length > 1 && !node.typeBounds.every(isSimpleTypeBound); +}); + +export const isSimpleTypeBound = (node: TypeBound): boolean => { + switch (node.nodeType) { + case NodeType.TypeParenthesized: + return isSimpleTypeBound(node.typeExpression); + // #Lifetime + case NodeType.LtIdentifier: + case NodeType.LtElided: + case NodeType.LtStatic: + return true; + case NodeType.TypeTraitBound: + return is_BareTypeTraitBound(node) && isSimpleTypeNamespaceTargetNoSelector(node.typeExpression); + default: + __DEV__: exit.never(node); + return false; + } + function isSimpleTypeNamespaceTargetNoSelector(node: TypeNamespaceTargetNoSelector): boolean { + switch (node.nodeType) { + case NodeType.Identifier: + return true; + case NodeType.TypePath: + return undefined === node.namespace || isSimpleTypeNamespaceTargetNoSelector(node.namespace); + case NodeType.TypeCall: + return false; + case NodeType.TypeFunction: + return isSimpleTypeNamespaceTargetNoSelector(node.callee) && node.parameters.length === 0 && !node.returnType; + default: + __DEV__: exit.never(node); + return false; + } + } +}; + +const isSimpleTypeArgument = IsSimpleFunction((node) => { + if (is_TypeNode(node)) { + return isSimpleType(node); + } + switch (node.nodeType) { + // #Lifetime + case NodeType.LtIdentifier: + case NodeType.LtElided: + case NodeType.LtStatic: + case NodeType.Literal: + return true; + case NodeType.MinusExpression: + return is_Literal(node.expression); + case NodeType.BlockExpression: + return false; //willBreak(getPrintFn(node)("body")); + case NodeType.TypeCallNamedArgument: + return isSimpleType(node.typeExpression); + case NodeType.TypeCallNamedBound: + return isSimpleType(node.typeTarget) && !hasComplexTypeBounds(node); + default: + __DEV__: exit.never(node); + return false; + } +}); + +export const hasComplexTypeArguments = HasComplexFunction>((node) => + !node.typeArguments || node.typeArguments.length === 0 + ? false + : node.typeArguments.length === 1 + ? (() => { + const arg = node.typeArguments[0]; + return is_TypeBoundsStandaloneNode(arg) || canBreak(print(arg)); + })() + : true +); + +export const hasComplexLtParameters = HasComplexFunction>((node) => { + const ltParameters = node.ltParameters; + if (!ltParameters || ltParameters.length === 0) { + return false; + } + if (ltParameters.length === 1) { + const arg = ltParameters[0]; + if (arg.ltBounds && arg.ltBounds.length > 1) { + return true; + } + + return false; + } + return true; +}); + +export const isShortGenericParameterDeclaration = IsSimpleFunction((node) => { + switch (node.nodeType) { + case NodeType.GenericTypeParameterDeclaration: + return !node.typeBounds && !node.typeDefault; + case NodeType.ConstTypeParameterDeclaration: + return (!node.typeAnnotation || is_MissingNode(node)) && !node.typeDefault; + case NodeType.GenericLtParameterDeclaration: + return !node.ltBounds; + default: + exit.never(); + } +}); + +export const hasComplexGenerics = HasComplexFunction((node) => { + return has_key_defined(node, "generics") && node.generics.length > 0 && !node.generics.every(isShortGenericParameterDeclaration); +}); + +export const hasComplexTypeAnnotation = HasComplexFunction((node) => { + if (is_VariableDeclarationNode(node) && !is_LetScrutinee(node)) { + const { typeAnnotation } = node; + return !!typeAnnotation && !is_MissingNode(typeAnnotation) && !isSimpleType(typeAnnotation); + } else { + return false; + } +}); diff --git a/frontend/src/utils/prettier/plugins/rust/format/core.ts b/frontend/src/utils/prettier/plugins/rust/format/core.ts new file mode 100644 index 0000000..40d6f95 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/format/core.ts @@ -0,0 +1,2349 @@ +import { + AndExpression, + AttributeOrDocComment, + BreakExpression, + CallExpression, + ClosureFunctionExpression, + ComparisonExpression, + DeclarationNode, + DelimKind, + EnumDeclaration, + ExpressionBody, + ExpressionNode, + ExpressionPath, + ExpressionWithBody, + ForLtParametersBody, + FunctionDeclaration, + FunctionLike, + FunctionNode, + IfBlockExpression, + ImplDeclaration, + LeftRightExpression, + LocArray, + MacroDeclaration, + MacroGroup, + MacroInlineRuleDeclaration, + MacroMatchSegment, + MacroRuleDeclaration, + MacroRulesDeclaration, + MatchExpression, + MatchExpressionCase, + MaybeBlockBody, + MaybeGenericArgsTarget, + MaybeHasLtBounds, + MaybeReturnTypeConstraint, + MaybeTypeAnnotationTarget, + MemberExpression, + NegativeImplDeclaration, + Node, + NodeType, + NodeWithBodyOrCases, + NodeWithCondition, + ObjectNode, + OperationExpression, + OrExpression, + PathNode, + PatternBody, + PatternNode, + PunctuationToken, + RangeNode, + RestPattern, + ReturnExpression, + StructDeclaration, + StructLiteral, + StructLiteralPropertySpread, + StructLiteralRestUnassigned, + TK, + TraitAliasDeclaration, + TraitDeclaration, + TupleStructDeclaration, + TypeAliasDeclaration, + TypeBoundsConstaint, + TypeFunctionNode, + UnaryExpression, + UnionDeclaration, + UnionPattern, + WhileBlockExpression, + YieldExpression, +} from "jinx-rust"; +import { + DelimChars, + end, + getBodyOrCases, + getDelimChars, + getParameters, + hasAttributes, + hasCondition, + hasItems, + hasLetScrutineeCondition, + hasParameters, + hasProperties, + hasSelfParameter, + hasSemiNoBody, + hasSemiNoProperties, + hasTypeBounds, + is_ArrayOrTupleLiteral, + is_Attribute, + is_ClosureBlock, + is_ClosureFunctionExpression, + is_DelimGroup, + is_ElseBlock, + is_EnumMemberStructDeclaration, + is_ExpressionAsTypeCast, + is_ExpressionNode, + is_ExpressionPath, + is_ExpressionStatement, + is_ExpressionTypeCast, + is_ExpressionWithBodyOrCases, + is_FlowControlExpression, + is_FunctionDeclaration, + is_FunctionParameterDeclaration, + is_GenericParameterDeclaration, + is_Identifier, + is_IdentifierOrIndex, + is_IfBlockExpression, + is_ImplDeclarationNode, + is_LetScrutinee, + is_LineCommentNode, + is_Literal, + is_LiteralBooleanLike, + is_LiteralNumberLike, + is_LiteralStringLike, + is_LogicalExpression, + is_MacroGroup, + is_MacroInlineRuleDeclaration, + is_MacroInvocation, + is_MacroParameterDeclaration, + is_MatchExpression, + is_MatchExpressionCase, + is_MemberExpression, + is_MinusExpression, + is_MissingNode, + is_NodeWithBodyOrCases, + is_OrExpression, + is_PostfixExpression, + is_Program, + is_PunctuationToken, + is_RangeLiteral, + is_RangePattern, + is_ReassignmentExpression, + is_ReassignmentNode, + is_RestPattern, + is_SourceFile, + is_StructDeclaration, + is_StructLiteral, + is_StructLiteralProperty, + is_StructLiteralPropertySpread, + is_StructPattern, + is_StructPatternPropertyDestructured, + is_StructProperty, + is_TupleLiteral, + is_TupleNode, + is_TuplePattern, + is_TupleStructDeclaration, + is_TypeCallNamedArgument, + is_TypeTuple, + is_UnaryExpression, + is_UnionDeclaration, + is_UnwrapExpression, + is_VariableDeclarationNode, + ownStart, + start, +} from "jinx-rust/utils"; +import { BlockLikeMacroInvocation, CallLikeMacroInvocation, is_CallExpression_or_CallLikeMacroInvocation } from "../transform"; +import { AssertTypesEq, Identity, Map_get, Narrow, assert, exit, find_last, flat, iLast, last_of, spread } from "../utils/common"; +import { + CF, + getFirstComment, + getNextNonSpaceNonCommentCharacterIndex, + hasBreaklineBefore, + hasComment, + hasNewline, + hasNewlineInRange, + isNextLineEmpty, + isNextLineEmptyAfterIndex, + isPreviousLineEmpty, + printCommentsSeparately, + printDanglingComments, + withComments, +} from "./comments"; +import { + hasComplexGenerics, + hasComplexLtParameters, + hasComplexTypeAnnotation, + hasComplexTypeArguments, + hasComplexTypeBounds, + isShortGenericParameterDeclaration, + is_short, +} from "./complexity"; +import { + AstPath, + DCM, + Doc, + align, + breakParent, + canBreak, + cleanDoc, + conditionalGroup, + dedentToRoot, + fill, + getDocParts, + group, + hardline, + ifBreak, + indent, + indentIfBreak, + isConcat, + join, + label, + line, + lineSuffixBoundary, + removeLines, + softline, + willBreak, +} from "./external"; +import { + f, + getContext, + getGrandParentNode, + getNode, + getOptions, + getParentNode, + getPrintFn, + is_printing_macro, + pathCall, + pathCallEach, + print, +} from "./plugin"; +import { canInlineBlockBody, emptyContent, needsParens, shouldFlatten } from "./styling"; + +export function isNoopExpressionStatement(node: Node) { + return is_ExpressionStatement(node) && undefined === node.expression && !hasAttributes(node) && !hasComment(node); +} + +export function getLastNotNoopExpressionStatement(parent: MaybeBlockBody) { + return parent.body && find_last(parent.body, (stmt) => !isNoopExpressionStatement(stmt)); +} + +export function is_xVariableEqualishLike(node: Node) { + switch (node.nodeType) { + case NodeType.LetScrutinee: + case NodeType.LetVariableDeclaration: + case NodeType.ConstVariableDeclaration: + case NodeType.StaticVariableDeclaration: + case NodeType.TypeAliasDeclaration: + case NodeType.TraitAliasDeclaration: + return true; + default: + return false; + } +} + +export function is_BinaryishExpression(node: Node): node is OrExpression | AndExpression | OperationExpression | ComparisonExpression { + switch (node.nodeType) { + case NodeType.OrExpression: + case NodeType.AndExpression: + case NodeType.OperationExpression: + case NodeType.ComparisonExpression: + return true; + default: + return false; + } +} + +export type StructSpread = StructLiteralPropertySpread | StructLiteralRestUnassigned | RestPattern; +export function is_StructSpread(node: Node): node is StructSpread { + switch (node.nodeType) { + case NodeType.StructLiteralPropertySpread: + case NodeType.StructLiteralRestUnassigned: + case NodeType.RestPattern: + return true; + default: + return false; + } +} + +type ArrayLikeNode = Exclude, UnionPattern>; +function isConciselyPrintedArray(node: ArrayLikeNode) { + return ( + node.items.length > 1 && + (node.items as Node[]).every( + (element) => + (is_LiteralNumberLike(element) || + (is_MinusExpression(element) && is_LiteralNumberLike(element.expression) && !hasComment(element.expression))) && + !hasComment(element, CF.Trailing | CF.Line, (comment) => !hasBreaklineBefore(comment)) + ) + ); +} +export function printCommentsInsideEmptyArray(path: AstPath) { + const node = path.getValue(); + if (hasComment(node, CF.Dangling)) { + return [printDanglingComments(node, false, DCM["items"]), softline]; + } else { + return ""; + } +} + +export function printNumber(rawNumber: string) { + return rawNumber + .toLowerCase() + .replace(/^([\d.]+e)(?:\+|(-))?0*(\d)/, "$1$2$3") + .replace(/^(\d+)e[+-]?0+$/, "$1.0") + .replace(/^([\d.]+)e[+-]?0+$/, "$1") + .replace(/\.(\d+?)0+(?=e|$)/, ".$1") + .replace(/\.(?=e|$)/, ".0"); +} + +export function printOnOwnLine(node: Node, printed: Doc) { + return [printed, maybeEmptyLine(node)]; +} +export function maybeEmptyLine(node: Node) { + return isNextLineEmpty(node) ? [hardline, hardline] : hardline; +} + +export function printBodyOrCases(print: print, node: T) { + // Note: Inner Attributes are inserted into body/cases (see "./transform.ts") + // Example: { #[OUTER] #![INNER] 0 }; body: ["#[OUTER] 0", "#![INNER]"] + const p: { node: Node; doc: Doc }[] = []; + if (is_MatchExpression(node)) { + __DEV__: Narrow>(print); + pathCallEach(node as Extract, "cases", (mCase) => { + p.push({ + node: mCase, + doc: is_MatchExpressionCase(mCase) && !is_ExpressionWithBodyOrCases(mCase.expression) ? [print(), ","] : print(), + }); + }); + } else { + __DEV__: Narrow>(node); + pathCallEach(node as Extract, "body", (stmt) => { + if (!isNoopExpressionStatement(stmt)) { + p.push({ node: stmt, doc: print() }); + } + }); + } + + const printed: Doc[] = bumpInnerAttributes(p).map(({ doc, node }, i, a) => + iLast(i, a) ? group(doc) : printOnOwnLine(node, group(doc)) + ); + + const comments = printDanglingCommentsForInline(node, DCM["body"]); + if (comments) printed.push(comments); + const ccomments = printDanglingCommentsForInline(node, DCM["cases"]); + if (ccomments) printed.push(ccomments); + + if (is_Program(node) && is_SourceFile(getParentNode(node)!) && printed.length > 0 && !comments) { + printed.push(hardline); + } + + return printed; + + function bumpInnerAttributes(arr: { node: Node; doc: Doc }[]) { + return arr.sort((a, b) => ownStart(a.node) - ownStart(b.node)); + } +} + +export function printMacroRules(print: print, node: T) { + return !Array.isArray(node.rules) + ? print("rules") + : node.rules.length > 0 + ? [" {", indent([hardline, ...print.join("rules", (rule) => maybeEmptyLine(rule))]), hardline, "}"] + : [" {", printDanglingCommentsForInline(node, DCM["rules"]) || emptyContent(node), "}"]; +} +function is_unary_token(item: MacroMatchSegment | undefined) { + switch (item && is_PunctuationToken(item) ? item.tk : TK.None) { + case TK["-"]: + case TK["*"]: + case TK["&"]: + case TK["#"]: + case TK["!"]: + case TK["~"]: + return true; + case TK["?"]: + return !/\s/.test(getOptions().originalText.charAt(end(item!))); + default: + return false; + } +} +function can_unary(node: MacroMatchSegment) { + return (!is_PunctuationToken(node) || is_unary_token(node)) && (!is_MacroGroup(node) || is_optional_unary(node)); +} +function is_optional_token(item: MacroMatchSegment | undefined): item is MacroGroup & { segments: [PunctuationToken] } { + return !!item && is_MacroGroup(item) && item.kind === "?" && item.segments.length === 1 && is_PunctuationToken(item.segments[0]); +} +function is_optional_unary(item: MacroMatchSegment | undefined) { + return is_optional_token(item) && is_unary_token(item.segments[0]); +} + +function is_optional_segment(item: Node): item is MacroGroup { + return is_MacroGroup(item) && item.kind === "?"; +} + +export function printRuleMatch(print: print, rule: T) { + // "", ".", "&&", "||", "=", "+", "-", "*", "/", "%", "&", "|", "^", "<<", ">>", "==", "!=", ">", ">=", "<", "<=", "+=", "-=", "*=", "/=", + // "%=", "&=", "|=", "^=", "<<=", ">>=", "$", "@", "_", "..", "...", "..=", ",", ";", ":", "::", "#", "?", "!", "=>", "->", "~" + + return print_map(rule, "match"); + + type ArrProps = { [K in keyof T]: NonNullable extends readonly any[] ? T[K] & unknown[] : never }; + function print_map & keyof typeof DCM>(node: T, property: K) { + __DEV__: assert(property in DCM); + const arr = node[property as any] as LocArray; + const shouldHug = should_hug(arr); + const dline = + arr.dk === DelimKind["{}"] ? line : shouldHug ? "" /* : is_MacroGroup(node) && node.kind !== "?" ? hardline */ : softline; + const isParamsLike = is_params_like(arr); + const shouldBreak = should_break(arr); + const d = getDelimChars(arr); + if (arr.length === 0) return [d.left, printDanglingCommentsForInline(node, DCM[property]), d.right]; + const printed = flat(print.map_join(property as any, print_item, join_item)); + + // const printed = flat(print.map_join(property as any, print_item, join_item)) + // .reduce( + // (arr, doc, i, a) => { + // last_of(arr).push(doc); + // if (doc === line && (a[i - 1] === "," || a[i - 1] === ";")) arr.push([]); + // return arr; + // }, + // [[]] as Doc[][] + // ) + // .map((grp) => group(grp)); + + return group([d.left, !dline ? printed : [indent([dline, printed]), dline], d.right], { + shouldBreak, + id: getMacroGroupId(node), + }); + + function should_hug(arr: LocArray) { + if (node === (rule as any)) return false; + let has_nonToken = false; + return arr.every((item) => !is_MacroGroup(item) && (is_PunctuationToken(item) || has_nonToken !== (has_nonToken = true))); + } + + function should_break(arr: LocArray) { + let has_decl = false; + return arr.some( + (item, i, a) => + (is_match_any(item) && arr.length !== 1) || + (!iLast(i, a) && isDeclStart(item, a[i + 1]) && has_decl === (has_decl = true)) + ); + } + + function print_item(item: MacroMatchSegment, index: number, arr: MacroMatchSegment[]) { + switch (item.nodeType) { + case NodeType.Identifier: + case NodeType.LtIdentifier: + case NodeType.Literal: + case NodeType.PunctuationToken: + case NodeType.MacroParameterDeclaration: + return print(); + case NodeType.MacroGroup: + Narrow>(print); + return printComments(["$", print_map(item, "segments"), print("sep"), item.kind]); + case NodeType.DelimGroup: + return printComments(print_map(item, "segments")); + } + __DEV__: exit.never(); + + function printComments(doc: Doc) { + const printed = withComments(item, doc); + const comment = getFirstComment(item, CF.Leading | CF.Line); + return comment && index !== 0 + ? isPreviousLineEmpty(comment) && + typeof join_item(arr[index - 1], item, index === 1 ? undefined : arr[index - 2]) === "string" + ? [hardline, hardline, printed] + : [hardline, printed] + : printed; + } + } + + function is_params_like(arr: MacroMatchSegment[]) { + return arr.some(function isComma(item) { + switch (item.nodeType) { + case NodeType.PunctuationToken: + return TK[","] === item.tk; + case NodeType.MacroGroup: + return (!!item.sep && isComma(item.sep)) || is_params_like(item.segments); + } + }); + } + + function join_item(item: MacroMatchSegment, next: MacroMatchSegment, prev: MacroMatchSegment | undefined) { + if (is_PunctuationToken(item)) { + switch (item.tk) { + case TK[","]: + case TK[";"]: + return line; + case TK["::"]: + case TK[".."]: + case TK["..."]: + case TK["."]: + case TK["#"]: + return ""; + case TK["!"]: + if (prev && is_ident(prev) && is_DelimGroup(next)) { + return next.segments.dk === DelimKind["{}"] ? " " : ""; + } + break; + case TK["@"]: + return is_ident(next) && (!prev || is_MacroGroup(prev) || is_DelimGroup(prev)) ? "" : " "; + } + + return is_unary_token(item) && // + (!prev || !is_ident(prev)) && + can_unary(next) + ? "" + : " "; + } + + switch (is_PunctuationToken(next) ? next.tk : TK.None) { + case TK[","]: + case TK[";"]: + case TK[":"]: + case TK["::"]: + case TK[".."]: + case TK["..."]: + case TK["."]: + return ""; + case TK["!"]: + if (is_ident(item)) { + return ""; + } + } + + if (is_match_any(item)) { + return line; + } + + { + const sep_tk = is_MacroGroup(item) && item.sep && is_PunctuationToken(item.sep) ? item.sep.tk : TK.None; + switch (sep_tk) { + case TK["::"]: + case TK["."]: + return ""; // $(...)::* | $(...).* + case TK[","]: + case TK[";"]: + return sep_tk === maybe_tk(next) ? ifBreak(line, " ", { groupId: getMacroGroupId(item) }) : line; + } + } + + if (is_optional_token(item)) { + switch (item.segments[0].tk) { + case TK["+"]: + case TK["|"]: + return " "; + case TK["::"]: + return ""; + } + if (is_unary_token(item.segments[0])) { + return ""; + } + } + + if (is_DelimGroup(item) || is_MacroGroup(item)) { + if (item.segments.dk === DelimKind["{}"]) { + return line; + } + + if (is_MacroGroup(item) && item.segments.length === 2) { + const { 0: left, 1: right } = item.segments; + if (is_PunctuationToken(left) && is_DelimGroup(right) && left.tk === TK["#"] && right.segments.dk === DelimKind["[]"]) { + return hardline; + } + } + return isParamsLike || is_tk(next) ? " " : line; + } + + const next_1 = next !== last_of(arr) && arr[arr.indexOf(next) + 1]; + if (is_ident(item) && is_DelimGroup(next) && next.segments.dk === DelimKind["()"]) { + if (!next_1 || !is_match_any(next_1)) { + return ""; + } + } + + if (is_match_any(next) && (!is_DelimGroup(next) || (next_1 && is_match_any(next_1)))) { + return line; + } + + // if (is_ident(item) && !is_ident(next) && !(is_DelimGroup(next) && next.segments.dk === DelimKind["{}"])) { + // const next_1 = arr[arr.indexOf(next) + 1]; + // if (next_1 && typeof join_item(next, next_1, item) === "object") { + // return line; + // } + // } + + return " "; + } + } + + function is_ident(item: MacroMatchSegment) { + switch (item.nodeType) { + case NodeType.Identifier: + return true; + case NodeType.MacroParameterDeclaration: + return item.ty.name === "ident"; + default: + return false; + } + } + function is_tk(item: MacroMatchSegment) { + return is_PunctuationToken(item) || is_optional_token(item); + } + function maybe_tk(item: MacroMatchSegment) { + switch (item.nodeType) { + case NodeType.PunctuationToken: + return item.tk; + case NodeType.MacroGroup: + return is_optional_token(item) ? item.segments[0].tk : TK.None; + default: + return TK.None; + } + } + function isDeclStart(item: MacroMatchSegment, next: MacroMatchSegment) { + if (is_Identifier(item)) { + switch (item.name) { + case "fn": + case "mod": + case "use": + case "struct": + case "trait": + case "union": + case "enum": + case "impl": + case "type": + case "let": + case "static": + case "const": + if (is_ident(next)) { + return true; + } + } + } + return false; + } + function is_match_any(item: MacroMatchSegment) { + return ( + !!item && + ((is_MacroGroup(item) && + !item.sep && + (item.kind === "*" || item.kind === "+") && + item.segments.length === 1 && + is_MacroParameterDeclaration(item.segments[0]) && + item.segments[0].ty.name === "tt") || + (is_DelimGroup(item) && item.segments.length === 1 && is_match_any(item.segments[0]))) + ); + } +} + +export function printRuleTransform( + print: print, + node: T, + t: DelimChars = getDelimChars(node.transform) +) { + const text = node.transform.loc.sliceText(); + const fline = is_MacroInlineRuleDeclaration(node) ? hardline : line; + if (/^. *\n/.test(text)) { + return [ + dedentToRoot([ + t.left, + fline, + text + .slice(1, -1) // + .replace(/^ *\n|\n\s*$/g, ""), + ]), + fline, + t.right, + ]; + } else if (/\n/.test(text) && node.transform.length === 1) { + const segment = node.transform[0]; + if (is_DelimGroup(segment) || is_MacroGroup(segment)) { + const inner = is_DelimGroup(segment) + ? getDelimChars(segment.segments) + : { left: "$(", right: `)${segment.sep?.loc.getOwnText() ?? ""}${segment.kind}` }; + return [ + dedentToRoot([ + t.left, + [ + indent(indent([fline, inner.left])), + line, + segment.segments.loc.sliceText(1, -1).replace(/^ *\n|\n\s*$/g, ""), + indent(indent([line, inner.right])), + ], + ]), + fline, + t.right, + ]; + } + } + return text; + + return node.transform.length > 0 + ? group([t.left, indent([line, print("transform")]), line, t.right]) + : [t.left, printDanglingCommentsForInline(node, DCM["transform"]), t.right]; +} + +function is_AssignmentOrVariableDeclarator(node: Node): boolean { + return is_ReassignmentNode(node) || is_VariableDeclarationNode(node); +} +function hasLeadingOwnLineComment(node: Node): boolean { + if (is_NodeWithBodyOrCases(node) && hasComment(node, CF.Leading, is_Attribute)) { + return true; + } + return hasComment( + node, + CF.Leading, + (comment) => hasNewline(end(comment)) && !getContext().options.danglingAttributes.includes(comment as any) + ); +} +function isComplexDestructuring(node: Node): boolean { + if (is_ReassignmentExpression(node)) { + const leftNode = node.left; + return ( + is_StructLiteral(leftNode) && // + leftNode.properties.length > 2 && + leftNode.properties.some((property) => is_StructLiteralProperty(property) || is_StructLiteralPropertySpread(property)) + ); + } + if (is_VariableDeclarationNode(node) || is_MatchExpressionCase(node) || is_LetScrutinee(node)) { + const leftNode = node.pattern; + return ( + is_StructPattern(leftNode) && // + leftNode.properties.length > 2 && + leftNode.properties.some((property) => is_StructPatternPropertyDestructured(property)) + ); + } + return false; +} + +// export function isShortWhereBoundDeclaration(node: WhereBoundDeclaration) { +// switch (node.nodeType) { +// case NodeType.WhereTypeBoundDeclaration: +// __DEV__: Narrow(node); +// return !node.ltParameters && is_Identifier(node.typeTarget); +// case NodeType.WhereLtBoundDeclaration: +// __DEV__: Narrow(node); +// // return !node.typeAnnotation && !node.typeDefault; +// default: +// exit.never(); +// } +// } + +function isArrowFunctionVariableDeclarator(node: Node) { + return is_VariableDeclarationNode(node) && node.expression && is_ClosureFunctionExpression(node.expression); +} +function isObjectPropertyWithShortKey(node: Node, keyDoc: Doc) { + if (!is_StructProperty(node)) return false; + keyDoc = cleanDoc(keyDoc); + const MIN_OVERLAP_FOR_BREAK = 3; + return typeof keyDoc === "string" && keyDoc.length < getContext().options.tabWidth + MIN_OVERLAP_FOR_BREAK; +} + +function print_CallExpression_end(print: print, node: CallExpression) { + return [f`::${printTypeArguments(print, node)}`, printCallArguments(print, node)]; +} + +export function printCallExpression(print: print, node: CallExpression) { + if (shouldPrint_CallExpression_chain(node) && !pathCall(node, "callee", (node) => needsParens(node))) { + return printMemberChain(print, node); + } + + const contents = [print("callee"), ...print_CallExpression_end(print, node)]; + + if (is_CallExpression_or_CallLikeMacroInvocation(node.callee)) { + return group(contents); + } + + return contents; +} + +export function printTypeAnnotation>>(print: print, node: T) { + return node.typeAnnotation && !is_MissingNode(node.typeAnnotation) ? [": ", print("typeAnnotation")] : ""; +} +export function printAnnotatedPattern & PatternBody>>(print: print, node: T) { + return [print("pattern"), printTypeAnnotation(print, node)]; +} + +function isLoneShortArgument(node: Node) { + if (hasComment(node)) { + return false; + } + + if ((is_Identifier(node) && is_short(node.name)) || (is_LiteralNumberLike(node) && !hasComment(node))) { + return true; + } + + if (is_LiteralStringLike(node)) { + return is_short(node.value) && !node.value.includes("\n"); + } + + return is_LiteralBooleanLike(node); +} + +// prettier-ignore +const toLayout = ["break-after-operator", "never-break-after-operator", "fluid", "break-lhs", "chain", "chain-tail", "chain-tail-arrow-chain", "only-left"]; +const enum Layout { + "break-after-operator", + "never-break-after-operator", + "fluid", + "break-lhs", + "chain", + "chain-tail", + "chain-tail-arrow-chain", + "only-left", +} + +export function printMemberExpression(print: print, node: MemberExpression) { + const objectDoc = print("expression"); + const lookupDoc = printMemberLookup(print, node); + + const shouldInline = shouldInlineMemberExpression(node, objectDoc); + + return label((objectDoc as any).label === "member-chain" ? "member-chain" : "member", [ + objectDoc, + shouldInline ? lookupDoc : group(indent([softline, lookupDoc])), + ]); +} + +function shouldInlineMemberExpression(node: MemberExpression, objectDoc: Doc) { + const { path } = getContext(); + const parent = getParentNode()!; + let i = 0; + let nmparent: Node | null = parent; + while (nmparent && (is_MemberExpression(nmparent) || is_PostfixExpression(nmparent))) { + nmparent = path.getParentNode(i++); + } + + const shouldInline = + (nmparent && (is_ExpressionPath(nmparent) || (is_ReassignmentNode(nmparent) && !is_Identifier(nmparent.left)))) || + !node.computed || + (is_Identifier(node.expression) && is_Identifier(node.property) && !is_MemberExpression(parent)) || + (is_AssignmentOrVariableDeclarator(parent) && + ((is_CallExpression_or_CallLikeMacroInvocation(node.expression) && node.expression.arguments.length > 0) || + (is_PostfixExpression(node.expression) && + is_CallExpression_or_CallLikeMacroInvocation(node.expression.expression) && + node.expression.expression.arguments.length > 0) || + (objectDoc as any).label === "member-chain")); + return shouldInline; +} + +export function printAssignment(leftDoc: Doc, operator: string, rightPropertyName: string) { + const assignmentNode = getNode(); + const rightNode = assignmentNode[rightPropertyName]; + + if (!rightNode) return group(leftDoc); + + const layout = chooseLayout(); + const rightDoc = getPrintFn(assignmentNode)(rightPropertyName as any, { assignmentLayout: layout }); + const res = (function () { + switch (layout) { + case Layout["break-after-operator"]: + return group([group(leftDoc), operator, group(indent([line, rightDoc]))]); + case Layout["never-break-after-operator"]: + return group([group(leftDoc), operator, " ", rightDoc]); + case Layout["fluid"]: { + const groupId = Symbol("assignment"); + return group([ + group(leftDoc), + operator, // + group(indent(line), { id: groupId }), + lineSuffixBoundary, + indentIfBreak(rightDoc, { groupId }), + ]); + } + case Layout["break-lhs"]: + return group([leftDoc, operator, " ", group(rightDoc)]); + case Layout["chain"]: + return [group(leftDoc), operator, line, rightDoc]; + case Layout["chain-tail"]: + return [group(leftDoc), operator, indent([line, rightDoc])]; + case Layout["chain-tail-arrow-chain"]: + return [group(leftDoc), operator, rightDoc]; + default: + exit.never(); + } + })(); + + return label(toLayout[layout], res); + return res; + + function chooseLayout() { + if ( + (is_ReassignmentExpression(assignmentNode) && is_printing_macro()) || + is_GenericParameterDeclaration(assignmentNode) || + is_TypeCallNamedArgument(assignmentNode) + ) { + return Layout["never-break-after-operator"]; + } + + const isTail = !is_ReassignmentNode(rightNode); + const shouldUseChainFormatting = getContext().path.match( + is_ReassignmentNode, + is_AssignmentOrVariableDeclarator, + (node: Node) => !isTail || (!is_ExpressionStatement(node) && !is_VariableDeclarationNode(node)) + ); + + if (shouldUseChainFormatting) { + return !isTail + ? Layout["chain"] + : is_ClosureFunctionExpression(rightNode) && is_ClosureFunctionExpression(rightNode.expression) + ? Layout["chain-tail-arrow-chain"] + : Layout["chain-tail"]; + } + + const isHeadOfLongChain = !isTail && is_ReassignmentNode(rightNode.right); + if (isHeadOfLongChain || hasLeadingOwnLineComment(rightNode)) { + return Layout["break-after-operator"]; + } + + if ( + isComplexDestructuring(assignmentNode) || + hasComplexGenerics(assignmentNode) || + hasComplexTypeAnnotation(assignmentNode) || + (isArrowFunctionVariableDeclarator(assignmentNode) && canBreak(leftDoc)) + ) { + return Layout["break-lhs"]; + } + + const hasShortKey = isObjectPropertyWithShortKey(assignmentNode, leftDoc); + + if (pathCall(assignmentNode, rightPropertyName as never, (rightNode) => shouldBreakAfterOperator(rightNode, hasShortKey))) { + return Layout["break-after-operator"]; + } + + if (hasShortKey || is_Literal(rightNode)) { + return Layout["never-break-after-operator"]; + } + + return Layout["fluid"]; + } + + function shouldBreakAfterOperator(rightNode: Node, hasShortKey: boolean) { + if (is_MemberExpression(rightNode) && shouldInlineMemberExpression(rightNode, getPrintFn(rightNode)("expression"))) { + return false; + } + + if (is_BinaryishExpression(rightNode) && !shouldInlineLogicalExpression(rightNode)) { + return true; + } + + if (is_IfBlockExpression(rightNode)) { + return false; + } + + if (hasShortKey) { + return false; + } + + return (function unwrap(node: Node) { + if (is_UnaryExpression(node) || is_PostfixExpression(node)) { + return pathCall(node, "expression", unwrap); + } + if (is_LiteralStringLike(node)) { + return true; + } + return isPoorlyBreakableMemberOrCallChain(node); + })(rightNode); + + function isPoorlyBreakableMemberOrCallChain(topNode: Node) { + return (function unwrap(node: Node): boolean { + if (is_MemberExpression(node) || is_PostfixExpression(node) || is_UnaryExpression(node)) { + return pathCall(node, "expression", unwrap); + } + + if (is_ExpressionPath(node)) { + return pathCall(node, "namespace", (namespace) => !namespace || unwrap(namespace)); + } + + if (is_CallExpression_or_CallLikeMacroInvocation(node)) { + const doc = printCallExpression(getPrintFn(), node); + if ((doc as any).label === "member-chain") { + return false; + } + + const args = node.arguments; + const isPoorlyBreakableCall = args.length === 0 || (args.length === 1 && isLoneShortArgument(args[0])); + if (!isPoorlyBreakableCall) { + return false; + } + + if (hasComplexTypeArguments(node)) { + return false; + } + + return pathCall(node, "callee", unwrap); + } + + return topNode === node ? false : is_Identifier(node); + })(topNode); + } + } +} +function is_MemberExpression_with_RangeOrLiteral_Property(node: Node | undefined) { + return ( + !!node && is_MemberExpression(node) && (node.computed ? is_Literal_or_SimpleRangeLiteral(node.property) : is_Literal(node.property)) + ); +} +function is_Literal_or_SimpleRangeLiteral(node: Node) { + return is_Literal(node) + ? true + : is_RangeLiteral(node) + ? (!node.lower || is_Literal(node.lower)) && (!node.upper || is_Literal(node.upper)) + : false; +} +function printMemberLookup(print: print, node: MemberExpression) { + return !node.computed + ? [".", print("property")] + : is_Literal_or_SimpleRangeLiteral(node.property) + ? ["[", print("property"), "]"] + : group(["[", indent([softline, print("property")]), softline, "]"]); +} + +function shouldPrint_CallExpression_chain(node: CallExpression) { + return is_MemberAccessLike(node.callee) || is_CallExpression_or_CallLikeMacroInvocation(node.callee); +} +export function is_MemberAccessLike(node: Node): node is ExpressionPath | MemberExpression { + switch (node.nodeType) { + case NodeType.ExpressionPath: + case NodeType.MemberExpression: + return true; + default: + return false; + } +} +type ChainItem = { node: Node; printed: Doc; needsParens: boolean }; +function printMemberChain(print: print, node: CallExpression) { + const parent = getParentNode(); + const isExpressionStatement = !parent || is_ExpressionStatement(parent); + const { printedNodes, groups } = splitCallChains(node); + + const shouldMerge = groups.length >= 2 && !hasComment(groups[1][0].node) && shouldNotWrap(groups); + + const printedGroups = groups.map(printGroup); + const oneLine = printedGroups; + + const cutoff = shouldMerge ? 3 : 2; + + const nodeHasComment = + printedNodes.slice(1, -1).some(({ node }) => hasComment(node, CF.Leading)) || + printedNodes.slice(0, -1).some(({ node }) => hasComment(node, CF.Trailing)) || + (groups[cutoff] && hasComment(groups[cutoff][0].node, CF.Leading)); + + if (groups.length <= cutoff && !nodeHasComment) { + return isLongCurriedCallExpression(node) ? oneLine : group(oneLine); + } + + const lastNodeBeforeIndent = last_of(groups[shouldMerge ? 1 : 0]).node; + const shouldHaveEmptyLineBeforeIndent = + !is_CallExpression_or_CallLikeMacroInvocation(lastNodeBeforeIndent) && shouldInsertEmptyLineAfter(lastNodeBeforeIndent); + + const expanded = [ + printGroup(groups[0]), + shouldMerge ? groups.slice(1, 2).map(printGroup) : "", + shouldHaveEmptyLineBeforeIndent ? hardline : "", + printIndentedGroup(groups.slice(shouldMerge ? 2 : 1)), + ]; + + const callExpressions = printedNodes.map(({ node }) => node).filter(is_CallExpression_or_CallLikeMacroInvocation); + const result: Doc = + nodeHasComment || + (callExpressions.length > 2 && callExpressions.some((expr) => expr.arguments.some((arg) => !isSimpleCallArgument(arg, 0)))) || + printedGroups.slice(0, -1).some(willBreak) || + lastGroupWillBreakAndOtherCallsHaveFunctionArguments() + ? group(expanded) + : [shouldHaveEmptyLineBeforeIndent || willBreak(oneLine) ? breakParent : "", conditionalGroup([oneLine, expanded])]; + + return label("member-chain", result); + + function shouldInsertEmptyLineAfter(node: Node) { + let start = end(node); + const last = getNextNonSpaceNonCommentCharacterIndex(node); + const { originalText } = getContext().options; + while (start < last) { + if (originalText.charAt(start) === ")") { + return isNextLineEmptyAfterIndex(start + 1); + } + start++; + } + return isNextLineEmpty(node); + } + + function isFactory(name: string) { + return /^[A-Z]|^[$_]+$/.test(name); + } + function isShort(name: string) { + return name.length <= getContext().options.tabWidth; + } + + function shouldNotWrap(groups: ChainItem[][]) { + const hasComputed = groups[1].length > 0 && is_MemberExpression(groups[1][0].node) && groups[1][0].node.computed; + + if (groups[0].length === 1) { + const firstNode = groups[0][0].node; + return ( + is_Identifier(firstNode) && (isFactory(firstNode.name) || (isExpressionStatement && isShort(firstNode.name)) || hasComputed) + ); + } + + const lastNode = last_of(groups[0]).node; + const lastNodeLeft = is_ExpressionPath(lastNode) + ? lastNode.namespace + : is_MemberExpression(lastNode) + ? lastNode.expression + : undefined; + return lastNodeLeft && is_Identifier(lastNodeLeft) && (isFactory(lastNodeLeft.name) || hasComputed); + } + + function printGroup(g: ChainItem[]) { + const printed: Doc[] = []; + if (printedNodes[0] === g[0]) { + for (const item of printedNodes) { + if (item.needsParens) printed.unshift("("); + } + } + for (const item of g) { + printed.push(item.printed); + if (item.needsParens) printed.push(")"); + } + return printed; + } + + function printIndentedGroup(groups: ChainItem[][]) { + if (groups.length === 0) return ""; + return indent(group([hardline, join(hardline, groups.map(printGroup))])); + } + + function lastGroupWillBreakAndOtherCallsHaveFunctionArguments() { + const lastGroupNode = last_of(last_of(groups)).node; + const lastGroupDoc = last_of(printedGroups); + return ( + is_CallExpression_or_CallLikeMacroInvocation(lastGroupNode) && + willBreak(lastGroupDoc) && + callExpressions.slice(0, -1).some((node) => node.arguments.some(is_ClosureFunctionExpression)) + ); + } + + function splitCallChains(topNode: CallExpression | CallLikeMacroInvocation) { + const printedNodes: ChainItem[] = [ + { + node: topNode, + needsParens: false, + printed: print_CallExpression_end(print, node), + }, + ]; + + pathCall(topNode, "callee", function READ_LEFT(node: Node) { + if (is_CallExpression_or_CallLikeMacroInvocation(node) && shouldPrint_CallExpression_chain(node)) { + __DEV__: Narrow>(print); + + unshift(print_CallExpression_end(print, node), shouldInsertEmptyLineAfter(node)); + pathCall(node, "callee", READ_LEFT); + } else if (is_MemberExpression(node)) { + __DEV__: Narrow>(print); + + unshift(printMemberLookup(print, node)); + pathCall(node, "expression", READ_LEFT); + } else if (is_ExpressionPath(node)) { + __DEV__: Narrow>(print); + + unshift(["::", print("segment")]); + if (node.namespace) { + pathCall(node, "namespace", READ_LEFT as any); + } + } else if (is_PostfixExpression(node)) { + unshift(is_UnwrapExpression(node) ? "?" : ".await"); + pathCall(node, "expression", READ_LEFT); + } else { + printedNodes.unshift({ node, needsParens: false, printed: print() }); + } + + function unshift(printed: Doc, needsHardlineAfter = false) { + printedNodes.unshift({ + node, + needsParens: is_MemberAccessLike(node) && needsParens(node), + printed: [withComments(node, printed), needsHardlineAfter ? hardline : ""], + }); + } + }); + + const groups = spread(function* () { + let i = 0; + let currentItem = printedNodes[i]; + + function testNextItem(fn: (item: ChainItem) => boolean) { + return i + 1 < printedNodes.length && fn(printedNodes[i + 1]); + } + + function readGroup(fn: () => Iterable) { + __DEV__: assert(i < printedNodes.length); + return spread(function* () { + for (var _item of fn()) { + yield currentItem; + if (++i < printedNodes.length) currentItem = printedNodes[i]; + else break; + } + }); + } + + function* loop(condition: (item: ChainItem) => boolean) { + while (condition(currentItem)) yield currentItem; + } + function* until(condition: (item: ChainItem) => boolean) { + while (!condition(currentItem)) yield currentItem; + } + + yield readGroup(function* () { + const isCallExpression = is_CallExpression_or_CallLikeMacroInvocation(currentItem.node); + + yield currentItem; + yield* loop( + ({ node, needsParens }) => + is_PostfixExpression(node) || + is_CallExpression_or_CallLikeMacroInvocation(node) || + is_MemberExpression_with_RangeOrLiteral_Property(node) || + needsParens + ); + + if (!isCallExpression) { + yield* loop( + ({ node, needsParens }) => + is_MemberAccessLike(node) && // + testNextItem(({ node }) => is_MemberAccessLike(node)) + ); + } + }); + + while (i < printedNodes.length) { + yield readGroup(function* () { + let isCallExpression = false; + + yield* until( + ({ node }) => + (isCallExpression = is_CallExpression_or_CallLikeMacroInvocation(node)) || // + hasComment(node, CF.Trailing) + ); + yield currentItem; + + if (isCallExpression) { + yield* loop(({ node }) => is_MemberExpression_with_RangeOrLiteral_Property(node)); + yield* until( + ({ node }) => + is_MemberAccessLike(node) || // + hasComment(node, CF.Trailing) + ); + } + }); + } + }); + return { printedNodes, groups }; + } +} + +function isSimpleCallArgument(node: Node, depth: number) { + if (depth >= 2) return false; + + if (is_IdentifierOrIndex(node)) { + return true; + } + + if (is_Literal(node)) { + return !is_LiteralStringLike(node) || !node.value.includes("\n"); + } + + if (is_ArrayOrTupleLiteral(node)) { + return node.items.every(isChildSimple); + } + + if (is_StructLiteral(node)) { + return ( + isSimpleCallArgument(node.struct, depth) && + node.properties.every((prop) => + is_StructLiteralPropertySpread(prop) + ? isChildSimple(prop.expression) + : is_StructLiteralProperty(prop) + ? isChildSimple(prop.value) + : true + ) + ); + } + + if (is_CallExpression_or_CallLikeMacroInvocation(node)) { + return ( + isSimpleCallArgument(node.callee, depth) && + (node.typeArguments ?? []).every(isChildSimple) && + node.arguments.every(isChildSimple) + ); + } + + if (is_MemberExpression(node)) { + return isSimpleCallArgument(node.expression, depth) && isSimpleCallArgument(node.property, depth); + } + + if (is_ExpressionTypeCast(node)) { + return isSimpleCallArgument(node.typeCallee, depth) && node.typeArguments.every(isChildSimple); + } + + if (is_ExpressionPath(node)) { + const namespace = node.namespace; + return !namespace || isSimpleCallArgument(namespace, depth); + } + + if (is_UnaryExpression(node) || is_PostfixExpression(node)) { + return isSimpleCallArgument(node.expression, depth); + } + + return false; + + function isChildSimple(child: Node) { + return isSimpleCallArgument(child, depth + 1); + } +} +function isLongCurriedCallExpression(node: Node) { + const parent = getParentNode(node)!; + return ( + is_CallExpression_or_CallLikeMacroInvocation(node) && + is_CallExpression_or_CallLikeMacroInvocation(parent) && + parent.callee === node && + node.arguments.length > parent.arguments.length && + parent.arguments.length > 0 + ); +} + +export function printTypeArguments>(print: print, node: T) { + return !node.typeArguments + ? "" + : node.typeArguments.length === 0 + ? ["<", printDanglingCommentsForInline(node, DCM["typeArguments"]), ">"] + : hasComplexTypeArguments(node) + ? group( + [ + "<", // + indent([softline, print.join("typeArguments", [",", line])]), + softline, + ">", + ], + { id: getTypeParametersGroupId(node) } + ) + : ["<", print.join("typeArguments", ", "), ">"]; +} + +export function printLtParameters>(print: print, node: T) { + return !node.ltParameters + ? "" + : node.ltParameters.length === 0 + ? ["for<", printDanglingCommentsForInline(node, DCM["ltParameters"]), "> "] + : hasComplexLtParameters(node) + ? group( + [ + "for<", // + indent([softline, print.join("ltParameters", [",", line])]), + softline, + "> ", + ], + { id: getTypeParametersGroupId(node) } + ) + : ["for<", print.join("ltParameters", ", "), "> "]; +} + +export function printGenerics(print: print, node: T) { + return group( + !node.generics + ? "" + : hasComplexGenerics(node) + ? [ + "<", + indent([softline, print.join("generics", [",", line])]), // + hasMultipleHeritage(node) ? indent([softline, ">"]) : [softline, ">"], + ] + : [ + "<", + print.join("generics", ", "), // + printDanglingCommentsForInline(node, DCM["generics"]), + ">", + ] + ); +} +function getPrintedTypeBounds>(print: print, node: T) { + if (!hasTypeBounds(node) || node.typeBounds.length === 0) return ""; + if (node.typeBounds.length === 1) return print.map("typeBounds"); + // let shouldIndent = false; + // const printed = print.map("typeBounds", (bound, i, arr) => + // 0 === i + // ? print() + // : true //isSimpleTypeBound(arr[i - 1]) && isSimpleTypeBound(bound) + // ? indent([" +", line, print()]) + // : [" + ", (shouldIndent ||= isSimpleTypeBound(arr[i - 1]) || isSimpleTypeBound(bound)) ? indent(print()) : print()] + // ); + const printed = print.join("typeBounds", (_, __, prev) => (!prev ? " +" : [" +", line])); + return [printed.shift()!, indent([line, printed])]; +} +export function printTypeBounds>(operator: "dyn" | "impl" | ":", print: print, node: T) { + if (!hasTypeBounds(node)) return ""; + const printed = getPrintedTypeBounds(print, node); + return printed ? group([operator, " ", printed]) : operator; + + return !node.typeBounds + ? "" + : hasComplexTypeBounds(node) + ? group(indent([ifBreak(line), operator, " ", join([line, "+ "], print.map("typeBounds"))])) + : [operator, " ", join(" + ", print.map("typeBounds"))]; +} + +export function printLtBounds>(left: Doc, print: print, node: T) { + return group( + !node.ltBounds + ? "" + : node.ltBounds.length === 0 + ? [left, " "] + : [left, " ", print.map("ltBounds", (typeBound, i) => (i === 0 ? print() : indent([line, "+ ", print()])))] + ); +} + +function printWhereBounds(print: print, node: T) { + if (!node.whereBounds || node.whereBounds.length === 0) return ""; + return adjustDeclarationClause( + node, // + "where", + print.join("whereBounds", [",", line]) + ); +} +export function printDeclarationTypeBounds>( + print: print, + node: T, + operator: ":" | " =" +) { + return hasTypeBounds(node) ? adjustDeclarationClause(node, operator, getPrintedTypeBounds(print, node)) : ""; +} +export function printImplTraitForType( + print: print, + node: ImplDeclaration | NegativeImplDeclaration +) { + return node.trait + ? [print("trait"), adjustDeclarationClause(node, "for", print("typeTarget"))] // + : print("typeTarget"); +} + +function adjustDeclarationClause(node: DeclarationNode, clause: ":" | " =" | "for" | "->" | "where", content: Doc) { + const isTypeBoundsClause = clause === ":" || clause === " ="; + return (clause === "where" || hasMultipleHeritage(node) ? indent : Identity)([ + clause === "->" + ? hasMultipleHeritage(node) && node.whereBounds!.length > 1 + ? line + : " " + : clause === "where" // + ? line + : isTypeBoundsClause + ? hasMultipleHeritage(node) + ? softline + : "" + : line, + clause, + content && + group( + clause === "where" + ? indent([line, content]) // + : clause === "->" || isTypeBoundsClause + ? [" ", content] + : [line, content] + ), + ]); +} +function hasNonWhereHeritageClause(node: DeclarationNode) { + AssertTypesEq< + DeclarationNode, + | FunctionDeclaration + | StructDeclaration + | TupleStructDeclaration + | UnionDeclaration + | TypeAliasDeclaration + | TraitDeclaration + | TraitAliasDeclaration + | NegativeImplDeclaration + | ImplDeclaration + | EnumDeclaration + >(); + switch (node.nodeType) { + case NodeType.FunctionDeclaration: + return !!node.returnType; + case NodeType.StructDeclaration: + case NodeType.TupleStructDeclaration: + case NodeType.UnionDeclaration: + case NodeType.EnumDeclaration: + return false; + case NodeType.TypeAliasDeclaration: + case NodeType.TraitDeclaration: + case NodeType.TraitAliasDeclaration: + // case NodeType.AutoTraitDeclaration: + return hasTypeBounds(node); + case NodeType.ImplDeclaration: + case NodeType.NegativeImplDeclaration: + return !!node.trait; + default: + __DEV__: exit.never(node); + } +} +function hasAnyHeritageClause(node: DeclarationNode) { + return !!node.whereBounds || hasNonWhereHeritageClause(node); +} + +function hasMultipleHeritage(node: DeclarationNode) { + return !!node.whereBounds && hasNonWhereHeritageClause(node); +} + +const getMacroGroupId = createGroupIdMapper("MacroGroup"); +const getHeritageGroupId = createGroupIdMapper("heritageGroup"); +const getWhereBoundsGroupId = createGroupIdMapper("where"); +const getTypeParametersGroupId = createGroupIdMapper("typeParameters"); +function createGroupIdMapper(description: string) { + const groupIds = new WeakMap(); + return (node: Node) => Map_get(groupIds, node, () => Symbol(description)); +} + +export function printDanglingCommentsForInline(node: Node, marker?: DCM) { + const hasOnlyBlockComments = + !hasComment(node, CF.Line | CF.Dangling, (comment) => !marker || comment.marker === marker) || is_Program(node); + const printed = printDanglingComments(node, hasOnlyBlockComments, marker); + return ( + printed && + (hasOnlyBlockComments && !is_Program(node) + ? willBreak(printed) + ? [indent([hardline, printed]), hardline] + : [printed] + : [printed, hardline]) + ); +} + +function isFormatLikeCall(node: CallExpression | CallLikeMacroInvocation) { + if (is_Identifier(node.callee) && !node.typeArguments) { + const [first, ...rest] = node.arguments; + if (is_Literal(first) && is_LiteralStringLike(first) && first.value.includes("{}") && rest.every(is_Identifier)) { + return true; + } + } + return false; +} + +class ArgExpansionBailout extends Error {} + +export function printCallArguments(print: print, node: T) { + const args = node.arguments; + const { left: LEFT, right: RIGHT } = getDelimChars(args); + + __DEV__: { + // assert(args.length === 0 || !hasComment(node, CF.Dangling), "", node); + if (is_MacroInvocation(node)) assert(args.every(is_ExpressionNode), "", node); + } + + if (args.length === 0) return [LEFT, printDanglingCommentsForInline(node, DCM["arguments"]), RIGHT]; + + // force inline format!(" {} ", ident) + if (args.length === 2 && isFormatLikeCall(node)) { + return [LEFT, print(["arguments", 0]), ", ", print(["arguments", 1]), RIGHT]; + } + + let anyArgEmptyLine = false; + let hasEmptyLineFollowingFirstArg = false; + const lastArgIndex = args.length - 1; + const trailingComma = false ? "," : ""; + const printedArguments = print.map("arguments", (arg, index, arr) => { + if (index === lastArgIndex) { + return [print()]; + } else if (isNextLineEmpty(arg)) { + if (index === 0) hasEmptyLineFollowingFirstArg = true; + anyArgEmptyLine = true; + return [print(), ",", hardline, hardline]; + } else { + return [print(), ",", line]; + } + }); + + if (anyArgEmptyLine || isFunctionCompositionArgs(args)) { + return allArgsBrokenOut(); + } + + const shouldGroupFirst = shouldGroupFirstArg(args); + const shouldGroupLast = shouldGroupLastArg(args); + if (shouldGroupFirst || shouldGroupLast) { + if (shouldGroupFirst ? printedArguments.slice(1).some(willBreak) : printedArguments.slice(0, -1).some(willBreak)) { + return allArgsBrokenOut(); + } + let printedExpanded: Doc[] = []; + const { path } = getContext(); + const stackBackup = [...path.stack]; + try { + path_try(() => { + getContext().path.each((p, i) => { + if (shouldGroupFirst && i === 0) { + printedExpanded = [ + [ + print([], { expandFirstArg: true }), + printedArguments.length > 1 ? "," : "", + hasEmptyLineFollowingFirstArg ? hardline : line, + hasEmptyLineFollowingFirstArg ? hardline : "", + ], + ...printedArguments.slice(1), + ]; + } + if (shouldGroupLast && i === lastArgIndex) { + printedExpanded = [...printedArguments.slice(0, -1), print([], { expandLastArg: true })]; + } + }, "arguments"); + }); + } catch (caught) { + path.stack.length = 0; + path.stack.push(...stackBackup); + if (caught instanceof ArgExpansionBailout) return allArgsBrokenOut(); + throw caught; + } + + return [ + printedArguments.some(willBreak) ? breakParent : "", + conditionalGroup([ + [LEFT, ...printedExpanded, RIGHT], + shouldGroupFirst + ? [LEFT, group(printedExpanded[0], { shouldBreak: true }), ...printedExpanded.slice(1), RIGHT] + : [LEFT, ...printedArguments.slice(0, -1), group(printedExpanded[lastArgIndex], { shouldBreak: true }), RIGHT], + allArgsBrokenOut(), + ]), + ]; + } + + const contents = [LEFT, indent([softline, ...printedArguments]), ifBreak(trailingComma), softline, RIGHT]; + + return isLongCurriedCallExpression(node) + ? contents + : group(contents, { shouldBreak: anyArgEmptyLine || printedArguments.some(willBreak) }); + + function allArgsBrokenOut() { + return group([LEFT, indent([line, ...printedArguments]), trailingComma, line, RIGHT], { shouldBreak: true }); + } +} + +function shouldHugFunctionParameters(node: Extract) { + if (!node) return false; + const parameters = getParameters(node); + if (parameters.length !== 1) return false; + const param = parameters[0]; + if (hasComment(param)) return false; + switch (param.nodeType) { + case NodeType.FunctionSelfParameterDeclaration: + case NodeType.FunctionSpread: + case NodeType.TypeFnPointerParameter: + default: + return false; + case NodeType.FunctionParameterDeclaration: + case NodeType.ClosureFunctionParameterDeclaration: + return "items" in param.pattern || "properties" in param.pattern; + } +} + +function shouldGroupFunctionParameters(functionNode: FunctionDeclaration, returnTypeDoc: Doc) { + const returnType = functionNode.returnType; + const generics = functionNode.generics; + const whereBounds = functionNode.whereBounds; + if (!returnType) return false; + if (generics) { + if (generics.length > 1) return false; + if (generics.length === 1 && !isShortGenericParameterDeclaration(generics[0])) return false; + } + if (whereBounds) { + if (whereBounds.length > 1) return false; + } + return ( + getParameters(functionNode).length === 1 && + (willBreak(returnTypeDoc) || willBreak(printWhereBounds(getPrintFn(functionNode), functionNode))) + ); +} + +export function printBlockBody(print: print, node: T): Doc { + const body = printBodyOrCases(print, node); + return [ + "{", + body.length > 0 + ? getBodyOrCases(node)?.length + ? canInlineBlockBody(node) + ? [indent([line, body]), line] + : group([indent([line, body]), line], { shouldBreak: true }) + : body + : emptyContent(node), + "}", + ]; +} + +export function printMaybeBlockBody( + print: print, + node: T +): Doc { + return hasSemiNoBody(node) ? ";" : adjustClause(node, printBlockBody(print, node)); +} + +export function printArrowFunction(print: print, node: T) { + const signatures: Doc[] = []; + const body: Doc[] = []; + const { args, path } = getContext(); + let chainShouldBreak = false; + + let tailNode: ClosureFunctionExpression = node; + (function rec(node: ClosureFunctionExpression) { + tailNode = node; + const doc = printArrowFunctionSignature(print, node); + if (signatures.length === 0) { + signatures.push(doc); + } else { + const { leading, trailing } = printCommentsSeparately(); + signatures.push([leading!, doc]); + body.unshift(trailing!); + } + + chainShouldBreak ||= !!node.returnType || !node.parameters.every((param) => isSimplePattern(param.pattern)); + + if (!is_ClosureFunctionExpression(node.expression) || (args && args.expandLastArg)) { + body.unshift(print("expression", args)); + } else { + pathCall(node, "expression", rec as any); + } + })(node); + + if (signatures.length > 1) { + return printArrowChain(signatures, chainShouldBreak, body, tailNode); + } else { + const printed = signatures[0]; + + if ( + !hasLeadingOwnLineComment(node.expression) && + (is_ArrayOrTupleLiteral(node.expression) || + is_StructLiteral(node.expression) || + is_ExpressionWithBodyOrCases(node.expression) || + is_ClosureFunctionExpression(node.expression)) + ) { + return group([printed, " ", body]); + } + + const shouldAddSoftLine = args && args.expandLastArg && !hasComment(node); + const printTrailingComma = args && args.expandLastArg && false; + const shouldAddParens = is_OrExpression(node.expression); + + return group([ + printed, + group([ + indent(shouldAddParens ? [line, ifBreak("", "("), body, ifBreak("", ")")] : [line, body]), + shouldAddSoftLine ? [ifBreak(printTrailingComma ? "," : ""), softline] : "", + ]), + ]); + } +} + +function printArrowChain(signatures: Doc[], shouldBreak: boolean, bodyDoc: Doc, tailNode: ClosureFunctionExpression) { + const { args } = getContext(); + const parent = getParentNode()!; + const isCallee = is_CallExpression_or_CallLikeMacroInvocation(parent) && parent.callee === getNode(); + const isAssignmentRhs = !!(args && args.assignmentLayout); + const shouldPutBodyOnSeparateLine = !is_ExpressionWithBodyOrCases(tailNode.expression) && !is_StructLiteral(tailNode.expression); + const shouldBreakBeforeChain = + (isCallee && shouldPutBodyOnSeparateLine) || (args && args.assignmentLayout === Layout["chain-tail-arrow-chain"]); + + const groupId = Symbol("arrow-chain"); + + return group([ + group( + indent([isCallee || isAssignmentRhs ? softline : "", group(join(line, signatures), { shouldBreak })]), // + { id: groupId, shouldBreak: shouldBreakBeforeChain } + ), + indentIfBreak(shouldPutBodyOnSeparateLine ? indent([line, bodyDoc]) : [" ", bodyDoc], { groupId }), + isCallee ? ifBreak(softline, "", { groupId }) : "", + ]); +} + +function printArrowFunctionSignature(print: print, node: T) { + const { args } = getContext(); + const expandArg = args && (args.expandLastArg || args.expandFirstArg); + let returnTypeDoc: Doc = printReturnType(print, node); + if (expandArg) { + if (willBreak(returnTypeDoc)) throw new ArgExpansionBailout(); + else returnTypeDoc = group(removeLines(returnTypeDoc)); + } + // const dangling = printDanglingComments(node, true, (comment) => node.parameters.loc.contains(comment)); + + // if (dangling) { + // parts.push(" ", dangling); + // } + return [ + print.b("static"), + print.b("async"), + print.b("move"), // + group([printFunctionParameters(print, node, expandArg), returnTypeDoc]), + ]; +} + +export function printGenerics_x_whereBounds(print: print, node: T, xDoc: Doc) { + const generics: Doc = is_ImplDeclarationNode(node) + ? [printGenerics(print, node), " "] + : [" ", print("id" as any), printGenerics(print, node)]; + + const whereBoundsDoc = printWhereBounds(print, node); + + return is_TupleStructDeclaration(node) + ? [...generics, xDoc, group(whereBoundsDoc, { id: getHeritageGroupId(node) })] + : [...generics, group([xDoc, whereBoundsDoc], { id: getHeritageGroupId(node) })]; +} + +export function adjustClause( + node: DeclarationNode | ((NodeWithBodyOrCases | BlockLikeMacroInvocation) & { body: undefined | {} }), + doc: Doc +) { + return [ + "whereBounds" in node && (!!node.whereBounds || (hasTypeBounds(node) && node.typeBounds.length > 1)) && willBreak(doc) + ? ifBreak(hardline, " ", { groupId: getHeritageGroupId(node) }) + : " ", + doc, + ]; +} + +export function printParametersAndReturnType(node: FunctionNode | TypeFunctionNode) { + const parametersDoc = printFunctionParameters(getPrintFn(node), node); + const returnTypeDoc = printReturnType(getPrintFn(node), node); + return is_FunctionDeclaration(node) && shouldGroupFunctionParameters(node, returnTypeDoc) + ? group([group(parametersDoc), returnTypeDoc]) + : group([parametersDoc, returnTypeDoc]); +} + +export function printFlowControlExpression(print: print, node: T) { + return !node.expression + ? "" + : // : hasLeadingComment(node.expression) + // ? [" (", indent([hardline, print("expression")]), hardline, ")"] + is_BinaryishExpression(node.expression) && !flowControlExpressionNeedsOuterParens(node) + ? group([" ", ifBreak("("), indent([softline, print("expression")]), softline, ifBreak(")")]) + : [" ", print("expression")]; + + function hasLeadingComment(node: Node) { + if (hasLeadingOwnLineComment(node)) return true; + if (hasNakedLeftSide(node)) { + let leftMost: Node | undefined = node; + while (leftMost && (leftMost = getLeftSide(leftMost))) { + if (hasLeadingOwnLineComment(leftMost)) return true; + } + } + return false; + } +} +export function flowControlExpressionNeedsOuterParens(flow: ReturnExpression | BreakExpression | YieldExpression) { + return ( + flow.expression && + (function hasLeadingComment(node: Node) { + if (hasLeadingOwnLineComment(node)) return true; + if (hasNakedLeftSide(node)) { + let leftMost: Node | undefined = node; + while (leftMost && (leftMost = getLeftSide(leftMost))) { + if (hasLeadingOwnLineComment(leftMost)) return true; + } + } + return false; + })(flow.expression) + ); +} +function getLeftSide(node: any, includeAttributes = false): Node | undefined { + let target: Node | undefined = + (node as LeftRightExpression).left ?? + (node as CallExpression).callee ?? + (node as PathNode).namespace ?? + (node as ExpressionWithBody).label ?? + (node as RangeNode).lower ?? + (node as StructLiteral).struct ?? + (node as NodeWithCondition).condition ?? + (node as ExpressionBody).expression; + if (target && includeAttributes && hasAttributes(node)) { + (node.attributes as AttributeOrDocComment[]).forEach((attr) => { + if (start(attr) < start(target!)) target = attr; + }); + } + return target; +} +function hasNakedLeftSide(node: Node) { + return ( + is_BinaryishExpression(node) || + is_ReassignmentNode(node) || + is_CallExpression_or_CallLikeMacroInvocation(node) || + is_MemberAccessLike(node) || + is_PostfixExpression(node) || + is_ExpressionAsTypeCast(node) + ); +} + +export function printReturnType>(print: print, node: T) { + return node.returnType + ? is_FunctionDeclaration(node) + ? adjustDeclarationClause(node, "->", print("returnType")) + : [" -> ", print("returnType")] + : ""; +} + +function printFunctionParameters( + print: print, + node: T, + expandArg = false, + printTypeParams = false +) { + const { left: leftDelim, right: rightDelim } = getDelimChars(node.parameters); + const generics = printTypeParams && is_FunctionDeclaration(node) ? printGenerics(print as any, node) : ""; + + if (!hasParameters(node)) { + return [ + generics, // + leftDelim, + printDanglingCommentsForInline(node, DCM["parameters"]), + rightDelim, + ]; + } + + const isParametersInTestCall = false; + const shouldHugParameters = shouldHugFunctionParameters(node); + const printed = print.join("parameters", sepFn); + if (hasSelfParameter(node)) { + printed.unshift(getContext().path.call(() => [print(), printed.length ? sepFn(node.parameters.self) : ""], "parameters", "self")); + } + + if (expandArg) { + if (willBreak(generics) || willBreak(printed)) throw new ArgExpansionBailout(); + return group([removeLines(generics), leftDelim, removeLines(printed), rightDelim]); + } else if (shouldHugParameters || isParametersInTestCall) { + return [generics, leftDelim, ...printed, rightDelim]; + } else { + return [generics, leftDelim, indent([softline, ...printed]), softline, rightDelim]; + } + function sepFn(param: Node) { + return shouldHugParameters || isParametersInTestCall // + ? ", " + : isNextLineEmpty(param) + ? [",", hardline, hardline] + : [",", line]; + } +} + +function path_try(callback: () => T): T { + const { stack } = getContext().path; + const stackBackup = [...stack]; + try { + return callback(); + } finally { + stack.length = 0; + stack.push(...stackBackup); + } +} +function shouldGroupFirstArg(args: LocArray): boolean { + if (args.length !== 2) return false; + const [firstArg, secondArg] = args; + return ( + !hasComment(firstArg) && + is_ClosureFunctionExpression(firstArg) && + is_ExpressionWithBodyOrCases(firstArg.expression) && + !is_ClosureFunctionExpression(secondArg) && + !couldGroupArg(secondArg) + ); +} +function shouldGroupLastArg(args: LocArray): boolean { + const lastArg = last_of(args); + const preLastArg = args[args.length - 2]; + return ( + !hasComment(lastArg, CF.Leading) && + !hasComment(lastArg, CF.Trailing) && + couldGroupArg(lastArg) && + (!preLastArg || preLastArg.nodeType !== lastArg.nodeType) && + (args.length !== 2 || !is_ClosureFunctionExpression(preLastArg) || !is_ArrayOrTupleLiteral(lastArg)) && + !(args.length > 1 && is_ArrayOrTupleLiteral(lastArg) && isConciselyPrintedArray(lastArg)) && + (args.length !== 1 || !is_IfBlockExpression(lastArg)) + ); +} +function couldGroupArg(arg: Node, arrowChainRecursion = false): boolean { + return ( + (is_StructLiteral(arg) && (arg.properties.length > 0 || hasComment(arg))) || + (is_ArrayOrTupleLiteral(arg) && (arg.items.length > 0 || hasComment(arg))) || + (is_ExpressionAsTypeCast(arg) && couldGroupArg(arg.expression)) || + (is_ClosureFunctionExpression(arg) && + (!arg.returnType || is_Identifier(arg.returnType) || !isNonEmptyBlockStatement(arg.expression)) && + (isNonEmptyBlockStatement(arg.expression) || + (is_ClosureFunctionExpression(arg.expression) && couldGroupArg(arg.expression, true)) || + is_StructLiteral(arg.expression) || + is_ArrayOrTupleLiteral(arg.expression) || + (!arrowChainRecursion && is_CallExpression_or_CallLikeMacroInvocation(arg.expression)))) || + is_ExpressionWithBodyOrCases(arg) //&& isNonEmptyBlockStatement(arg) + ); +} +function isNonEmptyBlockStatement(node: Node) { + if (is_MatchExpression(node)) return node.cases.length > 0; + return is_ExpressionWithBodyOrCases(node) && node.body.length > 0; +} +function isFunctionCompositionArgs(args) { + if (args.length <= 1) { + return false; + } + let count = 0; + for (const arg of args) { + if (is_ClosureFunctionExpression(arg)) { + if (++count > 1) return true; + } else if (is_CallExpression_or_CallLikeMacroInvocation(arg)) { + for (const childArg of arg.arguments) { + if (is_ClosureFunctionExpression(childArg)) { + return true; + } + } + } + } + return false; +} + +export function printBinaryishExpression( + print: print, + node: T +) { + const parent = getParentNode()!; + const grandParent = getGrandParentNode(); + const isInsideParenthesis = ("condition" in parent && parent.condition === node) || is_MatchExpression(parent); + + const parts = printBinaryishExpressions(false, isInsideParenthesis); + if (isInsideParenthesis) return parts; + if ( + (is_CallExpression_or_CallLikeMacroInvocation(parent) && parent.callee === node) || // + is_UnaryExpression(parent) || + is_MemberExpression(parent) + ) { + return group([indent([softline, ...parts]), softline]); + } + + const shouldNotIndent = + is_FlowControlExpression(parent) || + (is_ClosureFunctionExpression(parent) && parent.expression === node) || + is_ExpressionWithBodyOrCases(parent); + + const shouldIndentIfInlining = + is_ReassignmentNode(parent) || is_VariableDeclarationNode(parent) || is_StructLiteral(parent) || is_StructLiteral(grandParent); + + const samePrecedenceSubExpression = is_BinaryishExpression(node.left) && shouldFlatten(node, node.left); + + if ( + shouldNotIndent || + (shouldInlineLogicalExpression(node) && !samePrecedenceSubExpression) || + (!shouldInlineLogicalExpression(node) && shouldIndentIfInlining) + ) { + return group(parts); + } + + if (parts.length === 0) return ""; + const firstGroupIndex = parts.findIndex((part) => typeof part !== "string" && !Array.isArray(part) && part.type === "group"); + const leading = parts.slice(0, firstGroupIndex === -1 ? 1 : firstGroupIndex + 1); + return group([...leading, indent(parts.slice(leading.length))], { id: Symbol("logicalChain") }); + + function printBinaryishExpressions(isNested: boolean, isInsideParenthesis: boolean): Doc[] { + const { path, print, options } = getContext(); + const node = path.getValue(); + + if (!is_BinaryishExpression(node)) { + return [group(print())]; + } + + const parts: Doc[] = []; + + if (shouldFlatten(node, node.left)) { + parts.push(...pathCall(node, "left", () => printBinaryishExpressions(true, isInsideParenthesis))); + } else { + parts.push(group(print("left"))); + } + + const shouldInline = shouldInlineLogicalExpression(node); + const operator = node.kind; + + const right = [ + operator, + shouldInline ? " " : line, + // this is a hack (should always be 'print("right")') + !shouldInline && is_LogicalExpression(node.right) && shouldFlatten(node.right, node) + ? pathCall(node, "right", () => printBinaryishExpressions(true, isInsideParenthesis)) + : print("right"), + ]; + + const shouldBreak = hasComment(node.left, CF.Trailing | CF.Line); + const shouldGroup = + shouldBreak || + (!(isInsideParenthesis && is_LogicalExpression(node)) && + path.getParentNode()!.nodeType !== node.nodeType && + node.left.nodeType !== node.nodeType && + node.right.nodeType !== node.nodeType); + + parts.push(" ", shouldGroup ? group(right, { shouldBreak }) : right); + + if (isNested && hasComment(node)) { + const printed = cleanDoc(withComments(node, parts)); + if (isConcat(printed) || (printed as any).type === "fill") { + return getDocParts(printed) as Doc[]; + } + + return [printed]; + } + + return parts; + } +} + +// export function printLogicalExpression(print: print, node: T) { +// if (!is_insideScrutinee(node)) return printBinaryishExpression(print, node); + +// } + +function shouldInlineLogicalExpression(node: Node) { + if (is_LogicalExpression(node)) { + if (is_StructLiteral(node.right)) return node.right.properties.length > 0; + if (is_ArrayOrTupleLiteral(node.right)) return node.right.items.length > 0; + } + return false; +} + +export function printUnaryExpression(leftDoc: Doc, node: T) { + __DEV__: assert(is_UnaryExpression(node)); + // if (unaryNeedsOuterParens(node)) { + // return [leftDoc, group(["(", indent([softline, getPrintFn()("expression")]), softline, ")"])]; + // } else { + const printed = getPrintFn(node)("expression"); + + return group([leftDoc, printed]); + // } +} + +export function printIfBlock(print: print, node: T) { + let printed: Doc = [ + printIfBlockCondition(print, node), // + printBlockBody(print, node), + f` else ${print("else")}`, + ]; + + const parent = getParentNode()!; + + if (is_ClosureBlock(node, parent)) { + printed = parenthesize_if_break([indent([softline, printed]), softline]); + } else if (!is_ElseBlock(node, parent)) { + // if (needsParens(node)) { + // printed = group(printed); + // } + // if (needsParens(node)) { + // printed = group([indent([softline, printed]), softline]); + // } else { + // printed = group(printed); + // } + } + + return printed; +} + +export function printIfBlockCondition(print: print, node: T) { + if (!hasCondition(node)) return ""; + // const id = Symbol("if"); + // return ["if ", group(printCondition(print, node), { id }), ifBreak("", " ", { groupId: id })]; + return f`if ${printCondition(print, node)}`; +} + +// export function printMatchExpressionExpression(print: print, node: T) { +// return ["match ", maybe_parenthesize_condition(print, node), " "]; +// } + +export function printCondition(print: print, node: T) { + return pathCall(node, "condition", (condition) => { + if (!condition) return ""; + if (needsParens(condition)) return [print(), " "]; + const id = Symbol("condition"); + const softlineStart = true; //!is_LetScrutinee(condition); //!is_LetScrutinee(getLeftMostCondition(condition)); + const printed = [indent([softlineStart ? softline : "", print()]), softline]; + return [group(printed, { id }), ifBreak("", " ", { groupId: id })]; + return hasLetScrutineeCondition(node) + ? printed // parenthesizing nested "let" throws 'unused_parens' + : parenthesize_if_break(printed); + }); +} +export function parenthesize_if_break(doc: Doc) { + return conditionalGroup([doc, ["(", doc, ")"]], { shouldBreak: willBreak(doc) }); +} +function unwrapParenthesized(doc: Doc) { + __DEV__: { + const check = (doc: Doc) => Array.isArray(doc) && doc.length === 3 && doc[0] === "(" && doc[2] === ")"; + assert(Array.isArray(doc) && check(doc.length === 1 ? doc[0] : doc), "Expected ['(', Doc, ')']", doc); + } + return doc.length === 1 ? doc[0][1] : doc[1]; +} +export function withParensIdentOnBreak(node: Node, printed: Doc) { + return printed; + const parens = needsParens(node); + const grouped = group([indent([softline, parens ? unwrapParenthesized(printed) : printed]), softline]); + return conditionalGroup([printed, parens ? ["(", grouped, ")"] : grouped], { shouldBreak: willBreak(printed) }); +} +function isSimplePattern(node: PatternNode | undefined) { + if (!node) return false; + switch (node.nodeType) { + case NodeType.MacroInvocation: + return false; + case NodeType.ExpressionTypeCast: + return isSimplePattern(node.typeCallee) && !hasComplexTypeArguments(node); + case NodeType.ExpressionTypeSelector: + return is_Identifier(node.typeTarget) && (!node.typeExpression || is_Identifier(node.typeExpression)); + case NodeType.ExpressionPath: + return !node.namespace || isSimplePattern(node.namespace); + case NodeType.RangePattern: + return (!node.lower || isSimplePattern(node.lower)) && (!node.upper || isSimplePattern(node.upper)); + case NodeType.PatternVariableDeclaration: + case NodeType.ReferencePattern: + case NodeType.BoxPattern: + case NodeType.MinusPattern: + return isSimplePattern(node.pattern); + case NodeType.Identifier: + case NodeType.Literal: + case NodeType.RestPattern: + case NodeType.WildcardPattern: + return true; + default: + return false; + } +} + +export function printUnionPattern(print: print, node: T) { + if (node.patterns.length === 1) return print.map("patterns"); + const parent = getParentNode(); + const prebreak = parent && (is_VariableDeclarationNode(parent) || is_LetScrutinee(parent)) && !needsParens(node); + return group([ + prebreak ? softline : "", + print.map("patterns", (node, i, arr) => [ + withComments(node, [ + i === 0 // + ? ifBreak("| ") + : "| ", + align(2, print()), + ]), + i === arr.length - 1 ? "" : line, + ]), + ]); +} + +export function printArrayLike(print: print, node: T) { + const delims = getDelimChars(node.items); + if (node.items.length === 0) { + const comments = printDanglingCommentsForInline(node, DCM["items"]); + return comments ? group([delims.left, comments, delims.right]) : delims.left + delims.right; + } + + const groupId = Symbol("array"); + const shouldBreak = + // is_TupleStructDeclaration(node) || + !is_TupleNode(node) && + node.items.length > 1 && + (node.items as (ExpressionNode | PatternNode)[]).every((item, i) => { + const next = node.items[i + 1]; + return ( + ((hasProperties(item) && item.properties.length > 1) || (hasItems(item) && item.items.length > 1)) && + (!next || item.nodeType === next.nodeType) + ); + }); + const shouldUseConciseFormatting = isConciselyPrintedArray(node); + + const parent = getParentNode(node)!; + const needsForcedTrailingComma = + node.items.length === 1 + ? is_TupleLiteral(node) + ? is_RangeLiteral(node.items[0]) + ? !(is_ReassignmentExpression(parent) && parent.left === node) + : true + : is_TuplePattern(node) + ? !node.struct && !is_RangePattern(node.items[0]) && !is_RestPattern(node.items[0]) + : is_TypeTuple(node) + ? true + : false + : false; + const trailingComma: Doc = needsForcedTrailingComma + ? "," // + : shouldUseConciseFormatting + ? ifBreak(",", "", { groupId }) + : ifBreak(","); + + const printed = shouldUseConciseFormatting // + ? fill( + print.join( + "items", + (item, next) => + isNextLineEmpty(item) + ? [",", hardline, hardline] + : hasComment(next, CF.Leading, (comment) => is_LineCommentNode(comment) || comment.placement === "ownLine") + ? [",", hardline] + : [",", line], + trailingComma + ) + ) + : print.map_join( + "items", + () => group(print()), + (item) => (isNextLineEmpty(item) ? [",", line, softline] : [",", line]), + trailingComma + ); + + return group([delims.left, indent([softline, printed]), printDanglingComments(node, true, DCM["items"]), softline, delims.right], { + shouldBreak, + id: groupId, + }); +} + +export function printObject(print: print, node: T): Doc { + if (hasSemiNoProperties(node)) { + return ";"; + } + if (!hasProperties(node)) { + return [" {", printDanglingCommentsForInline(node, DCM["properties"]) || emptyContent(node), "}"]; + } + + const firstProperty = node.properties[0]; + + const parent = getParentNode()!; + + const shouldBreak = is_StructPattern(node) + ? false + : is_UnionDeclaration(node) || + is_StructDeclaration(node) || + is_EnumMemberStructDeclaration(node) || + hasNewlineInRange(start(node), start(firstProperty)); + + const content = [ + " {", + indent([ + line, + ...print.join( + "properties", // + (node) => (isNextLineEmpty(node) ? [",", hardline, hardline] : [",", line]), + (node) => (is_StructSpread(node) ? "" : ifBreak(",")) + ), + ]), + line, + "}", + ]; + + const grandparent = getGrandParentNode(); + if ( + grandparent && + (is_FunctionDeclaration(grandparent) || is_ClosureFunctionExpression(grandparent)) && + getParameters(grandparent)[0] === parent + ) { + return content; + } + + if ( + (is_StructLiteral(node) && is_ReassignmentNode(parent) && parent.left === node) || + (is_StructPattern(node) && + (is_VariableDeclarationNode(parent) || is_MatchExpressionCase(parent) || is_FunctionParameterDeclaration(parent)) && + parent.pattern === node) + ) { + return content; + } + + return group(content, { shouldBreak }); +} + +export function printEnumBody(print: print, node: T): Doc { + const printed = print.join("members", (member) => [",", maybeEmptyLine(member)], ","); + return [ + " {", + printed.length === 0 + ? printDanglingCommentsForInline(node, DCM["members"]) || emptyContent(node) + : [indent([hardline, ...printed]), hardline], + "}", + ]; +} diff --git a/frontend/src/utils/prettier/plugins/rust/format/external.ts b/frontend/src/utils/prettier/plugins/rust/format/external.ts new file mode 100644 index 0000000..3e84295 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/format/external.ts @@ -0,0 +1,126 @@ +import { Attribute, AttributeOrDocComment, Comment, DocCommentAttribute, LocArray, MemberExpression, Node, SourceFile } from "jinx-rust"; +import { PickProps } from "jinx-rust/utils"; +import type { Doc, ParserOptions, Printer } from "prettier"; +import { doc } from "prettier"; +import { AssertTypesEq } from "../utils/common"; + +export type { Doc, ParserOptions, Plugin, Printer } from "prettier"; + + +export const { + join, + line, + softline, + hardline, + literalline, + group, + conditionalGroup, + fill, + lineSuffix, + lineSuffixBoundary, + cursor, + breakParent, + ifBreak, + trim, + indent, + indentIfBreak, + align, + addAlignmentToDoc, + markAsRoot, + dedentToRoot, + dedent, + hardlineWithoutBreakParent, + literallineWithoutBreakParent, + label, +} = doc.builders; + +export const { + willBreak, + traverseDoc, + findInDoc, + mapDoc, + removeLines, + stripTrailingHardline, +} = doc.utils; + +// Fallback implementations for removed utils in prettier 3 +export const isConcat = (doc: any): boolean => Array.isArray(doc); +export const getDocParts = (doc: any): any[] => Array.isArray(doc) ? doc : [doc]; +export const propagateBreaks = (doc: any): any => doc; +export const normalizeParts = (parts: any[]): any[] => parts.flat(); +export const normalizeDoc = (doc: any): any => doc; +export const cleanDoc = (doc: any): any => doc; +export const canBreak = (doc: any): boolean => { + if (!doc) return false; + if (typeof doc === 'string') return false; + if (Array.isArray(doc)) return doc.some(canBreak); + if (doc.type === 'group' || doc.type === 'fill') return true; + return willBreak(doc); +}; + +export const Symbol_comments = Symbol.for("comments"); + +export interface CustomOptions extends ParserOptions { + [Symbol_comments]: AnyComment[]; + rsParsedFile: SourceFile; + commentSpans: Map; + printer: Printer; + cursorNode: any; + + comments: Comment[]; + danglingAttributes: AttributeOrDocComment[]; + actuallyMethodNodes: WeakSet; +} + +export type NodeWithComments = T & { comments: AnyComment[] }; +export interface MutatedComment extends Comment, PrettierCommentInfo {} +export interface MutatedAttribute extends Attribute, PrettierCommentInfo {} +export interface MutatedDocComment extends DocCommentAttribute, PrettierCommentInfo {} +export type AnyComment = MutatedComment | MutatedAttribute | MutatedDocComment; + +type keyofDelimitedArrayProps = T extends never ? never : keyof PickProps">>; + +__DEV__: AssertTypesEq>(); + +export enum DCM { + "arguments" = "arguments", + "parameters" = "parameters", + "items" = "items", + "properties" = "properties", + "members" = "members", + "body" = "body", + "cases" = "cases", + "typeArguments" = "typeArguments", + "ltParameters" = "ltParameters", + "generics" = "generics", + "specifiers" = "specifiers", + "rules" = "rules", + "match" = "match", + "transform" = "transform", + "segments" = "segments", +} + +export interface PrettierCommentInfo { + trailing: boolean; + leading: boolean; + unignore: boolean; + printed: boolean; + placement: "ownLine" | "endOfLine" | "remaining"; + // nodeDescription?: any; + marker?: DCM; +} + +export interface AstPath { + stack: (Node | string | number)[]; + callParent(callback: (path: this) => R, count?: number): R; + getName(): PropertyKey | null; + getValue(): T; + getNode(count?: number): T | null; + getParentNode(count?: number): T | null; + + match(...predicates: ((node: Node, name: string | null, number: number | null) => boolean)[]): boolean; + + call(callback: (path: AstPath, index: number, value: any) => R, ...props: (string | number)[]): R; + each(callback: (path: AstPath, index: number, value: any) => void, ...props: (string | number)[]): void; + map(callback: (path: AstPath, index: number, value: any) => R, ...props: (string | number)[]): R[]; +} diff --git a/frontend/src/utils/prettier/plugins/rust/format/plugin.ts b/frontend/src/utils/prettier/plugins/rust/format/plugin.ts new file mode 100644 index 0000000..41e40bd --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/format/plugin.ts @@ -0,0 +1,367 @@ +import { AttributeOrComment, IfBlockExpression, Node, Program, rs } from "jinx-rust"; +import { + ArrayProps, + BoolProps, + NodeProps, + end, + hasAttributes, + insertNodes, + is_Attribute, + is_AttributeOrComment, + is_BlockCommentKind, + is_BlockCommentNode, + is_Comment, + is_DocCommentAttribute, + is_ElseBlock, + is_LineCommentNode, + is_MacroInvocation, + is_MacroRule, + is_MissingNode, + is_Node, + is_PunctuationToken, + is_UnionPattern, + start, +} from "jinx-rust/utils"; +import { getCommentChildNodes, isTransformed, transform_ast } from "../transform"; +import { Narrow, assert, color, each, exit, iLast, is_array, map_tagged_template, print_string } from "../utils/common"; +import { + CF, + escapeComments, + getComments, + handleEndOfLineComment, + handleOwnLineComment, + handleRemainingComment, + hasBreaklineAfter, + hasComment, + isDangling, + isPrettierIgnoreAttribute, + setDidPrintComment, + withComments, +} from "./comments"; +import { withCheckContext } from "./complexity"; +import { isNoopExpressionStatement, maybeEmptyLine } from "./core"; +import { AstPath, CustomOptions, Doc, Plugin, Symbol_comments, group, hardline, indent, line, softline, ParserOptions } from "./external"; +import { printer } from "./printer"; +import { needsInnerParens, needsOuterSoftbreakParens, shouldPrintOuterAttributesAbove } from "./styling"; + +export function is_printing_macro() { + return getContext().path.stack.some((node) => is_Node(node) && (is_MacroInvocation(node) || is_Attribute(node))); +} + +export function assertPathAtNode(name: string, node: Node, ...ctx: any[]) { + __DEV__: if (getNode() !== node) + exit(`Attempted to call ${name}() in wrong prettier path context`, { asserted: node, actual: getNode() }, ...ctx); +} + +export function f(...args: [strings: TemplateStringsArray, ...values: Doc[]]) { + let cancel = false; + const res = map_tagged_template(args, (doc) => { + cancel ||= !doc || (is_array(doc) && doc.length === 0); + return doc; + }); + return cancel ? "" : res; +} + +export function sg_single(s: TemplateStringsArray, v_0: Doc) { + return group([s[0], indent([softline, v_0]), softline, s[1]]); +} +export function sg_duo(s: TemplateStringsArray, v_0: Doc, v_1: Doc) { + return group([s[0], indent([softline, v_0, s[1], line, v_1]), softline, s[2]]); +} + +let ctx: { + path: AstPath; + options: CustomOptions; + print: (path?: AstPath | string | [] | undefined, args?: any) => Doc; + args: any; +}; + +export const getNode = () => ctx.path.stack[ctx.path.stack.length - 1] as Node; +export const stackIncludes = (x: Node | string | number) => ctx.path.stack.includes(x); +export const getContext = () => ctx; +export const getOptions = () => ctx.options; +export const getProgram = () => ctx.options.rsParsedFile.program; +export const getAllComments = () => ctx.options[Symbol_comments]; +export const getParentNode = (child?: Node) => { + __DEV__: if (child) assertPathAtNode("getParentNode", child); + return ctx.path.getParentNode(); +}; +export const getGrandParentNode = () => ctx.path.getParentNode(1) as Node; +export const getPrintFn = (forNode?: T | undefined): print => { + __DEV__: if (forNode) assertPathAtNode("getPrintFn", forNode); + return print as print; +}; + +const get = (property: keyof any) => getNode()[property]; +const has = (property: keyof any) => !!get(property); + +export function pathCall & keyof T, R>(node: T, key: K, fn: (child: T[K]) => R): R { + return ctx.path.call(() => fn(getNode() as any), key as any); +} + +export function pathCallEach>( + node: T, + key: K, // @ts-expect-error + fn: (child: NonNullable[number], index: number) => void +) { + __DEV__: assertPathAtNode("", node); // @ts-expect-error + ctx.path.each((_, i) => fn(getNode() as any, i), key); +} + +export function pathCallAtParent(parent: T, fn: (parent: T) => R): R { + return ctx.path.callParent(() => { + __DEV__: assertPathAtNode("pathCallParent", parent); + return fn(parent); + }); +} +export function pathCallParentOf(child: Node, fn: (parent: T) => R): R { + __DEV__: assertPathAtNode("pathCallParentOf", child); + return ctx.path.callParent((p) => fn(getNode() as any)); +} + +export function pathCallTopMostIfBlockExpression(node: IfBlockExpression, fn: (node: IfBlockExpression) => R): R { + const parent = getParentNode(node)!; + return is_ElseBlock(node, parent) ? pathCallAtParent(parent, (parent) => pathCallTopMostIfBlockExpression(parent, fn)) : fn(node); +} + +function print(property?: any, args?: any): Doc | Doc[] { + if (!property) return ctx.print(undefined!, args); + if (Array.isArray(property)) return ctx.print(property as any, args); + const value = get(property); + return !!value ? (Array.isArray(value) ? ctx.path.map(ctx.print, property) : ctx.print(property, args)) : ""; +} + +namespace print { + export function b(property: string, res = `${property} `): Doc { + return has(property) ? res : ""; + } + export function map(property: string, mapItem?: MapFn): Doc[] { + return !has(property) ? [] : ctx.path.map(mapItem ? (p, i, a) => mapItem(a[i], i, a) : () => ctx.print(), property); + } + export function join(property: string, sep: SepFn | Doc, trailingSep: TrailingSepFn | Doc = ""): Doc[] { + return map_join(property, () => ctx.print(), sep, trailingSep); + } + export function map_join( + property: string, + mapFn: MapFn, + sep: SepFn | Doc, + sepTrailing: TrailingSepFn | Doc = "" + ) { + const sepFn = typeof sep === "function" ? sep : () => sep; + return map(property, (v, i, a) => [ + mapFn(v, i, a), + iLast(i, a as any) + ? typeof sepTrailing === "function" + ? sepTrailing(v) + : sepTrailing + : sepFn(v, a[i + 1], i === 0 ? undefined : a[i - 1]), + ]); + } +} + +// prettier-ignore +type SepFn = AK> = >(item: A[number], next_item: A[number], prev_item: A[number] | undefined) => Doc; +type MapFn = AK> = >(item: A[number], index: number, arr: A) => Doc; +type TrailingSepFn = AK> = >(item: A[number]) => Doc; +type AV = Extract, ReadonlyArray>; +type AK = keyof ArrayProps & keyof T; +// type AK = keyof PickProps & keyof T; + +export interface print { + (property?: [], args?: any): Doc; + (property?: [AK, number], args?: any): Doc; + (property?: AK, args?: any): Doc[]; + // (property?: T extends {rules:{nodeType:number}|{nodeType:number}[]} ? "rules" : never, args?: any): Doc[]; + (property?: keyof NodeProps & keyof T, args?: any): Doc; + b(property: keyof BoolProps, res?: string): Doc; + map>(property: K & keyof ArrayProps, mapFn?: MapFn): Doc[]; + join>(property: K, sep: SepFn | Doc, trailingSep?: TrailingSepFn | Doc): Doc[]; + map_join>(property: K, mapFn: MapFn, sep: SepFn | Doc, trailingSep?: TrailingSepFn | Doc): Doc[]; +} + +function genericPrint() { + return withCheckContext(() => { + const node = getNode(); + __DEV__: assert(node.nodeType in printer); + + let printed: Doc = hasPrettierIgnore(node) // + ? node.loc.getOwnText() + : printer[node.nodeType]!(print as any, node as never); + + const inner_parens = needsInnerParens(node); + + if (inner_parens) { + printed = group(["(", printed, ")"]); + } + + if (hasAttributes(node)) { + const print_above = shouldPrintOuterAttributesAbove(node); /* || node.attributes.length > 1 */ + printed = [ + ...print.join( + "attributes", + (attr) => + print_above + ? maybeEmptyLine(attr) + : is_LineCommentNode(attr) || (is_BlockCommentNode(attr) && hasBreaklineAfter(attr)) + ? hardline + : " ", + (attr) => + print_above && is_DocCommentAttribute(attr) + ? maybeEmptyLine(attr) + : print_above || is_LineCommentNode(attr) || (is_BlockCommentNode(attr) && hasBreaklineAfter(attr)) + ? hardline + : " " + ), + printed, + ]; + } + + printed = withComments( + node, + printed, + hasPrettierIgnore(node) || ((is_Attribute(node) || is_MacroInvocation(node)) && !isTransformed(node)) + ? escapeComments(0, (comment) => node.loc.ownContains(comment)) + : is_MacroRule(node) + ? escapeComments(0, (comment) => node.transform.loc.contains(comment)) + : is_UnionPattern(getParentNode() ?? ({ nodeType: 0 } as any)) + ? new Set(getComments(node, CF.Leading | CF.Trailing, (comment) => !isDangling(comment))) + : undefined + ); + + if (!inner_parens && needsOuterSoftbreakParens(node)) { + printed = [group(["(", indent([softline, printed]), softline, ")"])]; + } + + return printed; + }); + + function hasPrettierIgnore(node: Node) { + return ( + (node as any).prettierIgnore || + hasComment(node, CF.PrettierIgnore) || + (hasAttributes(node) && node.attributes.some(isPrettierIgnoreAttribute)) + ); + } +} + +export function canAttachComment(n: Node) { + return !is_Comment(n) && !isNoopExpressionStatement(n) && !is_MissingNode(n) && !is_PunctuationToken(n); +} + +export const plugin: Plugin = { + languages: [ + { + name: "Rust", + aliases: ["rs"], + parsers: ["jinx-rust"], + extensions: [".rs", ".rs.in"], + linguistLanguageId: 327, + vscodeLanguageIds: ["rust"], + tmScope: "source.rust", + aceMode: "rust", + codemirrorMode: "rust", + codemirrorMimeType: "text/x-rustsrc", + }, + ], + parsers: { + "jinx-rust": { + astFormat: "jinx-rust", + locStart: start, + locEnd: end, + parse(code: string, options: ParserOptions & Partial) { + const customOptions = options as CustomOptions; + ctx = { options: customOptions } as any; + + customOptions.rsParsedFile = rs.parseFile((customOptions.originalText = code), { filepath: customOptions.filepath }); + + customOptions.actuallyMethodNodes = new WeakSet(); + customOptions.danglingAttributes = []; + customOptions.comments = []; + + transform_ast(customOptions); + + const comments: AttributeOrComment[] = []; + insertNodes(comments, customOptions.comments); + insertNodes(comments, customOptions.danglingAttributes); + + // @ts-expect-error + customOptions.rsParsedFile.program.comments = comments; + + customOptions.commentSpans = new Map(comments.map((n) => [start(n), end(n)])); + + return customOptions.rsParsedFile.program; + }, + }, + }, + printers: { + "jinx-rust": { + preprocess: (node: Node) => (node as Program).loc?.src || node, + print(path, options, print, args) { + if (path.stack.length === 1) { + __DEV__: Narrow(options); + ctx = { path, options, print: print as any, args }; + try { + const printed = genericPrint(); + __DEV__: devEndCheck(printed); + return printed; + } finally { + ctx = undefined!; + } + } else if (args || ctx.args) { + const prev_args = ctx.args; + try { + ctx.args = args; + return genericPrint(); + } finally { + ctx.args = prev_args; + } + } else { + return genericPrint(); + } + }, + hasPrettierIgnore: () => false, + willPrintOwnComments: () => true, + isBlockComment: (node: Node): boolean => { + return is_AttributeOrComment(node) && is_BlockCommentKind(node as any); + }, + canAttachComment: canAttachComment, + getCommentChildNodes: getCommentChildNodes, + printComment: genericPrint, + handleComments: { + // @ts-expect-error + avoidAstMutation: true, + ownLine: handleOwnLineComment, + endOfLine: handleEndOfLineComment, + remaining: handleRemainingComment, + }, + }, + }, + options: {}, + defaultOptions: { + // default prettier (2) -> rustfmt (4) + tabWidth: 4, + // default prettier (80) -> rustfmt (100) + printWidth: 100, + }, +}; + +function devEndCheck(printed: Doc) { + let first = false; + const comments = getAllComments(); + each(comments, (comment, index) => { + if (!comment.printed) { + if (!first) (first = true), console.log(color.red(`Unprinted comments:`)); + const len = 40; + const msg = + color.magenta( + (comment.marker ? `Dangling "${comment.marker}" ` : "") + + (is_Attribute(comment) ? "Attribute " : is_DocCommentAttribute(comment) ? "DocCommentAttribute" : "Comment") + + ` ${index}/${comments.length}` + + color.yellow(` ${print_string(comment.loc.sliceText(0, len))}${comment.loc.len() > len ? " ..." : ""}`) + ) + color.grey(`\n at ${comment.loc.url()}`); + if (globalThis.TESTS_FORMAT_DEV) exit(msg); + else console.log(msg); + setDidPrintComment(comment); + } + }); +} diff --git a/frontend/src/utils/prettier/plugins/rust/format/printer.ts b/frontend/src/utils/prettier/plugins/rust/format/printer.ts new file mode 100644 index 0000000..98ea887 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/format/printer.ts @@ -0,0 +1,719 @@ +import { DelimKind, Node, NodeType, NTMap } from "jinx-rust"; +import { + getDelimChars, + hasSuffix, + is_ArrayOrTupleLiteral, + is_BlockExpression, + is_ClosureFunctionExpression, + is_Identifier, + is_IfBlockExpression, + is_LiteralNumberLike, + is_StructLiteral, + start, +} from "jinx-rust/utils"; +import { + BlockLikeMacroInvocation, + CallLikeMacroInvocation, + is_BlockLikeMacroInvocation, + is_CallLikeMacroInvocation, + isTransformed, +} from "../transform"; +import { exit } from "../utils/common"; +import { hasComment, print_comment } from "./comments"; +import { isSimpleType } from "./complexity"; +import { + adjustClause, + parenthesize_if_break, + printAnnotatedPattern, + printArrayLike, + printArrowFunction, + printAssignment, + printBinaryishExpression, + printBlockBody, + printBodyOrCases, + printCallArguments, + printCallExpression, + printCondition, + printDanglingCommentsForInline, + printDeclarationTypeBounds, + printEnumBody, + printFlowControlExpression, + printGenerics_x_whereBounds, + printIfBlock, + printIfBlockCondition, + printImplTraitForType, + printLtBounds, + printLtParameters, + printMacroRules, + printMaybeBlockBody, + printMemberExpression, + printNumber, + printObject, + printParametersAndReturnType, + printRuleMatch, + printRuleTransform, + printTypeAnnotation, + printTypeArguments, + printTypeBounds, + printUnaryExpression, + printUnionPattern, +} from "./core"; +import { DCM, Doc, group, hardline, ifBreak, indent, join, line, softline, willBreak } from "./external"; +import { f, getOptions, getParentNode, pathCall, sg_duo, sg_single, type print } from "./plugin"; +import { needsParens, stmtNeedsSemi } from "./styling"; + +type nPrint = (print: print, node: T) => Doc | never; + +export const printer: { [K in NodeType]: nPrint> } = { + [NodeType.MissingNode](print, node) { + return ""; + }, + [NodeType.SourceFile](print, node) { + return [ + print.b("UTF8BOM", "\uFEFF"), // + print("shebang"), + print("program"), + ]; + }, + [NodeType.Shebang](print, node) { + return [`#!${node.value}`, hardline]; + }, + [NodeType.Program](print, node) { + return printBodyOrCases(print, node); + }, + [NodeType.Snippet](print, node) { + exit.never(); + }, + [NodeType.Identifier](print, node) { + return node.name; + }, + [NodeType.Index](print, node) { + return node.name; + }, + [NodeType.LbIdentifier](print, node) { + return node.name; + }, + [NodeType.McIdentifier](print, node) { + return node.name; + }, + [NodeType.LtIdentifier](print, node) { + return node.name; + }, + [NodeType.PunctuationToken](print, node) { + return node.token; + }, + [NodeType.DelimGroup](print, node) { + return node.loc.getOwnText(); + }, + [NodeType.Literal](print, node) { + let { value } = node; + if (is_LiteralNumberLike(node)) value = printNumber(value); + return hasSuffix(node) ? [value, print("suffix")] : value; + }, + [NodeType.ItemPath](print, node) { + return [print("namespace"), "::", print("segment")]; + }, + [NodeType.ExpressionPath](print, node) { + return [print("namespace"), "::", print("segment")]; + }, + [NodeType.TypePath](print, node) { + return [print("namespace"), "::", print("segment")]; + }, + [NodeType.Comment](print, node) { + return print_comment(node); + }, + [NodeType.DocCommentAttribute](print, node) { + return print_comment(node); + }, + [NodeType.Attribute](print, node) { + return [ + node.inner ? "#![" : "#[", + isTransformed(node) + ? [print("segments"), printDanglingCommentsForInline(node)] // + : node.segments.loc.sliceText(1, -1).trim(), + "]", + ]; + }, + [NodeType.MacroInvocation](print, node) { + const hasCurlyBrackets = node.segments.dk === DelimKind["{}"]; + const delim = getDelimChars(node.segments); + if (node.segments.length === 0) { + return [print("callee"), "!", hasCurlyBrackets ? " " : "", delim.left, printDanglingCommentsForInline(node), delim.right]; + } + if (isTransformed(node)) { + if (is_CallLikeMacroInvocation(node)) { + return [print("callee"), "!", printCallArguments(print as print, node)]; + } + if (is_BlockLikeMacroInvocation(node)) { + return [print("callee"), "!", " ", printBlockBody(print as print, node)]; + } + } + let content = node.segments.loc.sliceText(1, -1); + if (content.trim().length === 0) { + content = ""; + } else if (!content.includes("\n")) { + content = content.trim(); + if (hasCurlyBrackets) content = " " + content + " "; + } + return [print("callee"), "!", hasCurlyBrackets ? " " : "", delim.left, content, delim.right]; + }, + [NodeType.MacroRulesDeclaration](print, node) { + return ["macro_rules! ", print("id"), printMacroRules(print, node)]; + }, + [NodeType.MacroRuleDeclaration](print, node) { + return [printRuleMatch(print, node), " => ", printRuleTransform(print, node), ";"]; + }, + [NodeType.MacroDeclaration](print, node) { + return [print("pub"), "macro ", print("id"), printMacroRules(print, node)]; + }, + [NodeType.MacroInlineRuleDeclaration](print, node) { + return [printRuleMatch(print, node), " ", printRuleTransform(print, node)]; + }, + [NodeType.MacroGroup](print, node) { + return node.loc.getOwnText(); + }, + [NodeType.MacroParameterDeclaration](print, node) { + return [print("id"), ":", print("ty")]; + }, + [NodeType.PubSpecifier](print, node) { + if (!node.location) return "pub "; + if (is_Identifier(node.location)) { + switch (node.location.name) { + case "crate": + if (start(node) === start(node.location)) { + return "crate "; + } else { + return ["pub(", print("location"), ") "]; + } + case "self": + case "super": + return ["pub(", print("location"), ") "]; + } + } + return ["pub(in ", print("location"), ") "]; + }, + [NodeType.ExternSpecifier](print, node) { + return ["extern ", f`${print("abi")} `]; + }, + [NodeType.ExpressionStatement](print, node) { + return [print("expression"), stmtNeedsSemi(node) ? ";" : ""]; + }, + [NodeType.UseStatement](print, node) { + return [print("pub"), "use ", print("import"), ";"]; + }, + [NodeType.DestructuredImport](print, node) { + if (node.specifiers.length === 0) return [print("source"), "::{", printDanglingCommentsForInline(node, DCM["specifiers"]), "}"]; + let space = true; + __DEV__: if (globalThis.TESTS_FORMAT_DEV) space = false; + return [ + print("source"), + group([ + "::{", + indent([space ? line : softline, join([",", line], print("specifiers")), ifBreak(",")]), + space ? line : softline, + "}", + ]), + ]; + }, + [NodeType.AmbientImport](print, node) { + return f`${print("source")}::*` || "*"; + }, + [NodeType.AnonymousImport](print, node) { + return [print("source"), " as ", "_"]; + }, + [NodeType.NamedImport](print, node) { + return [print("source"), f` as ${print("local")}`]; + }, + [NodeType.ExternCrateStatement](print, node) { + return [print("pub"), "extern crate ", print("import"), ";"]; + }, + [NodeType.TypeAliasDeclaration](print, node) { + return [ + print("pub"), + "type", + printAssignment( + printGenerics_x_whereBounds(print, node, printDeclarationTypeBounds(print, node, ":")), // + " =", + "typeExpression" + ), + ";", + ]; + }, + [NodeType.LetVariableDeclaration](print, node) { + return [ + "let ", + printAssignment( + printAnnotatedPattern(print, node), // + " =", + "expression" + ), + f` else ${print("else")}`, + ";", + ]; + }, + [NodeType.ConstVariableDeclaration](print, node) { + return [ + print("pub"), + "const ", + printAssignment( + printAnnotatedPattern(print, node), // + " =", + "expression" + ), + ";", + ]; + }, + [NodeType.StaticVariableDeclaration](print, node) { + return [ + print("pub"), + "static ", + printAssignment( + printAnnotatedPattern(print, node), // + " =", + "expression" + ), + ";", + ]; + }, + [NodeType.ModuleDeclaration](print, node) { + return [ + print("pub"), // + print.b("unsafe"), + "mod ", + print("id"), + printMaybeBlockBody(print, node), + ]; + }, + [NodeType.ExternBlockDeclaration](print, node) { + return [ + print("pub"), // + print.b("unsafe"), + "extern ", + f`${print("abi")} `, + printBlockBody(print, node), + ]; + }, + [NodeType.FunctionDeclaration](print, node) { + return [ + print("pub"), + print.b("const"), + print.b("async"), + print.b("unsafe"), + print("extern"), + "fn", + printGenerics_x_whereBounds(print, node, printParametersAndReturnType(node)), + printMaybeBlockBody(print, node), + ]; + }, + [NodeType.FunctionSelfParameterDeclaration](print, node) { + return group([print.b("ref", "&"), f`${print("lt")} `, print.b("mut"), "self", printTypeAnnotation(print, node)]); + }, + [NodeType.FunctionParameterDeclaration](print, node) { + return group(printAnnotatedPattern(print, node)); + }, + [NodeType.FunctionSpread](print, node) { + return "..."; + }, + [NodeType.StructDeclaration](print, node) { + return [print("pub"), "struct", printGenerics_x_whereBounds(print, node, ""), printObject(print, node)]; + }, + [NodeType.StructPropertyDeclaration](print, node) { + return [print("pub"), print("id"), printTypeAnnotation(print, node)]; + }, + [NodeType.TupleStructDeclaration](print, node) { + return [print("pub"), "struct", printGenerics_x_whereBounds(print, node, printArrayLike(print, node)), ";"]; + }, + [NodeType.TupleStructItemDeclaration](print, node) { + return [print("pub"), print("typeAnnotation")]; + }, + [NodeType.UnionDeclaration](print, node) { + return [print("pub"), "union", printGenerics_x_whereBounds(print, node, ""), printObject(print, node)]; + }, + [NodeType.EnumDeclaration](print, node) { + return [print("pub"), "enum", printGenerics_x_whereBounds(print, node, ""), printEnumBody(print, node)]; + }, + [NodeType.EnumMemberDeclaration](print, node) { + return [ + print("pub"), + printAssignment( + print("id"), // + " =", + "value" + ), + ]; + }, + [NodeType.EnumMemberTupleDeclaration](print, node) { + return [ + print("pub"), + printAssignment( + [print("id"), printArrayLike(print, node)], // + " =", + "value" + ), + ]; + }, + [NodeType.EnumMemberStructDeclaration](print, node) { + return [ + print("pub"), + printAssignment( + [print("id"), printObject(print, node)], // + " =", + "value" + ), + ]; + }, + [NodeType.TraitDeclaration](print, node) { + return [ + print("pub"), + print.b("unsafe"), + "trait", + printGenerics_x_whereBounds(print, node, printDeclarationTypeBounds(print, node, ":")), + adjustClause(node, printBlockBody(print, node)), + ]; + }, + [NodeType.AutoTraitDeclaration](print, node) { + return [ + print("pub"), + print.b("unsafe"), + "auto trait ", + print("id"), + " ", + printBlockBody(print, node as any), // see "transform.ts" + ]; + }, + [NodeType.TraitAliasDeclaration](print, node) { + return [ + print("pub"), + print.b("unsafe"), + "trait", + printGenerics_x_whereBounds(print, node, printDeclarationTypeBounds(print, node, " =")), + ";", + ]; + }, + [NodeType.ImplDeclaration](print, node) { + return [ + print("pub"), + print.b("unsafe"), + "impl", + printGenerics_x_whereBounds(print, node, [print.b("const"), printImplTraitForType(print, node)]), + adjustClause(node, printBlockBody(print, node)), + ]; + }, + [NodeType.NegativeImplDeclaration](print, node) { + return [ + print("pub"), + "impl", + printGenerics_x_whereBounds(print, node, ["!", printImplTraitForType(print, node)]), + " ", + printBlockBody(print, node as any), // see "transform.ts" + ]; + }, + [NodeType.ExpressionTypeSelector](print, node) { + return group(["<", print("typeTarget"), f` as ${print("typeExpression")}`, ">"]); + }, + [NodeType.ExpressionTypeCast](print, node) { + return [print("typeCallee"), f`::${printTypeArguments(print, node)}`]; + }, + [NodeType.ExpressionAsTypeCast](print, node) { + return [print("expression"), " as ", print("typeExpression")]; + }, + [NodeType.ReturnExpression](print, node) { + return ["return", printFlowControlExpression(print, node)]; + }, + [NodeType.BreakExpression](print, node) { + return ["break", f` ${print("label")}`, printFlowControlExpression(print, node)]; + }, + [NodeType.ContinueExpression](print, node) { + return ["continue", f` ${print("label")}`]; + }, + [NodeType.YieldExpression](print, node) { + return ["yield", printFlowControlExpression(print, node)]; + }, + [NodeType.RangeLiteral](print, node) { + return [print("lower"), "..", print.b("last", "="), print("upper")]; + }, + [NodeType.CallExpression](print, node) { + return printCallExpression(print, node); + }, + [NodeType.MemberExpression](print, node) { + return printMemberExpression(print, node); + }, + [NodeType.AwaitExpression](print, node) { + return [print("expression"), ".await"]; + }, + [NodeType.UnwrapExpression](print, node) { + return [print("expression"), "?"]; + }, + [NodeType.ParenthesizedExpression](print, node) { + exit.never(); + const shouldHug = !hasComment(node.expression) && (is_ArrayOrTupleLiteral(node.expression) || is_StructLiteral(node.expression)); + if (shouldHug) return ["(", print("expression"), ")"]; + return group(["(", indent([softline, print("expression")]), softline, ")"]); + }, + [NodeType.MinusExpression](print, node) { + return printUnaryExpression("-", node); + }, + [NodeType.NotExpression](print, node) { + return printUnaryExpression("!", node); + }, + [NodeType.OrExpression](print, node) { + return printBinaryishExpression(print, node); + }, + [NodeType.AndExpression](print, node) { + return printBinaryishExpression(print, node); + }, + [NodeType.ReassignmentExpression](print, node) { + return printAssignment(print("left"), " =", "right"); + }, + [NodeType.UnassignedExpression](print, node) { + return "_"; + }, + [NodeType.OperationExpression](print, node) { + return printBinaryishExpression(print, node); + }, + [NodeType.ReassignmentOperationExpression](print, node) { + return printAssignment(print("left"), " " + node.kind, "right"); + }, + [NodeType.ComparisonExpression](print, node) { + return printBinaryishExpression(print, node); + }, + [NodeType.LetScrutinee](print, node) { + return ["let ", printAssignment(print("pattern"), " =", "expression")]; + }, + [NodeType.ClosureFunctionExpression](print, node) { + return printArrowFunction(print, node); + }, + [NodeType.ClosureFunctionParameterDeclaration](print, node) { + return group(printAnnotatedPattern(print, node)); + }, + [NodeType.BlockExpression](print, node) { + return [ + f`${print("label")}: `, + print.b("const"), + print.b("async"), + print.b("move"), + print.b("unsafe"), + printBlockBody(print, node), + ]; + }, + [NodeType.LoopBlockExpression](print, node) { + return [f`${print("label")}: `, "loop ", printBlockBody(print, node)]; + }, + [NodeType.WhileBlockExpression](print, node) { + return [f`${print("label")}: `, "while ", printCondition(print, node), printBlockBody(print, node)]; + }, + [NodeType.ForInBlockExpression](print, node) { + return [f`${print("label")}: `, "for ", print("pattern"), " in ", print("expression"), " ", printBlockBody(print, node)]; + }, + [NodeType.IfBlockExpression](print, node) { + return [f`${print("label")}: `, printIfBlock(print, node)]; + }, + [NodeType.TryBlockExpression](print, node) { + return [f`${print("label")}: `, "try ", printBlockBody(print, node)]; + }, + [NodeType.MatchExpression](print, node) { + const id = Symbol("match"); + const expr = print("expression"); + const needs_parens = pathCall(node, "expression", needsParens); + + let printed: Doc = [ + f`${print("label")}: `, + "match ", + needs_parens ? expr : group([indent([softline, expr]), softline], { id }), + needs_parens ? " " : !willBreak(expr) ? ifBreak("", " ", { groupId: id }) : "" /* ifBreak("", " ", { groupId: id }) */, + printBlockBody(print, node), + ]; + + const parent = getParentNode()!; + if (is_ClosureFunctionExpression(parent) && parent.expression === node) { + printed = parenthesize_if_break([indent([softline, printed]), softline]); + } + return printed; + }, + [NodeType.MatchExpressionCase](print, node) { + return group([ + group(print("pattern")), + " ", + printIfBlockCondition(print, node), + "=>", // + (is_BlockExpression(node.expression) || is_IfBlockExpression(node.expression)) && + !hasComment(node.expression, 0, (comment) => getOptions().danglingAttributes.includes(comment as any)) + ? [" ", print("expression")] + : group(indent([line, print("expression")])), + ]); + return printAssignment( + [print("pattern"), " ", printIfBlockCondition(print, node)], // + "=>", + "expression" + ); + return [print("pattern"), " ", printIfBlockCondition(print, node)]; + }, + [NodeType.StructLiteral](print, node) { + return [print("struct"), printObject(print, node)]; + }, + [NodeType.StructLiteralPropertyShorthand](print, node) { + return print("value"); + }, + [NodeType.StructLiteralProperty](print, node) { + return [print("key"), ": ", print("value")]; + }, + [NodeType.StructLiteralPropertySpread](print, node) { + return ["..", print("expression")]; + }, + [NodeType.StructLiteralRestUnassigned](print, node) { + return ".."; + }, + [NodeType.ArrayLiteral](print, node) { + return printArrayLike(print, node); + }, + [NodeType.SizedArrayLiteral](print, node) { + return sg_duo`[${print("initExpression")};${print("sizeExpression")}]`; + }, + [NodeType.TupleLiteral](print, node) { + return printArrayLike(print, node); + }, + [NodeType.ReferenceExpression](print, node) { + return printUnaryExpression(["&", print.b("mut")], node); + }, + [NodeType.RawReferenceExpression](print, node) { + return printUnaryExpression(`&raw ${node.kind} `, node); + }, + [NodeType.DereferenceExpression](print, node) { + return printUnaryExpression("*", node); + }, + [NodeType.BoxExpression](print, node) { + return printUnaryExpression("box ", node); + }, + [NodeType.UnionPattern](print, node) { + return printUnionPattern(print, node); + }, + [NodeType.ParenthesizedPattern](print, node) { + exit.never(); + return sg_single`(${print("pattern")})`; + }, + [NodeType.RestPattern](print, node) { + return ".."; + }, + [NodeType.WildcardPattern](print, node) { + return "_"; + }, + [NodeType.PatternVariableDeclaration](print, node) { + return [print.b("ref"), print.b("mut"), printAssignment(print("id"), " @", "pattern")]; + }, + [NodeType.StructPattern](print, node) { + return [print("struct"), printObject(print, node)]; + }, + [NodeType.StructPatternPropertyDestructured](print, node) { + return [print("key"), ": ", print("pattern")]; + }, + [NodeType.StructPatternPropertyShorthand](print, node) { + return [print.b("box"), print.b("ref"), print.b("mut"), print("id")]; + }, + [NodeType.TuplePattern](print, node) { + return [print("struct"), printArrayLike(print, node)]; + }, + [NodeType.ArrayPattern](print, node) { + return printArrayLike(print, node); + }, + [NodeType.ReferencePattern](print, node) { + return ["&", print.b("mut"), print("pattern")]; + }, + [NodeType.BoxPattern](print, node) { + return ["box ", print("pattern")]; + }, + [NodeType.MinusPattern](print, node) { + return ["-", print("pattern")]; + }, + [NodeType.RangePattern](print, node) { + return [print("lower"), "..", print.b("last", "="), print("upper")]; + }, + [NodeType.TypeCall](print, node) { + return [print("typeCallee"), printTypeArguments(print, node)]; + }, + [NodeType.TypeCallNamedArgument](print, node) { + return printAssignment(print("target"), " =", "typeExpression"); + }, + [NodeType.TypeCallNamedBound](print, node) { + return [print("typeTarget"), printTypeBounds(":", print, node)]; + }, + [NodeType.LtElided](print, node) { + return "'_"; + }, + [NodeType.LtStatic](print, node) { + return "'static"; + }, + [NodeType.TypeNever](print, node) { + return "!"; + }, + [NodeType.TypeInferred](print, node) { + return "_"; + }, + [NodeType.GenericTypeParameterDeclaration](print, node) { + return printAssignment( + [print("id"), printTypeBounds(":", print, node)], // + " =", + "typeDefault" + ); + }, + [NodeType.ConstTypeParameterDeclaration](print, node) { + return [ + "const ", + printAssignment( + [print("id"), printTypeAnnotation(print, node)], // + " =", + "typeDefault" + ), + ]; + }, + [NodeType.GenericLtParameterDeclaration](print, node) { + return [print("id"), printLtBounds(":", print, node)]; + }, + [NodeType.WhereTypeBoundDeclaration](print, node) { + return [printLtParameters(print, node), print("typeTarget"), printTypeBounds(":", print, node)]; + }, + [NodeType.WhereLtBoundDeclaration](print, node) { + return [print("ltTarget"), printLtBounds(":", print, node)]; + }, + [NodeType.TypeTraitBound](print, node) { + return [print.b("maybeConst", "~const "), print.b("optional", "?"), printLtParameters(print, node), print("typeExpression")]; + }, + [NodeType.TypeDynBounds](print, node) { + return printTypeBounds("dyn", print, node); + }, + [NodeType.TypeImplBounds](print, node) { + return printTypeBounds("impl", print, node); + }, + [NodeType.TypeFnPointer](print, node) { + return [printLtParameters(print, node), print.b("unsafe"), print("extern"), "fn", printParametersAndReturnType(node)]; + }, + [NodeType.TypeFnPointerParameter](print, node) { + return [f`${print("id")}: `, print("typeAnnotation")]; + }, + [NodeType.TypeFunction](print, node) { + return [print("callee"), printParametersAndReturnType(node)]; + }, + [NodeType.TypeTuple](print, node) { + return printArrayLike(print, node); + }, + [NodeType.TypeSizedArray](print, node) { + return sg_duo`[${print("typeExpression")};${print("sizeExpression")}]`; + if (isSimpleType(node)) return ["[", print("typeExpression"), "; ", print("sizeExpression"), "]"]; + }, + [NodeType.TypeSlice](print, node) { + if (isSimpleType(node)) return ["[", print("typeExpression"), "]"]; + return sg_single`[${print("typeExpression")}]`; + }, + [NodeType.TypeReference](print, node) { + return ["&", f`${print("lt")} `, print.b("mut"), print("typeExpression")]; + }, + [NodeType.TypeDereferenceConst](print, node) { + return ["*const ", print("typeExpression")]; + }, + [NodeType.TypeDereferenceMut](print, node) { + return ["*mut ", print("typeExpression")]; + }, + [NodeType.TypeParenthesized](print, node) { + exit.never(); + return sg_single`(${print("typeExpression")})`; + }, +}; diff --git a/frontend/src/utils/prettier/plugins/rust/format/styling.ts b/frontend/src/utils/prettier/plugins/rust/format/styling.ts new file mode 100644 index 0000000..c462ee7 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/format/styling.ts @@ -0,0 +1,645 @@ +import { + ClosureFunctionExpression, + ComparisonExpression, + ConditionExpression, + EnumDeclaration, + EnumMemberStructDeclaration, + ExpressionAsTypeCast, + ExpressionNode, + ExpressionStatement, + ExpressionWithBody, + LeftRightExpression, + LogicalExpression, + MacroDeclaration, + MacroRulesDeclaration, + MissingNode, + Node, + NodeType, + NodeWithBody, + NodeWithBodyOrCases, + OperationExpression, + PRCD, + StructDeclaration, + StructLiteral, + StructPattern, + TK, + UnionDeclaration, +} from "jinx-rust"; +import { + can_have_OuterAttributes, + getAstPath, + getPrecedence, + hasAttributes, + hasBody, + hasCondition, + hasItems, + hasLetScrutineeCondition, + hasOuterAttributes, + hasProperties, + is_Attribute, + is_AttributeOrDocComment, + is_AwaitExpression, + is_BitwiseOperator, + is_CallExpression, + is_ClosureFunctionExpression, + is_ComparisonExpression, + is_DocCommentAttribute, + is_ElseBlock, + is_EnumMemberDeclaration, + is_EqualityOperator, + is_ExpressionAsTypeCast, + is_ExpressionStatement, + is_ExpressionWithBody, + is_ExpressionWithBodyOrCases, + is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation, + is_FlowControlExpression, + is_FlowControlMaybeValueExpression, + is_ForInBlockExpression, + is_Identifier, + is_IfBlockExpression, + is_ImplicitReturnAbleNode, + is_LeftRightExpression, + is_LetScrutinee, + is_Literal, + is_LiteralNumberLike, + is_LogicalExpression, + is_LoopBlockExpression, + is_MatchExpression, + is_MatchExpressionCase, + is_MemberExpression, + is_NodeWithBodyNoBody, + is_NodeWithMaybePatternNoUnionBody, + is_OperationExpression, + is_ParenthesizedNode, + is_PatternVariableDeclaration, + is_PostfixExpression, + is_RangeLiteral, + is_ReassignmentNode, + is_ReturnExpression, + is_StatementNode, + is_StructLiteral, + is_StructLiteralProperty, + is_StructPatternProperty, + is_StructPropertyDeclaration, + is_TypeBoundsStandaloneNode, + is_TypeFunctionNode, + is_TypeTraitBound, + is_UnaryExpression, + is_UnaryType, + is_UnionPattern, + is_WhileBlockExpression, + is_YieldExpression, + is_bitshiftOperator, + is_multiplicativeOperator, +} from "jinx-rust/utils"; +import { BlockLikeMacroInvocation, is_BlockLikeMacroInvocation, is_CallExpression_or_CallLikeMacroInvocation } from "../transform"; +import { exit, last_of } from "../utils/common"; +import { CF, hasBreaklineAfter, hasComment } from "./comments"; +import { flowControlExpressionNeedsOuterParens } from "./core"; +import { Doc, hardline, softline, willBreak } from "./external"; +import { + assertPathAtNode, + getContext, + getGrandParentNode, + getNode, + getOptions, + getParentNode, + getPrintFn, + is_printing_macro, + pathCallAtParent, + pathCallParentOf, + stackIncludes, +} from "./plugin"; + +export function needsOuterSoftbreakParens(node: Node) { + const parent = getParentNode(node); + + if (!parent) return false; + + if (is_ExpressionAsTypeCast(node)) { + return precedenceNeedsParens(node, parent); + } + + if ( + is_FlowControlMaybeValueExpression(parent) && // + parent.expression === node && + flowControlExpressionNeedsOuterParens(parent) + ) { + return true; + } + if ( + is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(node) && + (false || + (is_MemberExpression(parent) && parent.expression === node) || + (is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(parent) && !is_ElseBlock(node, parent))) + ) { + return true; + } + + if (is_UnionPattern(node) && is_NodeWithMaybePatternNoUnionBody(parent)) { + return true; + } + + if (hasComment(node)) { + if (is_UnaryExpression(parent)) { + return true; + } + + if (hasComment(node, CF.Line)) { + if (is_ReturnExpression(parent) || (is_YieldExpression(parent) && parent.expression === node)) { + return true; + } + } + + if ( + hasComment(node, CF.Leading, (comment) => is_Attribute(comment) && !comment.inner) && + !can_have_OuterAttributes(node, parent, true) + ) { + return true; + } + } + + return false; +} + +export function needsInnerParens(node: Node) { + if (needsOuterSoftbreakParens(node)) { + return false; + } + + const parent = getParentNode(node); + + if (!parent) { + return false; + } + + if (is_Identifier(node)) { + return false; + } + + if (is_Literal(node)) { + return is_LiteralNumberLike(node) && is_MemberExpression(parent) && node === parent.expression; + } + + if (is_CallExpression(parent) && parent.callee === node && is_MemberExpression(node)) { + return !getOptions().actuallyMethodNodes.has(node); + } + + if (is_ReassignmentNode(node)) { + if (is_printing_macro()) { + return false; + } + + if (is_ClosureFunctionExpression(parent) && node === parent.expression) { + return true; + } + + if (is_ExpressionStatement(parent)) { + return is_StructLiteral(node.left); + } + + if (is_ReassignmentNode(parent)) { + return false; + } + + return true; + } + + if (is_ParenthesizedNode(parent)) { + return false; + } + + if (is_ExpressionStatement(parent)) { + return false; + } + + if (is_RangeLiteral(node)) { + return ( + is_ExpressionAsTypeCast(parent) || + is_LogicalExpression(parent) || + is_UnaryExpression(parent) || + is_PostfixExpression(parent) || + (is_MemberExpression(parent) && node === parent.expression) || + (is_CallExpression(parent) && node === parent.callee) || + is_OperationExpression(parent) || + is_ComparisonExpression(parent) + ); + } + + if (is_LetScrutinee(parent) && is_LogicalExpression(node) && parent.expression === (node as any)) { + return true; + } + + if (is_UnaryExpression(node)) { + switch (parent.nodeType) { + case NodeType.MemberExpression: + case NodeType.AwaitExpression: + return node === parent.expression; + case NodeType.CallExpression: + return node === parent.callee; + default: + return false; + } + } + + if (is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(node)) { + if (is_ExpressionWithBodyOrCases(parent)) { + return !is_ElseBlock(node, parent); + } + if (is_LetScrutinee(parent) && parent.expression === node && is_ExpressionWithBodyOrCases(getGrandParentNode())) { + return true; + } + return ( + is_ExpressionAsTypeCast(parent) || + is_LogicalExpression(parent) || + is_UnaryExpression(parent) || + is_PostfixExpression(parent) || + (is_MemberExpression(parent) && node === parent.expression) || + (is_CallExpression(parent) && node === parent.callee) || + is_OperationExpression(parent) || + is_ComparisonExpression(parent) || + is_RangeLiteral(parent) + ); + } + + if (is_StructLiteral(node)) { + if (is_ExpressionWithBodyOrCases(parent)) { + return true; + } + + if (is_LetScrutinee(parent) && parent.expression === node && is_ExpressionWithBodyOrCases(getGrandParentNode())) { + return true; + } + if (is_UnaryExpression(parent) || is_PostfixExpression(parent) || is_MemberExpression(parent)) { + return parent.expression === node; + } + if (is_CallExpression(parent)) { + return parent.callee === node; + } + } + + if (is_LogicalExpression(node) || is_OperationExpression(node) || is_ComparisonExpression(node) || is_ClosureFunctionExpression(node)) { + return precedenceNeedsParens(node, parent); + } + + if (is_TypeFunctionNode(node)) { + const gp = getGrandParentNode(); + if (node.returnType && is_TypeTraitBound(parent) && is_TypeBoundsStandaloneNode(gp) && last_of(gp.typeBounds) !== parent) { + return true; + } + } + + if (is_TypeBoundsStandaloneNode(node)) { + return ( + (is_UnaryType(parent) && node.typeBounds.length > 1) || + is_TypeBoundsStandaloneNode(parent) || + is_TypeTraitBound(parent) || + (is_TypeFunctionNode(parent) && parent.returnType === node) + ); + } + + if (is_PatternVariableDeclaration(parent)) { + return is_UnionPattern(node); + } + + return false; +} + +function precedenceNeedsParens(node: LeftRightExpression | ClosureFunctionExpression | ExpressionAsTypeCast, parent: Node) { + if (is_UnaryExpression(parent) || is_PostfixExpression(parent)) return true; + if (is_ReassignmentNode(parent)) return parent.left === node; + if (is_MemberExpression(parent)) return parent.expression === node; + if (is_CallExpression(parent)) return parent.callee === node; + if (is_ExpressionAsTypeCast(parent)) return !is_ExpressionAsTypeCast(node); + if (is_LogicalExpression(parent)) return is_LogicalExpression(node) ? parent.nodeType !== node.nodeType : evalPrecedence(node, parent); + if (is_OperationExpression(parent) || is_ComparisonExpression(parent)) return evalPrecedence(node, parent); + return false; + function evalPrecedence( + child: LeftRightExpression | ClosureFunctionExpression | ExpressionAsTypeCast, + parent: ComparisonExpression | OperationExpression | LogicalExpression + ) { + if (is_ExpressionAsTypeCast(child) || is_ClosureFunctionExpression(child)) { + return true; + } + function getPrec(node, bool) { + // if (is_EqualityOperator(node.tk)) { + // return 11.3; + // } + // if (is_LargerLesserOperator(node.tk)) { + // return 11.6; + // } + return getPrecedence(node, bool); + } + + const childPRCD = getPrec(child, is_insideScrutinee(child)); + const parentPRCD = getPrec(parent, is_insideScrutinee(parent)); + + if (parentPRCD > childPRCD) { + return true; + } + + if (parentPRCD === childPRCD && parent.right === child) { + return true; + } + + if (parentPRCD === childPRCD && !shouldFlatten(parent, child)) { + return true; + } + + if (parentPRCD < childPRCD && child.tk === TK["%"]) { + return parentPRCD === PRCD["+-"]; + } + + if (is_BitwiseOperator(parent.tk) || (is_BitwiseOperator(child.tk) && is_EqualityOperator(parent.tk))) { + return true; + } + + return false; + } +} + +export function shouldFlatten(parent: ExpressionNode | ConditionExpression, node: ExpressionNode | ConditionExpression) { + if (getPrecedence(node, is_insideScrutinee(node)) !== getPrecedence(parent, is_insideScrutinee(parent))) return false; + if (is_ComparisonExpression(parent) && is_ComparisonExpression(node)) return false; + if (is_OperationExpression(parent) && is_OperationExpression(node)) { + if ( + (node.tk === TK["%"] && is_multiplicativeOperator(parent.tk)) || + (parent.tk === TK["%"] && is_multiplicativeOperator(node.tk)) || + (node.tk !== parent.tk && is_multiplicativeOperator(node.tk) && is_multiplicativeOperator(parent.tk)) || + (is_bitshiftOperator(node.tk) && is_bitshiftOperator(parent.tk)) + ) + return false; + } + return true; +} + +export function needsParens(node: Node) { + return needsOuterSoftbreakParens(node) || needsInnerParens(node); +} + +export function stmtNeedsSemi(stmt: ExpressionStatement, disregardExprType = false) { + return pathCallParentOf(stmt, (parent) => needsSemi(parent as any, stmt, disregardExprType)); +} + +const NoNode = { nodeType: 0 } as MissingNode; + +export function needsSemi(parent: NodeWithBody, stmt: ExpressionStatement, disregardExprType = false) { + const expr = disregardExprType ? NoNode : stmt.expression!; + const hadSemi = !disregardExprType && stmt.semi; + + return ( + !!expr && + (forcePreserveSemi() + ? true + : shouldNeverSemi() + ? false + : shouldPreserveSemi() + ? hadSemi || shouldAlwaysSemi() || canAutoCompleteSemi() + : true) + ); + + function forcePreserveSemi() { + /** Rust Compiler bug (preserve optional semicolon) */ + // rust-lang/rust#70844 https://github.com/rust-lang/rust/issues/70844 + // issue#22 https://github.com/jinxdash/prettier-plugin-rust/issues/22 + return ( + hadSemi && + stmt === last_of(parent.body!) && + ((is_IfBlockExpression(expr) && + hasLetScrutineeCondition(expr) && + !(is_LetScrutinee(expr.condition) && is_Identifier(expr.condition.expression))) || + (is_MatchExpression(expr) && !is_Identifier(expr.expression))) + ); + } + function shouldNeverSemi() { + return is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(expr); + } + function shouldPreserveSemi() { + return stmt === last_of(parent.body!) && (is_ImplicitReturnAbleNode(parent) || is_BlockLikeMacroInvocation(parent)); + } + function shouldAlwaysSemi() { + return is_FlowControlExpression(expr) || is_ReassignmentNode(expr); + } + function canAutoCompleteSemi() { + return withPathAt(parent, function checkParent(child: NodeWithBodyOrCases): boolean { + return pathCallParentOf(child, (parent) => { + if (is_IfBlockExpression(parent) && parent.else === child) { + // if ... { ... } else if { ... } ... + // ^ ------------------------------- parent + // ^ ----------- child + return checkParent(parent); + } + if (is_ExpressionStatement(parent)) { + // { .... { ... } ... } + // ^ -----------------^ grandparent + // ^ --- ^ ExpressionStatement + if (hasOuterAttributes(parent)) return false; + return stmtNeedsSemi(parent, true); + } + if (is_MatchExpressionCase(parent) && parent.expression === child) { + return pathCallParentOf(parent, checkParent); + } + return false; + }); + }); + } +} + +export function canInlineBlockBody(node: NodeWithBodyOrCases | BlockLikeMacroInvocation): boolean { + if (!is_ExpressionWithBody(node)) { + return false; + } + const body = node.body; + + if (body.length === 0) { + return canInlineInlineable(node); + } + + if (body.length === 1) { + const stmt = body[0]; + if (is_AttributeOrDocComment(stmt)) { + return true; + } + if (is_ExpressionStatement(stmt) && !needsSemi(node, stmt)) { + /** + * parent ( ExpressionStatement | StructLiteralProperty | LetVariableDeclaration | ... ) + * ... + * node { + * expr + * } + * ... + * + * + * Q: Can you inline "node { expr }" ? + */ + const expr = stmt.expression!; + + if ( + is_FlowControlExpression(expr) || // + is_ClosureFunctionExpression(expr) || + is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(expr) + ) { + return false; + } + + return canInlineInlineable(node); + } + } + return false; +} + +// function q(node: ExpressionWithBody) { +// pathCallTopMostIfBlockExpression(node, (node) => {}); +// } + +function canInlineInlineable(node: ExpressionWithBody) { + if (is_ForInBlockExpression(node) || is_LoopBlockExpression(node)) { + return false; + } + if (is_WhileBlockExpression(node)) { + return true; + } + + const parent = getParentNode(node)!; + + if ( + is_ExpressionStatement(parent) && + (!is_ImplicitReturnAbleNode(node) || pathCallAtParent(parent, (parent) => stmtNeedsSemi(parent, true))) + ) { + return false; + } + + if (is_ElseBlock(node, parent)) { + return pathCallAtParent(parent, canInlineBlockBody); + } + // if (is_CaseBlock(node, parent)) { + // return false; + // } + if (is_IfBlockExpression(node)) { + if ( + !node.else || + // hasLetScrutineeCondition(node) || + is_ExpressionWithBodyOrCases_or_BlockLikeMacroInvocation(node.condition) || + willBreak(getPrintFn(node)("condition")) + ) { + return false; + } + + const grandparent = getGrandParentNode(); + if (is_ExpressionStatement(parent) && hasBody(grandparent) && grandparent.body.length > 1) { + return false; + } + } + return true; + return ( + is_CallExpression_or_CallLikeMacroInvocation(parent) || + hasItems(parent) || + hasProperties(parent) || + is_ClosureFunctionExpression(parent) || + is_MemberExpression(parent) || + is_AwaitExpression(parent) || + is_LeftRightExpression(parent) + ); +} + +type NodeWithBracketContent = + | NodeWithBodyOrCases + | BlockLikeMacroInvocation + | EnumDeclaration + | StructDeclaration + | StructLiteral + | StructPattern + | EnumMemberStructDeclaration + | UnionDeclaration + | MacroRulesDeclaration + | MacroDeclaration; + +export function emptyContent(node: NodeWithBracketContent): Doc { + switch (node.nodeType) { + case NodeType.Program: + case NodeType.MacroRulesDeclaration: + case NodeType.MacroDeclaration: + case NodeType.ExternBlockDeclaration: + case NodeType.ModuleDeclaration: + case NodeType.TraitDeclaration: + case NodeType.StructDeclaration: + case NodeType.MacroInvocation: + case NodeType.FunctionDeclaration: + case NodeType.ImplDeclaration: + case NodeType.UnionDeclaration: + case NodeType.EnumDeclaration: + case NodeType.EnumMemberStructDeclaration: + case NodeType.StructLiteral: + case NodeType.StructPattern: + // case NodeType.MatchExpression: + return ""; + case NodeType.BlockExpression: + case NodeType.WhileBlockExpression: + case NodeType.ForInBlockExpression: + case NodeType.TryBlockExpression: + case NodeType.IfBlockExpression: + return canInlineInlineable(node) + ? is_IfBlockExpression(node) || is_ElseBlock(node, getParentNode()!) + ? softline + : "" + : hardline; + case NodeType.LoopBlockExpression: + case NodeType.MatchExpression: + return hardline; + default: + if (is_NodeWithBodyNoBody(node)) { + return ""; + } + __DEV__: exit.never(node); + return ""; + } +} + +export function is_insideScrutinee(target: Node) { + return withPathAt(target, (n) => stackIncludes("condition") && r(n)); + function r(CHILD: Node) { + switch (CHILD.nodeType) { + case NodeType.OrExpression: + case NodeType.AndExpression: + return pathCallParentOf(CHILD, (PARENT) => + hasCondition(PARENT) && PARENT.condition === CHILD // + ? hasLetScrutineeCondition(PARENT) + : r(PARENT) + ); + case NodeType.LetScrutinee: + return true; + default: + return false; + } + } +} + +function withPathAt(target: T, callback: (target: T) => R): R { + if (target === getNode()) return callback(target); + if (target === getParentNode()) return pathCallAtParent(target, () => callback(target)); + if (stackIncludes(target)) return pathCallAtParent(getParentNode()!, () => withPathAt(target, callback)); + return getContext().path.call(() => { + __DEV__: assertPathAtNode("withPathAt", target); + return callback(target); // @ts-ignore + }, ...getAstPath(getNode(), target)); +} +export function shouldPrintOuterAttributesAbove(node: Node) { + return ( + is_StatementNode(node) || + is_MatchExpressionCase(node) || + (hasAttributes(node) && + node.attributes.some( + canInlineOuterAttribute(node) + ? (attr) => is_DocCommentAttribute(attr) || hasBreaklineAfter(attr) // + : is_DocCommentAttribute + )) + ); + function canInlineOuterAttribute(node: Node) { + return ( + is_EnumMemberDeclaration(node) || + is_StructPropertyDeclaration(node) || + is_StructLiteralProperty(node) || + is_StructPatternProperty(node) + ); + } +} diff --git a/frontend/src/utils/prettier/plugins/rust/index.ts b/frontend/src/utils/prettier/plugins/rust/index.ts new file mode 100644 index 0000000..3c97854 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/index.ts @@ -0,0 +1,8 @@ +import { plugin } from "./format/plugin"; + +export default plugin; +export const languages = plugin.languages; +export const parsers = plugin.parsers; +export const printers = plugin.printers; +export const options = plugin.options; +export const defaultOptions = plugin.defaultOptions; diff --git a/frontend/src/utils/prettier/plugins/rust/transform/custom/attribute.ts b/frontend/src/utils/prettier/plugins/rust/transform/custom/attribute.ts new file mode 100644 index 0000000..abd1b0c --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/transform/custom/attribute.ts @@ -0,0 +1,116 @@ +import { + AttrSegment, + CallExpression, + DelimKind, + ExpressionPath, + Identifier, + Literal, + LocArray, + MacroInvocation, + NodeType, + PunctuationToken, + ReassignmentExpression, + TK, + rs, +} from "jinx-rust"; +import { isTK, start } from "jinx-rust/utils"; +import { assert, exit } from "../../utils/common"; +import { isIdent } from "./utils"; + +type SimpleAttrItem = + | Identifier // + | Literal + | ExpressionPath + | CallExpression + | ReassignmentExpression + | MacroInvocation; + +export function transform_simpleAttrSyntax(segments: MacroInvocation["segments"]) { + assert(segments.length !== 0, segments.loc.url()); + return transform_segments(segments, false); + + function transform_segments( + seq: LocArray, + nestedCall: N + ): N extends true ? LocArray : SimpleAttrItem { + let i = 0; + + if (nestedCall) { + const args = rs.createLocArray(DelimKind["()"], seq.loc.clone()); + while (i !== seq.length) { + args.push(read(true)); + if (i === seq.length) break; + assert(isTK(seq[i++], TK[","])); + } + return args as any; + } else { + const res = read(true); + assert(i === seq.length, res.loc.url()); + return res as any; + } + + function read(allowEq: boolean): SimpleAttrItem { + let lhs: Identifier | ExpressionPath; + + switch (seq[i].nodeType) { + case NodeType.Literal: + return seq[i++] as Literal; + case NodeType.Identifier: + lhs = seq[i++] as Identifier; + break; + case NodeType.PunctuationToken: + assert((seq[i] as PunctuationToken).tk === TK["::"], seq[i].loc.url()); + lhs = eatPathSegment(undefined); + break; + default: + exit.never(); + } + + while (true) { + if (i === seq.length) return lhs; + const seg = seq[i]; + switch (seg.nodeType) { + case NodeType.PunctuationToken: + switch (seg.tk) { + case TK[","]: + assert(nestedCall); + return lhs; + case TK["="]: { + assert(allowEq); + const right = (i++, read(false)); + return rs.mockNode(NodeType.ReassignmentExpression, right.loc.cloneFrom(start(lhs)), { + tk: TK["="], + kind: DelimKind["="], + left: lhs, + right, + }); + } + case TK["::"]: + lhs = eatPathSegment(lhs); + continue; + default: + exit.never(); + } + case NodeType.DelimGroup: + assert(seg.segments.dk === DelimKind["()"]); + return rs.mockNode(NodeType.CallExpression, seq[i++].loc.cloneFrom(start(lhs)), { + callee: lhs, + typeArguments: undefined, + method: undefined, + arguments: transform_segments(seg.segments, true), + }); + default: + exit.never(); + } + } + } + + function eatPathSegment(left: undefined | Identifier | ExpressionPath) { + const segment = seq[i + 1]; + assert(isIdent(segment)); + const res = rs.mockNode(NodeType.ExpressionPath, segment.loc.cloneFrom(start(left ?? seq[i])), { namespace: left, segment }); + i += 2; + return res; + } + } +} diff --git a/frontend/src/utils/prettier/plugins/rust/transform/custom/cfg_if.ts b/frontend/src/utils/prettier/plugins/rust/transform/custom/cfg_if.ts new file mode 100644 index 0000000..f1cec03 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/transform/custom/cfg_if.ts @@ -0,0 +1,92 @@ +import { + DelimGroup, + DelimKind, + IfBlockExpression, + LocArray, + MacroInvocation, + NodeType, + NodeWithBody, + rs, + Segment, + Snippet, + StatementNode, + TK, +} from "jinx-rust"; +import { insertNodes, start, transferAttributes } from "jinx-rust/utils"; +import { assert, iLast } from "../../utils/common"; +import { isGroup, isIdent, isToken } from "./utils"; + +export function transform_macro_cfg_if(segments: MacroInvocation["segments"]) { + const danglingAttributes: Snippet["danglingAttributes"] = []; + const comments: Snippet["comments"] = []; + + const block = (function create_if_block(i: number): IfBlockExpression | undefined { + if (i >= segments.length) return undefined; + + const _if = segments[i]; + const pound = segments[i + 1]; + const grp = segments[i + 2]; + const block = segments[i + 3]; + const _else = segments[i + 4]; + + assert( + isIdent(_if, "if") && + isToken(pound, TK["#"]) && + isGroup(grp, DelimKind["[]"]) && + isGroup(block, DelimKind["{}"]) && + (!_else || isIdent(_else, "else")) + ); + + return create_block(block, (body) => + rs.mockNode(NodeType.IfBlockExpression, block.loc.cloneFrom(start(_if)), { + label: undefined, + condition: rs.mockNode(NodeType.Attribute, grp.loc.cloneFrom(start(pound)), { + segments: grp.segments, + value: grp.segments.loc.sliceText(), + line: false, + inner: false, + }) as any, + body: body, + else: (_else && iLast(i + 5, segments) + ? function create_else_block(i: number) { + const block = segments[i]; + assert(isGroup(block, DelimKind["{}"])); + return create_block(block, (body) => + rs.mockNode(NodeType.BlockExpression, body.loc.clone(), { + label: undefined, + body, + }) + ); + } + : create_if_block)(i + 5), + }) + ); + })(0); + + const ast = rs.createLocArray( + segments.dk, + segments.loc, + block && [ + rs.mockNode(NodeType.ExpressionStatement, block.loc.clone(), { + expression: block, + semi: false, + }), + ] + ); + + return rs.mockNode(NodeType.Snippet, segments.loc.clone(), { ast, danglingAttributes, comments }); + + function create_block( + group: DelimGroup & { segments: { dk: 3 } }, + fn: (statements: LocArray) => R + ): R { + const snippet = rs.toBlockBody(group.segments); + + insertNodes(danglingAttributes, snippet.danglingAttributes); + insertNodes(comments, snippet.comments); + + const block = fn(snippet.ast); + transferAttributes(snippet, block); + return block; + } +} diff --git a/frontend/src/utils/prettier/plugins/rust/transform/custom/utils.ts b/frontend/src/utils/prettier/plugins/rust/transform/custom/utils.ts new file mode 100644 index 0000000..6aad32f --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/transform/custom/utils.ts @@ -0,0 +1,16 @@ +import { DelimGroup, DelimKind, Identifier, LocArray, PunctuationToken, Segment, TK } from "jinx-rust"; +import { isTK, is_DelimGroup, is_Identifier, is_PunctuationToken } from "jinx-rust/utils"; + +export function isIdent(node: Segment | undefined, name?: string): node is Identifier { + return !!node && is_Identifier(node) && (null == name || node.name === name); +} +export function isToken(node: Segment | undefined, tk?: TK): node is PunctuationToken { + return !!node && (null == tk ? is_PunctuationToken(node) : isTK(node, tk)); +} +export function isGroup(node: Segment | undefined, dk?: D): node is DelimGroup & { segments: LocArray } { + return !!node && is_DelimGroup(node) && (null == dk || node.segments.dk === dk); +} + +export function isCallLike(tk_1: Segment | undefined, tk_2: Segment | undefined): boolean { + return !!tk_1 && !!tk_2 && is_Identifier(tk_1) && is_DelimGroup(tk_2) && tk_2.segments.dk === DelimKind["()"]; +} diff --git a/frontend/src/utils/prettier/plugins/rust/transform/index.ts b/frontend/src/utils/prettier/plugins/rust/transform/index.ts new file mode 100644 index 0000000..7762205 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/transform/index.ts @@ -0,0 +1,568 @@ +import { + Attribute, + AttributeOrDocComment, + CallExpression, + DelimKind, + ExpressionNode, + LocArray, + MacroInvocation, + MemberExpression, + Node, + NodeType, + NodeWithBodyNoBody, + NodeWithTypeBounds, + NTMap, + ProgramLike, + rs, + Snippet, + StatementNode, + StructLiteral, + StructPattern, + TK, + TypeBound, + TypeBoundsStandaloneNode, + TypeDynBounds, + TypeTraitBound, +} from "jinx-rust"; +import { + countActualNodeChildren, + deleteAttributes, + each_childNode, + end, + getActualNodeChildren, + getBodyOrCases, + getMacroName, + getNodeChildren, + hasAttributes, + hasMethod, + hasTypeBounds, + includesTK, + insertNode, + insertNodes, + is_AttributeOrDocComment, + is_BareTypeTraitBound, + is_BlockExpression, + is_CallExpression, + is_ClosureFunctionExpression, + is_DocCommentAttribute, + is_ExpressionStatement, + is_ExpressionWithBodyOrCases, + is_FlowControlExpression, + is_IfBlockExpression, + is_MacroInvocation, + is_Node, + is_NodeWithBodyNoBody, + is_NodeWithBodyOrCases, + is_Program, + is_PunctuationToken, + is_ReassignmentNode, + is_Snippet, + is_TypeBoundsStandaloneNode, + is_TypeDynBounds, + is_TypeImplBounds, + is_TypeTraitBound, + ownStart, + reassignNodeProperty, + start, + transferAttributes, + unsafe_set_nodeType, +} from "jinx-rust/utils"; +import { isPrettierIgnoreAttribute, setPrettierIgnoreTarget } from "../format/comments"; +import { is_StructSpread } from "../format/core"; +import { CustomOptions } from "../format/external"; +import { getOptions } from "../format/plugin"; +import { + Array_replace, + Array_splice, + assert, + binarySearchIn, + each, + exit, + iLast, + last_of, + Map_get, + spliceAll, + try_eval, +} from "../utils/common"; +import { transform_simpleAttrSyntax } from "./custom/attribute"; +import { transform_macro_cfg_if } from "./custom/cfg_if"; + +export interface ExpressionLikeAttribute extends Attribute { + segments: LocArray; +} + +export interface CallLikeMacroInvocation extends MacroInvocation { + segments: LocArray; + callee: MacroInvocation["callee"]; + method: undefined; + typeArguments: undefined; + arguments: LocArray; +} + +export interface BlockLikeMacroInvocation extends MacroInvocation { + segments: LocArray; + body: LocArray; + attributes?: AttributeOrDocComment[]; +} + +export function is_CallLikeMacroInvocation(node: Node): node is CallLikeMacroInvocation { + return is_MacroInvocation(node) && "arguments" in node; +} + +export function is_BlockLikeMacroInvocation(node: Node): node is BlockLikeMacroInvocation { + return is_MacroInvocation(node) && "body" in node; +} + +export function is_CallExpression_or_CallLikeMacroInvocation(node: any): node is CallExpression | CallLikeMacroInvocation { + return is_CallExpression(node) || is_CallLikeMacroInvocation(node); +} + +const IGNORED_MACROS = new Set([ + // std + // crates + "quote", +]); + +const HARDCODED_MACRO_DELIMS = new Map(); +each( + { + [DelimKind["{}"]]: [ + // std + "thread_local", + // crates + "cfg_if", + ], + [DelimKind["()"]]: [ + // std + "assert_eq", + "assert_ne", + "assert", + "cfg", + "concat_bytes", + "concat_idents", + "concat", + "debug_assert_eq", + "debug_assert_ne", + "debug_assert", + "eprint", + "eprintln", + "format_args_nl", + "format_args", + "format", + "matches", + "panic", + "print", + "println", + "try", + "unimplemented", + "unreachable", + "write", + "writeln", + // crates + ], + [DelimKind["[]"]]: [ + // std + "vec", + // crates + ], + }, + (names, tk) => + each(names, (name) => { + HARDCODED_MACRO_DELIMS.set(name, +tk as MacroInvocation["segments"]["dk"]); + }) +); + +let _COMMENTS: CustomOptions["comments"] = undefined!; +let _DANGLING_ATTRIBUTES: CustomOptions["danglingAttributes"] = undefined!; + +export function transform_ast(options: CustomOptions) { + try { + _COMMENTS = options.comments; + _DANGLING_ATTRIBUTES = options.danglingAttributes; + transformNode(options.rsParsedFile); + } finally { + _depth = 0; + _COMMENTS = undefined!; + _DANGLING_ATTRIBUTES = undefined!; + } +} + +let _depth = 0; +const isReadingSnippet = () => 0 !== _depth; + +function maybe_transform_node( + node: T, + read_snippet: () => S, + fn: (node: T, snippet: S) => void +): T | undefined { + const snippet = try_eval(read_snippet); + if (snippet) { + ++_depth; + transformNode(snippet); + --_depth; + fn(node, snippet); + transformed.add(node); + return node; + } +} +const transformed = new WeakSet(); +export function isTransformed(node: Node) { + return transformed.has(node); +} + +const transform: { [K in NodeType]?: (node: NTMap[K]) => void } = { + [NodeType.Attribute](node) { + try_eval(() => { + node.segments = rs.createLocArray(node.segments.dk, node.segments.loc.clone(), [ + transform_simpleAttrSyntax(node.segments), + ]) as any; + transformed.add(node); + }); + }, + [NodeType.MacroInlineRuleDeclaration](node) { + node.match.dk = DelimKind["()"]; + node.transform.dk = DelimKind["{}"]; + }, + [NodeType.MacroInvocation](node) { + const name = getMacroName(node); + + if ( + IGNORED_MACROS.has(name) || + node.segments.length === 0 || + (node.segments.length === 1 && is_PunctuationToken(node.segments[0])) + ) { + return; + } + + const tk = transformMacroDelim(name, node); + + if (name === "matches") { + // + } + + if (name === "if_chain") { + // + } + + if (name === "cfg_if") { + transformBlockLike(() => transform_macro_cfg_if(node.segments) as any); + } else if (tk === DelimKind["{}"]) { + transformBlockLike(); /* || (includesTK(node, TK[","]) && transformCallLike()); */ + } else { + transformCallLike(); /* || (includesTK(node, TK[";"]) && transformBlockLike()); */ + } + + function transformBlockLike(transform = () => rs.toBlockBody(node.segments)) { + return maybe_transform_node(node as BlockLikeMacroInvocation, transform, (node, snippet) => { + const _body = snippet.ast; + _body.dk = tk; + + node.body = _body; + node.segments = _body; + transferAttributes(snippet, node); + }); + } + + function transformCallLike() { + return maybe_transform_node( + node as CallLikeMacroInvocation, + () => rs.toCallExpressionArguments(node.segments), + (node, snippet) => { + const _arguments = snippet.ast; + _arguments.dk = tk; + + node.method = undefined; + node.typeArguments = undefined; + node.arguments = _arguments; + node.segments = _arguments; + } + ); + } + }, + [NodeType.CallExpression](node) { + if (hasMethod(node)) { + node.callee = rs.mockNode(NodeType.MemberExpression, node.method.loc.cloneFrom(start(node.callee)), { + expression: node.callee, + property: node.method, + computed: false, + }); + node.method = undefined!; + getOptions().actuallyMethodNodes.add(node.callee as MemberExpression); + } + }, + + [NodeType.AutoTraitDeclaration](node) { + mockBodyNoBody(node); + }, + [NodeType.NegativeImplDeclaration](node) { + mockBodyNoBody(node); + }, + + [NodeType.StructLiteral](node) { + moveSpreadsToEnd(node); + }, + [NodeType.StructPattern](node) { + moveSpreadsToEnd(node); + }, +}; + +function moveSpreadsToEnd(node: StructLiteral | StructPattern) { + const props = node.properties; + if (props.some((p, i, a) => is_StructSpread(p) && !iLast(i, a))) { + const spreads: any[] = []; + for (let i = 0; i < props.length; i++) { + const prop = props[i]; + if (is_StructSpread(prop)) { + Array_splice(props, prop, i--); + spreads.push(prop); + } + } + props.push(...spreads); + } +} + +function mockBodyNoBody(node: NodeWithBodyNoBody) { + // @ts-expect-error + node.body = rs.createLocArray(last_of(rs.toTokens(node).ast).loc.clone(), DelimKind["{}"]); +} + +function transformMacroDelim(name: string, node: MacroInvocation): 1 | 2 | 3 { + if (HARDCODED_MACRO_DELIMS.has(name)) { + return HARDCODED_MACRO_DELIMS.get(name)!; + } + if (node.segments.dk === DelimKind["{}"] && includesTK(node, TK[","])) { + return DelimKind["()"]; + } + if (node.segments.dk === DelimKind["()"] && includesTK(node, TK[";"])) { + return DelimKind["{}"]; + } + return node.segments.dk; +} + +// export function createTransformed(create: () => S): S { +// return transformNode(create()); +// } + +const seen = new WeakSet(); +function transformNode(node: T, parent?: Node, key?: string, index?: any): T { + if (!seen.has(node)) { + seen.add(node); + if (is_Snippet(node) || is_Program(node)) { + registerPogramLike(node); + } + + each_childNode(node, transformNode); + + insert_blocks(node, parent, key, index); + + transform[node.nodeType]?.(node as any); + + flatten_typeBounds(node); + + transform_nodeAttributes(node); + } + return node; +} + +function insert_blocks(node: Node, parent?: Node, key?: string, index?: any) { + if (parent && key) { + if ( + !is_ExpressionStatement(parent) && + (false || + // "1 + break" -> "1 + { break; }" + is_FlowControlExpression(node) || + // "1 + a = b" -> "1 + { a = b; }" + (!isReadingSnippet() && is_ReassignmentNode(node) && !(is_ReassignmentNode(parent) && parent.left === node))) + ) { + reassignNodeProperty(blockify(node), parent, key, index); + } else if ( + is_ClosureFunctionExpression(node) && + (false || + // "|| -> T x" -> "|| -> T { x }" + (!!node.returnType && !is_BlockExpression(node.expression)) || + // "|| match x {}" -> "|| { match x {} }" + (is_ExpressionWithBodyOrCases(node.expression) && + !is_BlockExpression(node.expression) && + !is_IfBlockExpression(node.expression))) + ) { + node.expression = blockify(node.expression); + } + } + function blockify(node: ExpressionNode) { + const block = rs.mockNode(NodeType.BlockExpression, node.loc.clone(), { + label: undefined, + body: rs.createLocArray(DelimKind["{}"], node.loc.clone(), [ + rs.mockNode(NodeType.ExpressionStatement, node.loc.clone(), { semi: false, expression: node }), + ]), + }); + transferAttributes(node, block); + return block; + } +} + +function flatten_typeBounds(topNode: Node) { + if (hasTypeBounds(topNode)) { + const nestedBounds: TypeTraitBound[] = topNode.typeBounds.filter(isBoundWithNestedBounds); + const [first, ...subsequent] = nestedBounds; + + const flatten = (bound: TypeTraitBound) => + Array_replace(topNode.typeBounds, bound, ...(bound.typeExpression as unknown as TypeDynBounds).typeBounds); + + if (nestedBounds.every(isBareBoundWithNestedBoundsNoPrefix)) { + // A + (B + C) + // -> A + B + C + each(nestedBounds, flatten); + } else if ( + !hasDefinedPrefix(topNode) && + first === topNode.typeBounds[0] && + !isBareBoundWithNestedBoundsNoPrefix(first) && + subsequent.every(isBareBoundWithNestedBoundsNoPrefix) + ) { + if (is_TypeDynBounds(topNode)) { + if (is_TypeImplBounds(first.typeExpression)) { + // (impl A) + B + // -> impl A + B + unsafe_set_nodeType(topNode, NodeType.TypeImplBounds); + } else { + // (dyn A) + B + // -> dyn A + B + topNode.dyn = true; + } + each(nestedBounds, flatten); + } else { + each(subsequent, flatten); + (first.typeExpression as unknown as TypeDynBounds).typeBounds.push(...topNode.typeBounds.slice(1)); + topNode.typeBounds.length = 1; + } + } + } + + function isBoundWithNestedBounds(bound: TypeBound): bound is TypeTraitBound & { typeExpression: TypeBoundsStandaloneNode } { + return is_TypeTraitBound(bound) && is_TypeBoundsStandaloneNode(bound.typeExpression); + } + function isBareBoundWithNestedBounds(bound: TypeBound): bound is TypeTraitBound & { typeExpression: TypeBoundsStandaloneNode } { + return isBoundWithNestedBounds(bound) && is_BareTypeTraitBound(bound); + } + function isBareBoundWithNestedBoundsNoPrefix(bound: TypeBound): bound is TypeTraitBound & { typeExpression: TypeDynBounds } { + return isBareBoundWithNestedBounds(bound) && !hasDefinedPrefix(bound.typeExpression); + } + function hasDefinedPrefix(node: NodeWithTypeBounds) { + return (is_TypeDynBounds(node) && node.dyn) || is_TypeImplBounds(node); + } +} + +function transform_nodeAttributes(node: Node) { + /** + * # Inside Token trees: + * + * 1. DocCommentAttribute --is parsed as--> Comment + * 2. Attribute --is parsed as--> Token<'#'>, DelimGroup<'[]'> + * + * # Transforming tokens into a Snippet: + * + * 1. DocCommentAttribute <--replace from-- Comment + * a) Remove node with same loc from comments + * b) Merge Snippet.danglingAttributes with Program.danglingAttributes + * + * 2. Attribute (no action needed) + * + */ + if (hasAttributes(node)) { + const attrs = node.attributes; + for (let i = 0; i < attrs.length; i++) { + const attr = attrs[i]; + if (isReadingSnippet() && is_DocCommentAttribute(attr)) { + const index = binarySearchIn(_COMMENTS, start(attr), start); + __DEV__: assert(index !== -1), assert(end(_COMMENTS[index]) === end(attr)); + _COMMENTS.splice(index, 1); + } + if (attr.inner) { + if (isPrettierIgnoreAttribute(attr)) { + setPrettierIgnoreTarget(is_Program(node) ? node.loc.src : node, attr); + } + // @ts-expect-error Inserting Attribute into StatementNode[] + insertNode(is_Snippet(node) ? node.ast : getBodyOrCases(node)!, attr); + Array_splice(attrs, attr, i--); + } + } + if (attrs.length === 0) { + deleteAttributes(node); + } + } +} + +function registerPogramLike(program: Extract) { + const comments = spliceAll(program.comments); + const danglingAttributes = spliceAll(program.danglingAttributes); + for (let i = 0; i < danglingAttributes.length; i++) { + const attr = danglingAttributes[i]; + // if (isReadingSnippet() && is_DocCommentAttribute(attr)) { + // } + if (is_DocCommentAttribute(attr)) { + if (isReadingSnippet()) { + const index = binarySearchIn(_COMMENTS, start(attr), start); + __DEV__: assert(index !== -1), assert(end(_COMMENTS[index]) === end(attr)); + _COMMENTS.splice(index, 1); + } + } else { + transformNode(danglingAttributes[i], program, "danglingAttributes", i); + } + } + if (!isReadingSnippet()) insertNodes(_COMMENTS, comments); + insertNodes(_DANGLING_ATTRIBUTES, danglingAttributes); +} + +const CommentChildNodes = new WeakMap(); + +export function getCommentChildNodes(n: any): Node[] { + const children = Map_get(CommentChildNodes, n, getTransformedNodeChildren); + /** + * parent { + * #[attr] + * #![attr] <-------- list misplaced inner attrs as part of "#[attr] child {}" + * child {} + * } + */ + if (is_NodeWithBodyOrCases(n) || is_BlockLikeMacroInvocation(n)) { + for (let i = 0; i < children.length; i++) { + const attr = children[i]; + if (is_AttributeOrDocComment(attr)) { + const target = children.find((n) => start(n) <= start(attr) && ownStart(n) >= end(attr)); + if (target) { + children.splice(i--, 1); + insertNode(Map_get(CommentChildNodes, target, getTransformedNodeChildren), attr); + } + } + } + } + return children; + + function getTransformedNodeChildren(node: Node) { + if (is_Program(node)) node.comments ??= []; // prettier core deletes this property + const children = getNodeChildren(node); + + if (is_NodeWithBodyNoBody(node)) { + insertNodes(children, (node as any).body); + } + + __DEV__: { + const actual_count = countActualNodeChildren(node); + if ( + children.length !== actual_count && + !(is_MacroInvocation(node) && actual_count - node.segments.length === children.length) + ) { + const actual = getActualNodeChildren(node); + const missing = actual.filter((n) => !children.includes(n)); + const unknown = children.filter((n) => !actual.includes(n)); + const duplicates_in_object = actual.filter((n, i, a) => i !== 0 && n === a[i - 1]); + const duplicates_in_childNodes = children.filter((n, i, a) => i !== 0 && n === a[i - 1]); + const ctx = { missing, unknown, duplicates_in_object, duplicates_in_childNodes }; + for (let key in ctx) if (ctx[key].length === 0) delete ctx[key]; + exit(`${node.type} was transformed but did not patch its childNodes list`, ctx, node.loc.url(), node); + } + for (const child of children) + if (!is_Node(child)) exit(`${node.type}'s childNodes includes unexpected entries`, { node, child }); + } + return children; + } +} diff --git a/frontend/src/utils/prettier/plugins/rust/utils/common.ts b/frontend/src/utils/prettier/plugins/rust/utils/common.ts new file mode 100644 index 0000000..fce0dc1 --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/utils/common.ts @@ -0,0 +1,246 @@ +import { createCustomError } from "./debug"; + +declare global { + interface ImportMeta { + url: string; + } +} + +export function Narrow(value: R): asserts value is T {} +export function AssertTypesEq(...args: [B] extends [A] ? [] : [RIGHT_TYPES_NOT_ASSIGNABLE_TO_LEFT: Exclude]) {} + +// prettier-ignore +type indexof = A extends readonly any[] ? A extends 0 ? any : keyof A & number : A extends Set ? never : A extends Map ? U + : A extends Iterable ? never : A extends object ? keyof A & (number | string) : never; +// prettier-ignore +type valueof = A extends ReadonlyArray ? A extends 0 ? any : U : A extends Set ? U : A extends Map ? U + : A extends Iterable ? U : A extends object ? A[keyof A & (number | string)] : never; +// prettier-ignore +type vObject = | object | readonly V[] | { [key: string]: V } | anySet | anyMap; +export type itfn = (value: valueof, key: indexof) => R; +type anySet = Set; +type anyMap = Map; +type anyfunction = (...args: A) => R; +type objlike = object | anyfunction; +type anymap = K extends objlike ? Map | WeakMap : Map; + +export function exit(message: string, ...ctx: any[]): never { + if (ctx.length > 0) console.log("Error context:", { ...ctx }); + throw createCustomError({ message }); +} +exit.never = function never(...ctx: any[]): never { + exit("Reached unreachable code", ...ctx); +}; +export function assert(predicate: boolean, err?: string, ...ctx: any[]): asserts predicate { + __DEV__: if (typeof predicate !== "boolean") exit("Expected boolean", predicate); + if (false === predicate) exit(err ?? "Assertion failed", ...ctx); +} +export function Identity(v: T): T { + return v; +} + +export function last_of>(arr: T): T extends readonly [...infer A, infer U] ? U : T[number] { + __DEV__: isArrayLike(arr) || exit("Expected Array"), arr.length > 0 || exit("Attempted to retrieve last item of an empty array", arr); + return arr[arr.length - 1]; +} +export function maybe_last_of( + arr: T +): T extends any[] ? (T extends readonly [...infer A, infer U] ? U : T[number]) : undefined { + return (undefined === arr || 0 === arr.length ? undefined : last_of(arr as any[])) as any; +} + +export function normPath(filepath: string) { + return filepath.replace(/^file:\/\/\//, "").replace(/\\\\?/g, "/"); +} + +export function print_string(str: string) { + return /[\u0000-\u0020]/.test(str) + ? str + .replace(/ /g, "•") + .replace(/\n/g, "↲") + .replace(/\t/g, "╚") + .replace(/[\u0000-\u0020]/g, "□") + : str; +} + +function isArrayLike(value: any): value is ArrayLike { + return is_object(value) && oisArrayLike(value); +} + +function oisArrayLike(value: {}): value is ArrayLike { + return "length" in value && (0 === (value as any).length || "0" in value); +} + +export function binarySearchIn(array: ArrayLike, target: number, toValue: (item: T) => number) { + if (isEmpty(array)) return -1; + let i = 0; + let low = 0; + let high = array.length - 1; + let value = toValue(array[high]); + if (target >= value) return high; + else high--; + while (low <= high) { + i = low + ((high - low) >> 1); + value = toValue(array[i]); + if (target === value) return i; + if (target > value) low = i + 1; + else high = i - 1; + } + return low - 1; +} + +export function getTerminalWidth(fallbackWidth = 200) { + return globalThis?.process?.stdout?.columns ?? fallbackWidth; +} + +// @ts-ignore +const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined"; +export const color = ((cfn, mfn) => ({ + black: cfn(30), + red: cfn(31), + green: cfn(32), + yellow: cfn(33), + blue: cfn(34), + magenta: cfn(35), + cyan: cfn(36), + white: cfn(37), + grey: cfn(90), + bold: mfn(1, 22), + italic: mfn(3, 23), + underline: mfn(4, 24), + hidden: mfn(8, 28), + hiddenCursor: (str: string) => `\x1B[?25l${str}\x1B[?25h`, + unstyle: (str: string) => str.replace(/\x1B\[[0-9][0-9]?m/g, ""), + unstyledLength: (str: string) => str.replace(/\x1B\[[0-9][0-9]?m/g, "").length, + link: (str: string) => color.underline(color.blue(str)), +}))( + (c1: number) => (isBrowser ? Identity : (str: string) => `\x1B[${c1}m${str.replace(/\x1B\[39m/g, `\x1B[${c1}m`)}\x1B[39m`), + (c1: number, c2: number) => (isBrowser ? Identity : (str: string) => `\x1B[${c1}m${str}\x1B[${c2}m`) +); +export function Map_get(map: WeakMap, key: K, init: (key: K) => V): V; +export function Map_get(map: Map, key: K, init: (key: K) => V): V; +export function Map_get(map: anymap, key: K, init: (key: K) => V): V { + if (!map.has(key)) map.set(key, init(key)); + return map.get(key)!; +} +export function isEmpty(array: ArrayLike): boolean { + __DEV__: assert(isArrayLike(array)); + return 0 === array.length; +} +export function Array_splice(array: T, target: T[number], index: number = array.indexOf(target)) { + __DEV__: { + const i = arguments.length === 2 ? array.indexOf(target) : index; + assert(i === index && i !== -1 && i === array.lastIndexOf(target), "", { array, target, index, i }); + } + array.splice(index, 1); +} +export function Array_replace(array: T, target: T[number], ...replacements: T[number][]) { + const i = array.indexOf(target); + __DEV__: if (i === -1 || i !== array.lastIndexOf(target)) + exit("Array_replace", { index: i, lastIndex: array.lastIndexOf(target), array, target, replacements }); + array.splice(array.indexOf(target), 1, ...replacements); +} +export function has_key_defined( + o: T, + k: K +): o is K extends never + ? never + : T extends { [k in K]: any } + ? T & { [k in K]: {} } + : T extends { [k in K]?: any } + ? T & { [k in K]: {} } + : never { + return k in o && undefined !== o[k]; +} + +export function is_object(data: unknown): data is object | ({ [key: string]: unknown } | unknown[]) { + return "object" === typeof data && null !== data; +} + +export function is_array(data: unknown): data is any[] { + return Array.isArray(data); +} + +function ois_vobject(data: any) { + __DEV__: assert(is_object(data)); + switch (data.constructor) { + case Array: + case Object: + case Set: + case Map: + return true; + default: + return false; + } +} + +export function each(data: A, callback: itfn): void; +export function each(data: any, callback: (value: any, index: any) => void): void { + __DEV__: assert(ois_vobject(data)); + // prettier-ignore + switch (data.constructor) { + case Array: { let i = 0; for (; i < data.length; i++) callback(data[i], i); return; } + case Object: { let k; for (k in data) callback(data[k], k); return; } + case Set: { let d; for (d of data) callback(d, undefined!); return; } + case Map: { let e; for (e of data) callback(e[1], e[0]); return; } + default: { let x; for (x of data) callback(x, undefined!); return; } + } +} + +export function iLast(index: number, array: any[]) { + return 1 + index === array.length; +} + +export function find_last(arr: T[], test: itfn): T | undefined { + for (var i = arr.length; --i !== -1; ) if (test(arr[i], i)) return arr[i]; +} + +export function try_eval(fn: () => T): T | undefined { + try { + return fn(); + } catch (e) { + return undefined; + } +} + +export function clamp(min: number, max: number, value: number) { + return value > min ? (value < max ? value : max) : min; +} + +export type MaybeFlatten = T extends ReadonlyArray ? MaybeFlatten> : T; +export type FlatArray = MaybeFlatten[]; +export function flat(arr: T): FlatArray { + return (arr as any as [any]).flat(Infinity); +} +export function flatMap(arr: T, mapFn: (item: T[number], index: number, array: T) => R): FlatArray { + return flat(arr.map(mapFn as any)); +} + +export function joinln(...arr: string[]): string { + return arr.join("\n"); +} + +export function join_lines(fn: () => Generator): string { + return [...fn()].join("\n"); +} + +export function reduce_tagged_template(args: [strings: TemplateStringsArray, ...values: T[]], map: (value: T) => string) { + for (var str = "" + args[0][0], i = 1; i < args.length; i++) str += map(args[i] as T) + args[0][i]; + return str; +} + +export function map_tagged_template(args: [strings: TemplateStringsArray, ...values: T[]], map: (value: T) => R) { + const arr: (R | string)[] = [args[0][0]]; + for (var i = 1; i < args.length; i++) arr.push(map(args[i] as T), args[0][i]); + return arr; +} + +export function spliceAll(array: T): [...T] { + const r: [...T] = [...array]; + array.length = 0; + return r; +} + +export function spread(fn: () => Iterable): R[] { + return [...fn()]; +} diff --git a/frontend/src/utils/prettier/plugins/rust/utils/debug.ts b/frontend/src/utils/prettier/plugins/rust/utils/debug.ts new file mode 100644 index 0000000..c66ffae --- /dev/null +++ b/frontend/src/utils/prettier/plugins/rust/utils/debug.ts @@ -0,0 +1,141 @@ +import { clamp, color, getTerminalWidth, normPath } from "./common"; + +const cwd = + typeof process === "object" && typeof process?.cwd === "function" ? /* @__PURE__ */ normPath(/* @__PURE__ */ process.cwd() ?? "") : ""; +function normPath_strip_cwd(filepath: string) { + let normFilePath = normPath(filepath); + return normFilePath.startsWith(cwd) ? normFilePath.slice(cwd.length + 1) : normFilePath; +} + +type StackStyleFn = (callee: string, item: StackItem) => (str: string) => string; +interface Stack extends Array { + message: string; + style?: { callee?: StackStyleFn; url?: StackStyleFn } | undefined; +} + +class StackLine { + declare readonly raw: string; + declare readonly callee: string; + declare readonly filepath: string; + declare readonly line: string; + declare readonly col: string; + declare readonly other: string; + declare readonly url: string; + constructor(raw: string) { + ({ + 1: this.callee = "", + 2: this.filepath = "", + 3: this.line = "", + 4: this.col = "", + 5: this.other = "", + } = (this.raw = raw).match(/at (?:(.+?)\s+\()?(?:(.+?):([0-9]+)(?::([0-9]+))?|([^)]+))\)?/) ?? ["", "", "", "", "", ""]); + this.url = this.filepath // + ? normPath_strip_cwd(this.filepath) + (this.line && this.col && `:${this.line}:${this.col}`) + : this.other === "native" + ? "" + : ""; + } +} + +function getPrintWidth() { + return clamp(0, getTerminalWidth(128), 200) - 4; +} + +class StackItem extends StackLine { + constructor(private readonly stack: Stack, readonly i: number, raw: string) { + super(raw); + } + hidden = false; + hide() { + this.hidden = true; + return this; + } + hideNext(n: number) { + for (let i = 0; i < n; i++) this.at(i)?.hide(); + } + hideWhileTrue(test: (line: StackItem) => boolean) { + let line: StackItem | undefined = this; + while (line && test(line)) line = line.hide().next(); + } + at(relIndex: number) { + return this.i + relIndex >= this.stack.length || this.i + relIndex < 0 ? undefined : this.stack[this.i + relIndex]; + } + next() { + return this.at(+1); + } + toString() { + const url = this.url; + const calleeColor = this.stack.style?.callee?.(this.callee, this) ?? color.cyan; + const urlColor = this.stack.style?.url?.(url, this) ?? color.grey; + return compose2Cols(" at " + calleeColor(this.callee), urlColor(url), getPrintWidth()); + } +} + +// prettier-ignore +function createStack(message: string, Error_stack: string, style: Stack["style"]): Stack { + for (var STACK: Stack = [] as any, i = 0, stack = Error_stack.split("\n").slice(2); i < stack.length; i++) STACK[i] = new StackItem(STACK, i, stack[i]); + return (STACK.message = message), (STACK.style = style), STACK; +} + +function composeStack(stack: Stack) { + var hidden = 0; + var str = stack.message; + for (var item of stack) item.hidden ? ++hidden : (str += "\n" + item.toString()); + return str + (hidden > 0 ? "\n" + color.grey(compose2Cols("", `...filtered ${hidden} lines`, getPrintWidth())) : ""); +} + +export function get_caller_cmd(offset = 0) { + const obj: { stack: string } = {} as any; + Error.captureStackTrace(obj, get_caller_cmd); + const lines = obj.stack.split("\n"); + return new StackLine(lines[1 + clamp(0, lines.length - 2, offset)]).url; +} + +var Error_prepareStackTrace; +let replaced_default_prepareStackTrace = false; +function custom_prepareStackTrace(err, calls) { + return (Error_prepareStackTrace?.(err, calls) ?? calls.join("\n"))?.replace(/file:\/\/\//g, "").replace(/\\\\?/g, "/") ?? calls; +} + +export function overrideDefaultError(silent = false) { + if (replaced_default_prepareStackTrace === (replaced_default_prepareStackTrace = true)) return; + Error_prepareStackTrace = Error.prepareStackTrace ?? ((_, calls) => calls.join("\n")); + Error.prepareStackTrace = custom_prepareStackTrace; + if (!silent) console.log(color.grey(`[devtools] Replaced Error.prepareStackTrace at ${get_caller_cmd(1)}`)); +} + +export function createCustomError({ + message = "Unknown Error", + editStack = (stack: StackItem[]) => {}, + style = undefined as Stack["style"], + stackTraceLimit = 20, +}): Error { + const _stackTraceLimit = Error.stackTraceLimit; + const _prepareStackTrace = Error.prepareStackTrace; + if (replaced_default_prepareStackTrace && _prepareStackTrace === custom_prepareStackTrace) + Error.prepareStackTrace = Error_prepareStackTrace; + + Error.stackTraceLimit = stackTraceLimit; + + const _ctx: { stack: string } = {} as any; + + Error.captureStackTrace(_ctx, createCustomError); + + const stack = createStack(message, _ctx.stack, style); + Error.prepareStackTrace = function (err, calls) { + editStack(stack); + return composeStack(stack); + }; + + const err = new Error(message); // (get) to trigger prepareStackTrace, (set) to prevent treeshaking + err.stack = err.stack; + + Error.stackTraceLimit = _stackTraceLimit; + Error.prepareStackTrace = _prepareStackTrace; + + return err; +} + +function compose2Cols(left: string, right: string, len = 64, min = 1) { + return left + " ".repeat(clamp(min, len, len - (color.unstyledLength(left) + color.unstyledLength(right)))) + right; +} diff --git a/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts b/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts index 44aa189..ed9b35b 100644 --- a/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts +++ b/frontend/src/views/editor/extensions/codeblock/lang-parser/languages.ts @@ -40,6 +40,8 @@ import goPrettierPlugin from "@/utils/prettier/plugins/go/go" import sqlPrettierPlugin from "@/utils/prettier/plugins/sql/sql" import phpPrettierPlugin from "@/utils/prettier/plugins/php" import javaPrettierPlugin from "@/utils/prettier/plugins/java" +import xmlPrettierPlugin from "@prettier/plugin-xml" +import * as rustPrettierPlugin from "@/utils/prettier/plugins/rust"; import * as prettierPluginEstree from "prettier/plugins/estree"; /** @@ -91,9 +93,15 @@ export const LANGUAGES: LanguageInfo[] = [ parser: "css", plugins: [cssPrettierPlugin] }), - new LanguageInfo("xml", "XML", xmlLanguage.parser), + new LanguageInfo("xml", "XML", xmlLanguage.parser,{ + parser: "xml", + plugins: [xmlPrettierPlugin] + }), new LanguageInfo("cpp", "C++", cppLanguage.parser), - new LanguageInfo("rs", "Rust", rustLanguage.parser), + new LanguageInfo("rs", "Rust", rustLanguage.parser,{ + parser: "jinx-rust", + plugins: [rustPrettierPlugin] + }), new LanguageInfo("cs", "C#", StreamLanguage.define(csharp).parser), new LanguageInfo("rb", "Ruby", StreamLanguage.define(ruby).parser), new LanguageInfo("sh", "Shell", StreamLanguage.define(shell).parser),