@prettier/plugin-ruby
Version:
prettier plugin for the Ruby programming language
102 lines (91 loc) • 3 kB
JavaScript
const {
align,
breakParent,
concat,
group,
hardline,
ifBreak,
indent,
softline
} = require("../prettier");
const { containsAssignment } = require("../utils");
const inlineEnsureParens = require("../utils/inlineEnsureParens");
const printLoop = (keyword, modifier) => (path, { rubyModifier }, print) => {
const [_predicate, stmts] = path.getValue().body;
// If the only statement inside this while loop is a void statement, then we
// can shorten to just displaying the predicate and then a semicolon.
if (
stmts.body.length === 1 &&
stmts.body[0].type === "void_stmt" &&
!stmts.body[0].comments
) {
return group(
concat([
keyword,
" ",
path.call(print, "body", 0),
ifBreak(softline, "; "),
"end"
])
);
}
const inlineLoop = concat(
inlineEnsureParens(path, [
path.call(print, "body", 1),
` ${keyword} `,
path.call(print, "body", 0)
])
);
// If we're in the modifier form and we're modifying a `begin`, then this is a
// special case where we need to explicitly use the modifier form because
// otherwise the semantic meaning changes. This looks like:
//
// begin
// foo
// end while bar
//
// The above is effectively a `do...while` loop (which we don't have in ruby).
if (modifier && path.getValue().body[1].type === "begin") {
return inlineLoop;
}
const blockLoop = concat([
concat([
`${keyword} `,
align(keyword.length + 1, path.call(print, "body", 0))
]),
indent(concat([softline, path.call(print, "body", 1)])),
concat([softline, "end"])
]);
// If we're disallowing inline loops or if the predicate of the loop contains
// an assignment (in which case we can't know for certain that that
// assignment doesn't impact the statements inside the loop) then we can't
// use the modifier form and we must use the block form.
if (!rubyModifier || containsAssignment(path.getValue().body[0])) {
return concat([breakParent, blockLoop]);
}
return group(ifBreak(blockLoop, inlineLoop));
};
// Technically this is incorrect. A `for` loop actually introduces and modifies
// a local variable that then remains in the outer scope. Additionally, if the
// `each` method was somehow missing from the enumerable (it's possible...),
// then this transformation would fail. However - I've never actually seen a
// `for` loop used in production. If someone actually calls me on it, I'll fix
// this, but for now I'm leaving it.
const printFor = (path, opts, print) =>
group(
concat([
path.call(print, "body", 1),
".each do |",
path.call(print, "body", 0),
"|",
indent(concat([hardline, path.call(print, "body", 2)])),
concat([hardline, "end"])
])
);
module.exports = {
while: printLoop("while", false),
while_mod: printLoop("while", true),
until: printLoop("until", false),
until_mod: printLoop("until", true),
for: printFor
};