New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test: add parameter default test to renamer #10055
Conversation
I think that, in order to fix this, we need to change this line: instead of running |
Hi Nicolo, Thanks for your reply and I have drafted a WIP commit. However, after I add another test case let a = "outside";
function j(g = a) {
return g;
} it successfully fails my WIP draft commit though the commit works good on those two testcase. I would work on this test case tomorrow. And I believe this case would also improve code coverage on the |
fa11361
to
07bd17e
Compare
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/11164/ |
Hi Nicolo, I have polished my work and it should be ready to review. On this example, let a = "outside";
function j(g = a) {
return g;
} I think it is expected since the binding scope is not always same with the |
const params = path.get("params"); | ||
for (let i = 0; i < params.length; i++) { | ||
const param = params[i]; | ||
if (param.isAssignmentPattern()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not enough; someone could also do this: (which is currently broken anyway)
let name;
function fn({ [name]: value }) {
let name;
}
or
let name;
function fn({ key = name }) {
let name;
}
if (!state.paramDefaultOuterBinding) { | ||
if ( | ||
right.isIdentifier() && | ||
right.node.body.name === oldName && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right.node.body.name === oldName && | |
right.node.name === oldName && |
Identifiers don't have body
, right?
Also, this should have been caught by a test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh, good catch. Fixed and testcase is added.
if ( | ||
right.isIdentifier() && | ||
right.node.body.name === oldName && | ||
!isSafeBinding(scope, right.node.body) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!isSafeBinding(scope, right.node.body) | |
!isSafeBinding(scope, right.node) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 840a26e.
Please write a few more tests, to ensure that everything works 🙂
Yeah, it is ok 👍 |
Hi Nicolo,
I have crafted some examples and it turns out this "body/whole" approach would not cover some edge cases. In principal, what's missing here is that babel-traverse has not modeled the implicit intermediate scope for function parameters. Examples of different categoriesisolated scopeOn the following example, let a = "outside"
f(g = () => a) {
let a = "inside";
}
same scopeBesides new scope, another category is expressions in various binding patterns. For example // computed property in `ObjectBindingPattern`
function l([{
[a]: g
}]) {}
// expression in `KeyedBindingInitializer`
function k([{
g = a
}]) {
let a = "inside";
return g;
} These expressions do not introduce a new scope. So the nested binding patternsPerhaps the most complicated examples comes from nested binding patterns. On this example function p(a, g = (a = a) => a) {
g(a);
} There are two The "body/whole" assumes that if New approachSo today I have tried a different approach other than "body/whole" to solve these. For those in separated scope, I patched the For those expressions in the same scope, I added logic in For the third cases, I think we should should do a recursive revised I have pushed a working edition of my new approach, it is still proof-of-concept and, of course need refactoring before ready to merge. I would like to discuss this idea before I go too far. Thank you for your patience. |
Ping @nicolo-ribaudo. :) |
@@ -45,6 +45,20 @@ function gatherNodeParts(node: Object, parts: Array) { | |||
} | |||
} | |||
|
|||
function isScopeInFunctionParameter(scope) { | |||
//todo: check arrow function expression or any util method? | |||
if (scope.parent && scope.parent.block.type === "FunctionDeclaration") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be any function kind. It should be t.isFunction(scope.parent.block)
//todo: check arrow function expression or any util method? | ||
if (scope.parent && scope.parent.block.type === "FunctionDeclaration") { | ||
const fdPath = scope.path.findParent( | ||
path => path.type === "FunctionDeclaration", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here
); | ||
if (fdPath) { | ||
return !!fdPath.findParent(path => { | ||
return fdPath.node.params.includes(path.node); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that I understand this. If path.node
must be inside the parameters of fdPath
, why are we searching it in the parents of fdPath
?
if (!scope) { | ||
break; | ||
} | ||
} while (true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we keep this as while ((scope = scope.parent))
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, I have refactored this part.
(path.type === "ObjectProperty" && path.node.computed) | ||
); | ||
}); | ||
const fdPath = path.findParent(path => path.type === "FunctionDeclaration"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- We should only compute this is
computableNode
has been found - Can we share lines 12-17 with the same code in the
isScopeInFunctionParameter
function?
function isInFunctionParameterScope(path) { | ||
const computableNode = path.findParent(path => { | ||
return ( | ||
path.type === "AssignmentPattern" || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT about using path.parentPath.isAssignmentPattern({ right: path.node })
instead?
I don't think that it would change anything, but the left
of an assignment isn't computable anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT about using path.parentPath.isAssignmentPattern({ right: path.node }) instead?
Good point. I have refactored this part and feel horrible on what I was writing a month ago.
When the scope path is a function declaration, check possible outer bindings in the default parameter assignment expression. They should not be renamed in this case.
0e71dc3
to
4151066
Compare
while ( | ||
p.parentPath && | ||
!p.parentPath.isAssignmentPattern({ right: p.node }) && | ||
!p.isObjectProperty({ computed: true }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should only stop if we were inside the key, not if we were inside the "value"/pattern of a computed property.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reworked on #10912 to get rid of the spaghetti. 😝
Closing this PR in favor of #10912. |
This is a follow-up to #10053 (review)
Expected:
Renamer
should not rename the bindings in the function default parameter if it is borrowed from parent scopeRenamer
should rename the bindings in the function default parameter if it is borrowed from theparam
binding in the function scope.Actual:
Renamer
renames the bindings in the function default parameter even if it is borrowed from parent scopeRenamer
renames the bindings in the function default parameter when it is borrowed from theparam
binding in the function scope.On the test example, we have renamer to rename
a
toz
onFunctionDeclaration
visitor.In the first case,
The Renamer also rename
() => a
to() => z
. This behavior is wrong sincea
is defined on global scope.In the second case,
The renamer should also
() => a
to() => z
sincea
is aparam
binding now.If we agree on the correctness of my proposed behavior, then this PR reveals a bug in
renamer
and should be fixed in later release.Note that CI will break since the master does not conform to this behavior.