babel-plugin-transform-last-statement
A Babel plugin to transform the last statements of functions (as well as the program itself if enabled via its options). By default, it'll turn them into return statements.
This is part of an [attempt to push template literals as far as possible towards being used as other template formats (like nunjucks
, pug
...). Running functions inside the template expressions was a good step (see process-template-literals and omniformat). It made expressing conditionals, as well as loops,especially, cumbersome to read if riddled with return
. This transform allows a lighter syntax like:
function template(data) {
`<ul>
${() => {
for(var i = 0; i++; i <5){
`<li>${i}</li>`
}
}}
</ul>`
}
// Will get turned into the more verbose:
function template(data) {
return `<ul>
${() => {
var _result = [];
for(var i = 0; i++; i <5){
_result.push(`<li>${i}</li>`);
}
return _result;
}}
</ul>`
}
Its use is not limited to templates and it will work on any kind of functions (including class and object methods):
// Input
function template(data) {
if (data.onlyFirstname) {
`${data.firstName}`
}
`${data.firstname} - ${data.lastname}`
}
// Will become the following. Note the first if
// doesn't return as we can't make any assumptions
// about whether it should return or not.
function template(data) {
if (data.onlyFirstname) {
`${data.firstName}`
}
return `${data.firstname} - ${data.lastname}`
}
And of course, you can still explicitly return as you please.
Usage
Install the plugin with your favourite package manager. For example, with NPM:
npm install --save-dev babel-plugin-transform-last-statement
Then in your Babel configuration, add it to the list of plugins:
{
plugins:['transform-last-statement']
}
Options
-
topLevel
: By default, the plugin only processes the last statements of functions. Toggling that option on withtopLevel: true
will also have it process the last statement of the file too.
Processing details
The transform traverses the functions statements, last to first, to determine which to process. It looks into blocks like if
,else
, switch
, try
..., gathers the results of loops for
, do{...}while
... into arrays, and stops the processing if a return
, throw
, continue
was found.
It transforms the last statement, making the function return
it. It ignores:
- function declarations, as they might be written last for readability, taking advantage of hoisting
- variable declarations, because I couldn't see a valid use case for declaring a variable as last statement. Either
return
directly, or do something with the variable in further statements. - empty statements (for ex a line with only a semi-colon) are ignored, as it doesn't help readability to spot a
;
that'll get transformed inreturn;
and make the function return undefined instead of its last actual value. -
break
statements, though they are markers for knowing when to return inswitch
statements.
The transform does not:
-
take care of eliminating dead code, like a
break
left after an injectedreturn
. This left to minifiers -
do clever things to detect empty blocks. If you do need that kind of block (I couldn't imagine a use case), you'll need to return explicitly:
function tempalte() { `this won't be returned automatically` if (test){ ; ; } }
See the annotated source for details on how each kind of statements is processed.
Possible future work
- [ ] Refactor the traversal to use a visitor more similar to Babel's regular visitor.
- [ ] Allow customisation of how the last statement is processed. This could allow, for example, to write the expression to a Writable stream
Contributing
I'm happy to consider issues or pull requests. However I can't make any guarantee on how responsive I can be reviewing them
-
For bugs, please provide an (ideally isolated) sample of the code triggering the issue, with the behaviour you'd expect.
-
For new features, please provide samples of input and output code.
-
For code contributions, please make sure:
- that you updated tests. You can either add new
.test.js
file in the__tests__
folder, or a.input.js
file that will get compared with the corresponding.output.js
file in the__tests__/__fixtures__/index
. See theindex.test.js
file if you need to tweak processing for specific tests. - your code is linted (husky should take care of setting up a git-hook before commit)
- your commit messages follow the conventional changelog convention (commitizen can help you there)
These links might also come handy:
-
AST Explorer: For visualizing the AST (pick
babylon7
as parser) and trying out transforms (pickbabelv7
as transform). - Babel AST specifications: List of the nodes that can be part of the AST created by Babel
- Babel handbook: The guide for creating Babel plugins
- that you updated tests. You can either add new