@rawwee/prettier-plugin-twig-melody
Version:
Prettier Plugin for Twig/Melody (Enhanced Fork)
301 lines (271 loc) • 12.2 kB
JavaScript
;
const { printSequenceExpression } = require("./print/SequenceExpression.js");
const { printBinaryExpression } = require("./print/BinaryExpression.js");
const {
printConditionalExpression
} = require("./print/ConditionalExpression.js");
const { printElement } = require("./print/Element.js");
const { printAttribute } = require("./print/Attribute.js");
const { printIdentifier } = require("./print/Identifier.js");
const { printExpressionStatement } = require("./print/ExpressionStatement.js");
const { printMemberExpression } = require("./print/MemberExpression.js");
const { printFilterExpression } = require("./print/FilterExpression.js");
const { printObjectExpression } = require("./print/ObjectExpression.js");
const { printObjectProperty } = require("./print/ObjectProperty.js");
const { printCallExpression } = require("./print/CallExpression.js");
const { printTestExpression } = require("./print/TestExpression.js");
const { printUnaryExpression } = require("./print/UnaryExpression.js");
const { printUnarySubclass } = require("./print/UnarySubclass.js");
const { printTextStatement } = require("./print/TextStatement.js");
const { printStringLiteral } = require("./print/StringLiteral.js");
const { printArrayExpression } = require("./print/ArrayExpression.js");
const { printSliceExpression } = require("./print/SliceExpression.js");
const { printUseStatement } = require("./print/UseStatement.js");
const { printAliasExpression } = require("./print/AliasExpression.js");
const { printBlockStatement } = require("./print/BlockStatement.js");
const { printSpacelessBlock } = require("./print/SpacelessBlock.js");
const { printAutoescapeBlock } = require("./print/AutoescapeBlock.js");
const { printFlushStatement } = require("./print/FlushStatement.js");
const { printIncludeStatement } = require("./print/IncludeStatement.js");
const { printIfStatement } = require("./print/IfStatement.js");
const { printMountStatement } = require("./print/MountStatement.js");
const { printForStatement } = require("./print/ForStatement.js");
const { printSetStatement } = require("./print/SetStatement.js");
const { printDoStatement } = require("./print/DoStatement.js");
const { printExtendsStatement } = require("./print/ExtendsStatement.js");
const { printEmbedStatement } = require("./print/EmbedStatement.js");
const { printImportDeclaration } = require("./print/ImportDeclaration.js");
const { printFromStatement } = require("./print/FromStatement.js");
const { printTwigComment } = require("./print/TwigComment.js");
const { printHtmlComment } = require("./print/HtmlComment.js");
const { printDeclaration } = require("./print/Declaration.js");
const { printGenericTwigTag } = require("./print/GenericTwigTag.js");
const { printGenericToken } = require("./print/GenericToken.js");
const {
printMacroDeclarationStatement
} = require("./print/MacroDeclarationStatement.js");
const {
printFilterBlockStatement
} = require("./print/FilterBlockStatement.js");
const {
printVariableDeclarationStatement
} = require("./print/VariableDeclarationStatement.js");
const {
printNamedArgumentExpression
} = require("./print/NamedArgumentExpression.js");
const {
isWhitespaceNode,
isHtmlCommentEqualTo,
isTwigCommentEqualTo,
getPluginPathsFromOptions,
loadPlugins
} = require("./util");
const { ORIGINAL_SOURCE, VUE_ALPINE_REPLACEMENTS } = require("./parser");
const printFunctions = {};
const applyPlugin = loadedPlugin => {
if (loadedPlugin && loadedPlugin.printers) {
for (const printerName of Object.keys(loadedPlugin.printers)) {
printFunctions[printerName] = loadedPlugin.printers[printerName];
}
}
};
const applyPlugins = options => {
const pluginPaths = getPluginPathsFromOptions(options);
const loadedPlugins = loadPlugins(pluginPaths);
loadedPlugins.forEach(plugin => {
applyPlugin(plugin);
});
};
const isHtmlIgnoreNextComment = isHtmlCommentEqualTo("prettier-ignore");
const isHtmlIgnoreStartComment = isHtmlCommentEqualTo("prettier-ignore-start");
const isHtmlIgnoreEndComment = isHtmlCommentEqualTo("prettier-ignore-end");
const isTwigIgnoreNextComment = isTwigCommentEqualTo("prettier-ignore");
const isTwigIgnoreStartComment = isTwigCommentEqualTo("prettier-ignore-start");
const isTwigIgnoreEndComment = isTwigCommentEqualTo("prettier-ignore-end");
const isIgnoreNextComment = s =>
isHtmlIgnoreNextComment(s) || isTwigIgnoreNextComment(s);
const isIgnoreRegionStartComment = s =>
isHtmlIgnoreStartComment(s) || isTwigIgnoreStartComment(s);
const isIgnoreRegionEndComment = s =>
isHtmlIgnoreEndComment(s) || isTwigIgnoreEndComment(s);
let originalSource = "";
let vueAlpineReplacements = new Map();
let ignoreRegion = false;
let ignoreNext = false;
const checkForIgnoreStart = node => {
// Keep current "ignoreNext" value if it's true,
// but is not applied in this step yet
ignoreNext =
(ignoreNext && !shouldApplyIgnoreNext(node)) ||
isIgnoreNextComment(node);
ignoreRegion = ignoreRegion || isIgnoreRegionStartComment(node);
};
const checkForIgnoreEnd = node => {
if (ignoreRegion && isIgnoreRegionEndComment(node)) {
ignoreRegion = false;
}
};
const shouldApplyIgnoreNext = node => !isWhitespaceNode(node);
const print = (path, options, print) => {
applyPlugins(options);
const node = path.getValue();
const nodeType = node.constructor.name;
// Try to get the entire original source from AST root
if (node[ORIGINAL_SOURCE]) {
originalSource = node[ORIGINAL_SOURCE];
}
// Try to get Vue/Alpine replacements from AST root
if (node[VUE_ALPINE_REPLACEMENTS]) {
vueAlpineReplacements = node[VUE_ALPINE_REPLACEMENTS];
// Add replacements to options so they're available to print functions
options.vueAlpineReplacements = vueAlpineReplacements;
}
// Ensure replacements are always available from the global variable
if (!options.vueAlpineReplacements && vueAlpineReplacements) {
options.vueAlpineReplacements = vueAlpineReplacements;
}
if (options.twigPrintWidth) {
options.printWidth = options.twigPrintWidth;
}
checkForIgnoreEnd(node);
const useOriginalSource =
(shouldApplyIgnoreNext(node) && ignoreNext) || ignoreRegion;
const hasPrintFunction = printFunctions[nodeType];
// Happy path: We have a formatting function, and the user wants the
// node formatted
if (!useOriginalSource && hasPrintFunction) {
checkForIgnoreStart(node);
const result = printFunctions[nodeType](node, path, print, options);
return result;
} else if (!hasPrintFunction) {
console.warn(`No print function available for node type "${nodeType}"`);
}
checkForIgnoreStart(node);
// Fallback: Use the node's loc property with the
// originalSource property on the AST root
if (canGetSubstringForNode(node)) {
return getSubstringForNode(node);
}
return "";
};
const getSubstringForNode = node =>
originalSource.substring(node.loc.start.index, node.loc.end.index);
const canGetSubstringForNode = node =>
originalSource &&
node.loc &&
node.loc.start &&
node.loc.end &&
node.loc.start.index &&
node.loc.end.index;
/**
* Prettier printing works with a so-called FastPath object, which is
* passed into many of the following methods through a "path" argument.
* This is basically a stack, and the way to do do recursion in Prettier
* is through this path object.
*
* For example, you might expect to write something like this:
*
* BinaryExpression.prototype.prettyPrint = _ => {
* return concat([
* this.left.prettyPrint(),
* " ",
* this.operator,
* " ",
* this.right.prettyPrint()
* ]);
* };
*
* Here, the prettyPrint() method of BinaryExpression calls the prettyPrint()
* methods of the left and right operands. However, it actually has to be
* done like this in Prettier plugins:
*
* BinaryExpression.prototype.prettyPrint = (path, print) => {
* const docs = [
* path.call(print, "left"),
* " ",
* this.operator,
* " ",
* path.call(print, "right")
* ];
* return concat(docs);
* };
*
* The first argument to path.call() seems to always be the print function
* that is passed in (a case of bad interface design and over-complication?),
* at least I have not found any other instance. The arguments after that are
* field names that are pulled from the node and put on the stack for the
* next processing step(s) => this is how recursion is done.
*
*/
printFunctions["SequenceExpression"] = printSequenceExpression;
printFunctions["ConstantValue"] = node => {
return node.value;
};
printFunctions["StringLiteral"] = printStringLiteral;
printFunctions["Identifier"] = printIdentifier;
printFunctions["UnaryExpression"] = printUnaryExpression;
printFunctions["BinaryExpression"] = printBinaryExpression;
printFunctions["BinarySubclass"] = printBinaryExpression;
printFunctions["UnarySubclass"] = printUnarySubclass;
printFunctions["TestExpression"] = printTestExpression;
printFunctions["ConditionalExpression"] = printConditionalExpression;
printFunctions["Element"] = printElement;
printFunctions["Attribute"] = printAttribute;
printFunctions["PrintTextStatement"] = printTextStatement;
printFunctions["PrintExpressionStatement"] = printExpressionStatement;
printFunctions["MemberExpression"] = printMemberExpression;
printFunctions["FilterExpression"] = printFilterExpression;
printFunctions["ObjectExpression"] = printObjectExpression;
printFunctions["ObjectProperty"] = printObjectProperty;
// Return value has to be a string
const returnNodeValue = node => "" + node.value;
printFunctions["Fragment"] = (node, path, print) => {
return path.call(print, "value");
};
printFunctions["NumericLiteral"] = returnNodeValue;
printFunctions["BooleanLiteral"] = returnNodeValue;
printFunctions["NullLiteral"] = () => "null";
printFunctions["ArrayExpression"] = printArrayExpression;
printFunctions["CallExpression"] = printCallExpression;
printFunctions["NamedArgumentExpression"] = printNamedArgumentExpression;
printFunctions["SliceExpression"] = printSliceExpression;
printFunctions["UseStatement"] = printUseStatement;
printFunctions["AliasExpression"] = printAliasExpression;
printFunctions["BlockStatement"] = printBlockStatement;
printFunctions["SpacelessBlock"] = printSpacelessBlock;
printFunctions["AutoescapeBlock"] = printAutoescapeBlock;
printFunctions["FlushStatement"] = printFlushStatement;
printFunctions["IncludeStatement"] = printIncludeStatement;
printFunctions["IfStatement"] = printIfStatement;
printFunctions["MountStatement"] = printMountStatement;
printFunctions["ForStatement"] = printForStatement;
printFunctions["BinaryConcatExpression"] = printBinaryExpression;
printFunctions["SetStatement"] = printSetStatement;
printFunctions[
"VariableDeclarationStatement"
] = printVariableDeclarationStatement;
printFunctions["DoStatement"] = printDoStatement;
printFunctions["ExtendsStatement"] = printExtendsStatement;
printFunctions["EmbedStatement"] = printEmbedStatement;
printFunctions["FilterBlockStatement"] = printFilterBlockStatement;
printFunctions["ImportDeclaration"] = printImportDeclaration;
printFunctions["FromStatement"] = printFromStatement;
printFunctions["MacroDeclarationStatement"] = printMacroDeclarationStatement;
printFunctions["TwigComment"] = printTwigComment;
printFunctions["HtmlComment"] = printHtmlComment;
printFunctions["Declaration"] = printDeclaration;
printFunctions["GenericTwigTag"] = (node, path, print, options) => {
const tagName = node.tagName;
if (printFunctions[tagName + "Tag"]) {
// Give the user the chance to implement a custom
// print function for certain generic Twig tags
return printFunctions[tagName + "Tag"](node, path, print, options);
}
return printGenericTwigTag(node, path, print, options);
};
printFunctions["GenericToken"] = printGenericToken;
// Fallbacks
printFunctions["String"] = s => s;
module.exports = {
print
};