groovy-beautify
Version:
A simple beautifier which allows formatting and indenting Groovy scripts
591 lines (590 loc) • 17 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) =>
key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : (obj[key] = value);
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
class Parser {
constructor(text, rules) {
__publicField(this, "text");
__publicField(this, "textLength");
__publicField(this, "textPosition");
__publicField(this, "rules");
this.text = text;
this.textLength = text ? text.length : 0;
this.textPosition = 0;
this.rules = rules || [];
}
scan() {
if (this.textPosition >= this.textLength) return null;
return this.text[this.textPosition++];
}
isDone() {
return this.textPosition >= this.textLength;
}
move(length) {
this.textPosition += length;
}
match(cb, expression, wholeword) {
var _a;
let result;
if (expression instanceof RegExp) {
const modifiedExpression = new RegExp("^(" + expression.source + ")", expression.flags);
const text = (_a = this.text.substring(this.textPosition).match(modifiedExpression)) == null ? void 0 : _a[0];
if (text !== void 0) {
result = text;
}
} else if (expression instanceof Array) {
for (const exp of expression) {
const match = this.match(cb, exp);
if (match) {
result = match;
break;
}
}
} else if (typeof expression === "function") {
result = expression(cb, this.text.substring(this.textPosition));
} else if (typeof expression === "string") {
if (this.text.substring(this.textPosition).startsWith(expression)) {
result = expression;
}
}
if (result && wholeword) {
const prevSymbol = this.text.charAt(this.textPosition - 1);
const nextSymbol = this.text.charAt(this.textPosition + result.length);
if (prevSymbol.match(/[A-Za-z0-9_]/) || nextSymbol.match(/[A-Za-z0-9_]/)) {
result = void 0;
}
}
return result;
}
matchStart(cb, rule) {
return this.match(cb, rule.start, rule.wholeword);
}
matchEnd(cb, rule) {
return this.match(cb, rule.end, rule.wholeword);
}
matchSkip(cb, rule) {
return this.match(cb, rule.skip, rule.wholeword);
}
createDefaultTextObject(text) {
return {
type: "text",
start: text,
};
}
parse() {
var _a;
const root = { type: "root", start: "" };
const ruleStack = [];
const objStack = [root];
while (!this.isDone()) {
const activeRule = ruleStack[ruleStack.length - 1];
const activeObj = objStack[objStack.length - 1];
const activeObjLastChild = (_a = activeObj.children) == null ? void 0 : _a[activeObj.children.length - 1];
if (activeRule) {
if (activeRule.skip) {
const activeRuleSkipText = this.matchSkip(activeObj, activeRule);
if (typeof activeRuleSkipText === "string") {
if (activeObjLastChild && activeObjLastChild.type === "text") {
activeObjLastChild.start += activeRuleSkipText;
} else {
if (!activeObj.children) {
activeObj.children = [];
}
activeObj.children.push(this.createDefaultTextObject(activeRuleSkipText));
}
this.move(activeRuleSkipText.length);
continue;
}
}
if (activeRule.end) {
const activeRuleText = this.matchEnd(activeObj, activeRule);
if (typeof activeRuleText === "string") {
activeObj.end = activeRuleText;
ruleStack.pop();
objStack.pop();
this.move(activeRuleText.length);
continue;
}
}
}
if (!(activeRule == null ? void 0 : activeRule.exclusive)) {
const matchedRule = this.rules.find((r) => this.matchStart(activeObj, r));
if (matchedRule) {
const matchedRuleText = this.matchStart(activeObj, matchedRule);
if (typeof matchedRuleText === "string") {
const newObj = {
type: matchedRule.name,
start: matchedRuleText,
};
if (!activeObj.children) {
activeObj.children = [];
}
activeObj.children.push(newObj);
if (matchedRule.end) {
ruleStack.push(matchedRule);
objStack.push(newObj);
}
this.move(matchedRuleText.length);
continue;
}
}
}
const ch = this.scan();
if (ch) {
if (activeObjLastChild && activeObjLastChild.type === "text") {
activeObjLastChild.start += ch;
} else {
if (!activeObj.children) {
activeObj.children = [];
}
activeObj.children.push(this.createDefaultTextObject(ch));
}
}
}
return root;
}
}
class ParseRule {
constructor(name, options) {
__publicField(this, "name");
__publicField(this, "exclusive", false);
__publicField(this, "wholeword", false);
__publicField(this, "start");
__publicField(this, "skip");
__publicField(this, "end");
this.name = name;
this.exclusive = !!options.exclusive;
this.wholeword = !!options.wholeword;
this.start = options.start;
this.skip = options.skip;
this.end = options.end;
}
}
var GroovyParseRules = [
new ParseRule("keywordblock", {
start: (cb, text) => {
var _a;
const functionalChildren = (_a = cb.children) == null ? void 0 : _a.filter((c) => c.type !== "whitespace");
const last1 = functionalChildren == null ? void 0 : functionalChildren.at(-1);
const last2 = functionalChildren == null ? void 0 : functionalChildren.at(-2);
const trimmedText = text.trimStart();
if (
(last1 == null ? void 0 : last1.type) === "round" &&
(last2 == null ? void 0 : last2.type) === "keywords" &&
!trimmedText.startsWith("{")
) {
return text.substring(0, text.length - trimmedText.length);
}
return void 0;
},
end: /\n|$|(?=})/,
}),
new ParseRule("blockcomment", { start: "/*", end: "*/", exclusive: true }),
new ParseRule("linecomment", { start: "//", end: /(?=\n)/, exclusive: true }),
new ParseRule("multilinestring", { start: "'''", end: "'''", exclusive: true }),
new ParseRule("multilinestring", { start: '"""', end: '"""', exclusive: true }),
new ParseRule("string", { start: '"', end: '"', skip: '\\"', exclusive: true }),
new ParseRule("string", { start: "'", end: "'", skip: "\\'", exclusive: true }),
new ParseRule("regexp", { start: /\//g, end: /\//g, skip: /'/g, exclusive: true }),
new ParseRule("numeric", { start: /0[xX][0-9a-fA-F]+\b/g }),
new ParseRule("numeric", { start: /[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/g }),
new ParseRule("boolean", { start: /(?:true|false)\b/g }),
new ParseRule("block", { start: "{", end: "}" }),
new ParseRule("square", { start: ["?[", "["], end: "]" }),
new ParseRule("round", { start: "(", end: ")" }),
new ParseRule("dot", { start: /((\?|\*|&)+)?\.(?=[^\d])(\*)?/g }),
new ParseRule("delimiters", { start: [",", ":", ";"] }),
new ParseRule("incdec", { start: ["--", "++", "**"] }),
new ParseRule("operators", {
start: [
"--",
"-=",
"->",
"-",
"::",
"!==",
"!=",
"?:",
"?=",
"?",
"..<",
"..",
"**=",
"**",
"*=",
"*",
"/=",
"/",
"&&",
"&=",
"&",
"%=",
"%",
"^=",
"^",
"++",
"+=",
"+",
"<..<",
"<..",
"<<=",
"<<",
"<=>",
"<=",
"<",
"===",
"==~",
"==",
"=~",
"=",
">=",
">>=",
">>>=",
">>",
">",
"|=",
"||",
"|",
"~",
],
}),
new ParseRule("negation", { start: "!" }),
new ParseRule("keywords", {
start: [
"abstract",
"assert",
"boolean",
"break",
"byte",
"case",
"catch",
"char",
"class",
"continue",
"default",
"def",
"double",
"do",
"else",
"enum",
"extends",
"finally",
"final",
"float",
"for",
"goto",
"if",
"implements",
"import",
"instanceof",
"interface",
"int",
"long",
"native",
"new",
"package",
"private",
"protected",
"public",
"return",
"short",
"static",
"strictfp",
"super",
"switch",
"synchronized",
"throws",
"throw",
"transient",
"try",
"void",
"volatile",
"while",
"with",
],
wholeword: true,
}),
new ParseRule("identifiers", { start: /[a-zA-Z_$][a-zA-Z0-9_$]*\b/g, wholeword: true }),
new ParseRule("whitespace", { start: /\s+/g }),
];
class Formatter {
constructor(rules, options) {
__publicField(this, "rules");
__publicField(this, "options");
this.rules = (rules == null ? void 0 : rules.map((r) => new r(this))) || [];
this.options = options || { width: 80 };
}
format(obj, indent = 0) {
let text = "";
if (obj) {
const formatRule = this.rules.find((r) => r.matches(obj));
if (obj.start !== void 0) {
if (formatRule) {
text += formatRule.formatStart(obj, indent);
}
}
if (formatRule) {
text += formatRule.formatChildren(obj, indent);
}
if (obj.end !== void 0) {
if (formatRule) {
text += formatRule.formatEnd(obj, indent);
}
}
}
return text;
}
}
function padLeft(text, indent) {
return "".padStart(indent * 4) + text;
}
function padRight(text, indent) {
return text + "".padStart(indent * 4);
}
function trimSpacesAndTabsLeft(text) {
return text.replace(/^( |\t)+/, "");
}
function trimSpacesAndTabsRight(text) {
return text.replace(/( |\t)+$/, "");
}
class FormatRule {
constructor(formatter) {
__publicField(this, "formatter");
this.formatter = formatter;
}
matches(cb, siblings) {
return true;
}
afterSelf(nextText, indent) {
return nextText;
}
beforeSelf(prevText, indent, newLine) {
return prevText;
}
beforeChild(childText, indent) {
return childText;
}
formatStart(cb, indent) {
var _a;
return (_a = cb.start) != null ? _a : "";
}
formatEnd(cb, indent) {
var _a;
return (_a = cb.end) != null ? _a : "";
}
allowBreak(cb) {
return false;
}
formatChildren(parent, indent) {
var _a;
return (
((_a = parent == null ? void 0 : parent.children) == null
? void 0
: _a.reduce((res, child, i, children) => {
var _a2, _b, _c, _d;
const childFormatRule = this.formatter.rules.find((r) => r.matches(child));
const parentFormatRule = this.formatter.rules.find((r) => r.matches(parent));
const prevFormatRule = this.formatter.rules.find((r) => r.matches(children[i - 1]));
const nextFormatRule = this.formatter.rules.find((r) => r.matches(children[i + 1]));
let childString = this.formatter.format(child, indent);
if (prevFormatRule) {
childString = prevFormatRule.afterSelf(childString, indent);
}
if (parentFormatRule) {
childString = parentFormatRule.beforeChild(childString, indent);
}
if (nextFormatRule) {
const trimmed = trimSpacesAndTabsRight(res + childString);
const newLine = !trimmed.length || trimSpacesAndTabsRight(res + childString).endsWith("\n");
childString = nextFormatRule.beforeSelf(childString, indent, newLine);
}
const lastLineLength = (_b = (_a2 = res.split("\n").at(-1)) == null ? void 0 : _a2.length) != null ? _b : 0;
const childFirstLineLength = (_d = (_c = childString.split("\n").at(0)) == null ? void 0 : _c.length) != null ? _d : 0;
if (
lastLineLength + childFirstLineLength > this.formatter.options.width &&
(childFormatRule == null ? void 0 : childFormatRule.allowBreak(child))
) {
childString = "\n" + childString.trimStart();
if (parentFormatRule) {
childString = parentFormatRule.beforeChild(childString, indent + 1);
}
}
res += childString;
return res;
}, "")) || ""
);
}
}
class RootFormatRule extends FormatRule {
matches(cb) {
return (cb == null ? void 0 : cb.type) === "root";
}
beforeChild(childText) {
let text = childText;
const trimmedRight = trimSpacesAndTabsRight(text);
if (trimmedRight.endsWith("\n")) {
text = trimmedRight;
}
return text;
}
}
class BaseBlockRule extends FormatRule {
beforeChild(childText, indent) {
let text = childText;
const trimmedLeft = trimSpacesAndTabsLeft(text);
if (trimmedLeft.startsWith("\n")) {
text = "\n" + padLeft(trimmedLeft.trimStart(), indent);
}
const trimmedRight = trimSpacesAndTabsRight(text);
if (trimmedRight.endsWith("\n")) {
text = padRight(trimmedRight, indent);
}
return text;
}
}
class BlockFormatRule extends BaseBlockRule {
matches(cb) {
return (cb == null ? void 0 : cb.type) === "block";
}
beforeSelf(prevText, indent, newLine) {
if (!newLine) {
return prevText.trimEnd() + " ";
} else {
return prevText;
}
}
formatEnd(cb, indent) {
return cb.end ? padLeft(cb.end, indent) : "";
}
formatChildren(cb, indent) {
let blockText = super.formatChildren(cb, indent + 1);
blockText = blockText.trim();
return "\n" + padLeft(blockText, indent + 1) + "\n";
}
}
class KeywordBlockFormatRule extends BlockFormatRule {
matches(cb) {
return (cb == null ? void 0 : cb.type) === "keywordblock";
}
formatStart() {
return "{";
}
formatEnd(cb, indent) {
return padLeft("}\n", indent);
}
}
class InlineBlockFormatRule extends BaseBlockRule {
matches(cb) {
return (cb == null ? void 0 : cb.type) === "round" || (cb == null ? void 0 : cb.type) === "square";
}
isMultiline(cb) {
var _a;
return (_a = cb.children) == null
? void 0
: _a.some((child) => {
var _a2;
return (_a2 = child.start) == null ? void 0 : _a2.includes("\n");
});
}
beforeSelf(prevText, indent, newLine) {
if (newLine) {
return trimSpacesAndTabsRight(prevText);
} else {
return prevText;
}
}
formatEnd(cb, indent) {
var _a;
if (this.isMultiline(cb)) {
return cb.end ? padLeft(cb.end, indent) : "";
} else {
return (_a = cb.end) != null ? _a : "";
}
}
formatChildren(cb, indent) {
if (this.isMultiline(cb)) {
let blockText = super.formatChildren(cb, indent + 1);
blockText = blockText.trim();
return "\n" + padLeft(blockText, indent + 1) + "\n";
} else {
const blockText = super.formatChildren(cb, indent);
return blockText.trim();
}
}
}
class DotSyntaxFormatRule extends FormatRule {
matches(cb) {
return (cb == null ? void 0 : cb.type) === "dot";
}
beforeSelf(prevText, indent, newLine) {
if (newLine) {
return padRight(trimSpacesAndTabsRight(prevText), indent + 1);
} else {
return prevText;
}
}
}
class OperatorsRule extends FormatRule {
matches(cb) {
return (cb == null ? void 0 : cb.type) === "operators";
}
beforeSelf(prevText) {
return trimSpacesAndTabsRight(prevText) + " ";
}
afterSelf(nextText) {
return " " + nextText.trimStart();
}
allowBreak() {
return true;
}
}
class DelimitersRule extends FormatRule {
matches(cb) {
return (cb == null ? void 0 : cb.type) === "delimiters";
}
beforeSelf(prevText) {
return trimSpacesAndTabsRight(prevText);
}
afterSelf(nextText) {
return " " + trimSpacesAndTabsLeft(nextText);
}
}
class KeywordRule extends FormatRule {
matches(cb) {
return (cb == null ? void 0 : cb.type) === "keywords";
}
beforeSelf(prevText, indent, newLine) {
if (!newLine) {
return prevText.trimEnd() + " ";
} else {
return prevText;
}
}
afterSelf(nextText) {
return " " + nextText.trimStart();
}
allowBreak() {
return true;
}
}
var GroovyFormatRules = [
RootFormatRule,
BlockFormatRule,
KeywordBlockFormatRule,
InlineBlockFormatRule,
DotSyntaxFormatRule,
KeywordRule,
OperatorsRule,
DelimitersRule,
FormatRule,
];
function index(groovyCode, options) {
const parser = new Parser(groovyCode, GroovyParseRules);
const parsingResult = parser.parse();
const formatter = new Formatter(GroovyFormatRules, options);
return formatter.format(parsingResult);
}
export { index as default };