-
-
Notifications
You must be signed in to change notification settings - Fork 225
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
Shorten assignments to itself #230
Changes from 13 commits
3344baa
a4d10bc
c666da1
c6ec99d
6b913fe
c1aa0ca
6dfe218
6813842
5ddc5f8
2c7a0d5
4f7e8eb
64ed8d9
8c0a456
6eaded6
edd236f
b058bd0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,6 +28,62 @@ module.exports = ({ types: t }) => { | |
const or = (a, b) => t.logicalExpression("||", a, b); | ||
const and = (a, b) => t.logicalExpression("&&", a, b); | ||
|
||
const operators = new Set([ | ||
'+', '-', '*', '%', | ||
'<<', '>>', '>>>', | ||
'&', '|', '^', '/', | ||
'**' | ||
]); | ||
|
||
const updateOperators = new Set([ | ||
'+', '-' | ||
]); | ||
|
||
function isEqual(arr1, arr2) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ugh, good catch. So the reason it works is because property names are resolved RTL so There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is specific to this feature, we should have a comment or change name to indicate that, since this is at the top level (nearly). |
||
return arr1.every((value, index) => { | ||
return String(value) === String(arr2[index]); | ||
}); | ||
} | ||
|
||
function getName(node) { | ||
if (node.type === 'ThisExpression') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: why not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really care; I would just want us to be consistent in the entire codebase. Do we want abstractions over performance or vice versa? Ditto with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's also about being able to use type aliases. So There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless we start compiling |
||
return 'this'; | ||
} | ||
if (node.type === 'Super') { | ||
return 'super'; | ||
} | ||
if (node.type === 'NullLiteral') { | ||
return 'null'; | ||
} | ||
// augment identifiers so that they don't match | ||
// string/number literals | ||
// but still match against each other | ||
return node.name | ||
? node.name + '_' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems a bit hacky -- would it make sense to return an object with "type" and "value" properties instead? Also, since the type of if (t.isIdentifier(node)) {
return {'type': 'id', 'value': node.name};
} else if (t.isLiteral(node)) {
return {'type': 'lit', 'value': node.value};
} else {
return undefined;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about object-based abstraction but wasn't sure about perf. impact of all the "unnecessary" intermediate objects. Definitely would be less hackish. The reason I went with duck-typing instead of explicit I just checked and I see that
Let me clean this up a little. |
||
: node.value /* Literal */; | ||
} | ||
|
||
function getPropNames(path) { | ||
if (!path.isMemberExpression()) { | ||
return; | ||
} | ||
|
||
let obj = path.get('object'); | ||
|
||
const prop = path.get('property'); | ||
const propNames = [getName(prop.node)]; | ||
|
||
while (obj.type === 'MemberExpression') { | ||
const node = obj.get('property').node; | ||
if (node) { | ||
propNames.push(getName(node)); | ||
} | ||
obj = obj.get('object'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: It's probably faster/clearer to use nodes instead of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's useful to have path in many places especially when there is a bug... we can optimize to nodes later when we have it correct. path has more information and we might not know when we have a bug in the code. I faced issues where some information from path(at least parentPath) would be required to solve the issue. |
||
} | ||
propNames.push(getName(obj.node)); | ||
|
||
return propNames; | ||
} | ||
const OP_AND = (input) => input === "&&"; | ||
const OP_OR = (input) => input === "||"; | ||
|
||
|
@@ -194,6 +250,57 @@ module.exports = ({ types: t }) => { | |
} | ||
}, | ||
|
||
AssignmentExpression(path) { | ||
|
||
const rightExpr = path.get('right'); | ||
const leftExpr = path.get('left'); | ||
|
||
const canBeUpdateExpression = ( | ||
rightExpr.get('right').isNumericLiteral() && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the behavior if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be ok - gives a path anyway? http://astexplorer.net/#/qyjXEtbUxr |
||
rightExpr.get('right').node.value === 1 && | ||
updateOperators.has(rightExpr.node.operator)); | ||
|
||
if (leftExpr.isMemberExpression()) { | ||
|
||
const leftPropNames = getPropNames(leftExpr); | ||
const rightPropNames = getPropNames(rightExpr.get('left')); | ||
|
||
if (!leftPropNames || | ||
leftPropNames.includes(undefined) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We bail out whenever we can't resolve "name" of one of the properties in the chain; e.g. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
!rightPropNames || | ||
rightPropNames.includes(undefined) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. indexOf |
||
!operators.has(rightExpr.node.operator) || | ||
!isEqual(leftPropNames, rightPropNames)) { | ||
return; | ||
} | ||
} | ||
else { | ||
if (!rightExpr.isBinaryExpression() || | ||
!operators.has(rightExpr.node.operator) || | ||
leftExpr.node.name !== rightExpr.node.left.name) { | ||
return; | ||
} | ||
} | ||
|
||
let newExpression; | ||
|
||
// special case x=x+1 --> ++x | ||
if (canBeUpdateExpression) { | ||
newExpression = t.updateExpression( | ||
rightExpr.node.operator + rightExpr.node.operator, | ||
t.clone(leftExpr.node), | ||
true /* prefix */); | ||
} | ||
else { | ||
newExpression = t.assignmentExpression( | ||
rightExpr.node.operator + '=', | ||
t.clone(leftExpr.node), | ||
t.clone(rightExpr.node.right)); | ||
} | ||
|
||
path.replaceWith(newExpression); | ||
}, | ||
|
||
ConditionalExpression: { | ||
enter: [ | ||
// !foo ? 'foo' : 'bar' -> foo ? 'bar' : 'foo' | ||
|
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 need to consider splitting plugins into multiple modules. This kind of mixing of helpers and supplemental stuff is not great ^. I think we could even strive for 1 type-of-transform per file.