Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/prettier-plugin-java/src/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export function determineFormatterOffOnRanges(cst: JavaNonTerminal) {

export function isFullyBetweenFormatterOffOn(path: AstPath<JavaNode>) {
const { node, root } = path;
if (isNonTerminal(node) && node.location === undefined) {
return false;
}
const start = parser.locStart(node);
const end = parser.locEnd(node);
return (
Expand Down
16 changes: 13 additions & 3 deletions packages/prettier-plugin-java/src/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,29 @@ import {
isFullyBetweenFormatterOffOn
} from "./comments.js";
import {
embedTextBlock,
isNonTerminal,
isTerminal,
printComment,
printTextBlock,
type JavaNode,
type JavaTerminal
} from "./printers/helpers.js";
import { printerForNodeType } from "./printers/index.js";

export default {
print(path: DistributedAstPath<JavaNode>, options, print, args) {
return hasTerminal(path)
? path.node.image
: printerForNodeType(path.node.name)(path, print, options, args);
if (hasTerminal(path)) {
return path.node.tokenType.name === "TextBlock"
? printTextBlock(path)
: path.node.image;
}
return printerForNodeType(path.node.name)(path, print, options, args);
},
embed(path: DistributedAstPath<JavaNode>) {
return hasTerminal(path) && path.node.tokenType.name === "TextBlock"
? embedTextBlock(path)
: null;
},
hasPrettierIgnore(path) {
const { node } = path;
Expand Down
90 changes: 89 additions & 1 deletion packages/prettier-plugin-java/src/printers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
IToken,
StatementCstNode
} from "java-parser";
import type { AstPath, Doc, ParserOptions } from "prettier";
import type { AstPath, Doc, Options, ParserOptions } from "prettier";
import { builders } from "prettier/doc";
import type { JavaComment } from "../comments.js";
import parser from "../parser.js";
Expand Down Expand Up @@ -328,6 +328,94 @@ export function printClassType(
});
}

export function printTextBlock(path: AstPath<JavaTerminal>) {
const [open, ...lines] = path.node.image.split("\n");
const baseIndent = findBaseIndent(lines);
const textBlock = join(hardline, [
open,
...lines.map(line => line.slice(baseIndent))
]);
const ancestor = path.getNode(16) as JavaNonTerminal | null;
return ancestor?.name === "variableInitializer" ||
(ancestor?.name === "binaryExpression" &&
ancestor.children.AssignmentOperator)
? indent(textBlock)
: textBlock;
}

export function embedTextBlock(path: AstPath<JavaTerminal>) {
const language = findEmbeddedLanguage(path);
if (!language) {
return null;
}
const text = path.node.image
.replace(/^"""\n/, "")
.replace(/"""$/, "")
.replace(/\\u+([0-9a-fA-F]{4})/g, (_, hex) =>
String.fromCharCode(parseInt(hex, 16))
);
const unindentedText = stripIndent(text);
const decodedText = translateEscapes(unindentedText);

return async (
textToDoc: (text: string, options: Options) => Promise<Doc>
) => {
const doc = await textToDoc(decodedText, { parser: language });
return group(indent(['"""', hardline, doc, hardline, '"""']));
};
}

function findEmbeddedLanguage(path: AstPath<JavaNode>) {
return path.ancestors
.find(
node =>
(isNonTerminal(node) && node.name === "blockStatement") ||
node.comments?.some(({ leading }) => leading)
)
?.comments?.filter(({ leading }) => leading)
.reverse()
.map(
({ image }) =>
image.match(/^(?:\/\/|\/\*)\s*language\s*=\s*([^\s]+)/)?.[1]
)
.find(language => language)
?.toLowerCase();
}

function stripIndent(text: string) {
const lines = text.split("\n");
const indent = findBaseIndent(lines);
return lines.map(line => line.slice(indent)).join("\n");
}

function translateEscapes(text: string) {
return text.replace(
/\\(?:([bfntr"'\\])|([0-7]{1,3})|\n)/g,
(_, single, octal) => {
if (single) {
switch (single) {
case "b":
return "\b";
case "f":
return "\f";
case "n":
return "\n";
case "t":
return "\t";
case "r":
return "\r";
default:
return single;
}
} else if (octal) {
return String.fromCharCode(parseInt(octal, 8));
} else {
return "";
}
}
);
}

export function isBinaryExpression(expression: ExpressionCstNode) {
const conditionalExpression =
expression.children.conditionalExpression?.[0].children;
Expand Down
27 changes: 2 additions & 25 deletions packages/prettier-plugin-java/src/printers/lexical-structure.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,12 @@
import { builders } from "prettier/doc";
import {
findBaseIndent,
map,
onlyDefinedKey,
printSingle,
type JavaNodePrinters,
type JavaNonTerminal
type JavaNodePrinters
} from "./helpers.js";

const { hardline, indent, join } = builders;

export default {
literal(path, print) {
const { TextBlock } = path.node.children;
if (!TextBlock) {
return printSingle(path, print);
}
const [open, ...lines] = TextBlock[0].image.split("\n");
const baseIndent = findBaseIndent(lines);
const textBlock = join(hardline, [
open,
...lines.map(line => line.slice(baseIndent))
]);
const ancestor = path.getNode(14) as JavaNonTerminal | null;
return ancestor?.name === "variableInitializer" ||
(ancestor?.name === "binaryExpression" &&
ancestor.children.AssignmentOperator)
? indent(textBlock)
: textBlock;
},

literal: printSingle,
integerLiteral: printSingle,
floatingPointLiteral: printSingle,
booleanLiteral: printSingle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,21 @@ public void print(%s object) {
);
}

void json() {
// language=json
String config = """
{ "name":"example",
"enabled" :true,
"timeout":30}
""";

/* language = JSON */
String query = """
{
"sql":"SELECT * FROM users \
WHERE active=1 \
AND deleted=0",
"limit":10}
""";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,19 @@ public void print(%s object) {
abc"""
);
}

void json() {
// language=json
String config = """
{ "name": "example", "enabled": true, "timeout": 30 }
""";

/* language = JSON */
String query = """
{
"sql": "SELECT * FROM users WHERE active=1 AND deleted=0",
"limit": 10
}
""";
}
}
Loading