- Notifications
You must be signed in to change notification settings - Fork 300
Pure component destructuring #94
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
Changes from 8 commits
5615873 4115b5b 51fe8fb 4408b4e 536845e 1bfa3de 9e3e246 a0c0489 c74bbae 50d3a56 3ed45f9 79cdcfe f9e7858 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 |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| 'use strict'; | ||
| | ||
| var React = require('React'); | ||
| | ||
| function doSomething(props) { return props; } | ||
| | ||
| class ShouldDestsructure extends React.Component { | ||
| render() { | ||
| return <div className={this.props.foo} />; | ||
| } | ||
| } | ||
| | ||
| class ShouldDestructureAndRemoveDuplicateDeclaration extends React.Component { | ||
| render() { | ||
| const fizz = { buzz: 'buzz' }; | ||
| const bar = this.props.bar; | ||
| const baz = this.props.bizzaz; | ||
| const buzz = fizz.buzz; | ||
| return <div className={this.props.foo} bar={bar} baz={baz} buzz={buzz} />; | ||
| } | ||
| } | ||
| | ||
| class UsesThisDotProps extends React.Component { | ||
| render() { | ||
| doSomething(this.props); | ||
| return <div className={this.props.foo} />; | ||
| } | ||
| } | ||
| | ||
| class DestructuresThisDotProps extends React.Component { | ||
| // would be nice to destructure in this case | ||
| render() { | ||
| const { bar } = this.props; | ||
| return <div className={this.props.foo} bar={bar} />; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| 'use strict'; | ||
| | ||
| var React = require('React'); | ||
| | ||
| function doSomething(props) { return props; } | ||
| | ||
| function ShouldDestsructure( | ||
| { | ||
| foo, | ||
| }, | ||
| ) { | ||
| return <div className={foo} />; | ||
| } | ||
| | ||
| function ShouldDestructureAndRemoveDuplicateDeclaration( | ||
| { | ||
| bar, | ||
| bizzaz, | ||
| foo, | ||
| }, | ||
| ) { | ||
| const fizz = { buzz: 'buzz' }; | ||
| const baz = bizzaz; | ||
| const buzz = fizz.buzz; | ||
| return <div className={foo} bar={bar} baz={baz} buzz={buzz} />; | ||
| } | ||
| | ||
| function UsesThisDotProps(props) { | ||
| doSomething(props); | ||
| return <div className={props.foo} />; | ||
| } | ||
| | ||
| function DestructuresThisDotProps(props) { | ||
| const { bar } = props; | ||
| return <div className={props.foo} bar={bar} />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -15,6 +15,7 @@ module.exports = function(file, api, options) { | |
| const ReactUtils = require('./utils/ReactUtils')(j); | ||
| | ||
| const useArrows = options.useArrows || false; | ||
| const destructureEnabled = options.destructure || false; | ||
| const silenceWarnings = options.silenceWarnings || false; | ||
| const printOptions = options.printOptions || { | ||
| quote: 'single', | ||
| | @@ -85,31 +86,83 @@ module.exports = function(file, api, options) { | |
| return identifier; | ||
| }; | ||
| | ||
| const canDestructure = path => path | ||
| .find(j.Identifier, { | ||
| name: 'props' | ||
| }) | ||
| .filter(p => p.parentPath.parentPath.value.type !== 'MemberExpression') | ||
| .size() === 0; | ||
| | ||
| const createShorthandProperty = j => prop => { | ||
| const property = j.property('init', j.identifier(prop), j.identifier(prop)); | ||
| property.shorthand = true; | ||
| return property; | ||
| }; | ||
| | ||
| const isDuplicateDeclaration = path => { | ||
| if (path && path.value && path.value.id && path.value.init) { | ||
| return path.value.id.name === path.value.init.name; | ||
| } | ||
| return false; | ||
| }; | ||
| | ||
| const destructureProps = body => { | ||
| const toDestructure = body.find(j.MemberExpression, { | ||
| object: { | ||
| name: 'props' | ||
| } | ||
| }); | ||
| if (toDestructure) { | ||
| const propNames = new Set(); | ||
| toDestructure.replaceWith(path => { | ||
| const propName = path.value.property.name; | ||
| propNames.add(propName); | ||
| 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. While this approach (adding names to the set while replacing) is efficient, it introduces some potential shadowing issues. For example: Input: class Shadowing extends React.Component { render() { let style = { color: 'black' }; return <div style={{...style, ...this.props.style}} />; } }Output: function Shadowing( { style, }, ) { let style = { color: 'black' }; return <div style={{...style, ...style}} />; }I think a better way here is to scan the whole function twice (using | ||
| return j.identifier(propName); | ||
| }); | ||
| if (propNames.size > 0) { | ||
| const assignments = body.find(j.VariableDeclarator); | ||
| const duplicateAssignments = assignments.filter(isDuplicateDeclaration); | ||
| duplicateAssignments.remove(); | ||
| return j.objectExpression(Array.from(propNames).map(createShorthandProperty(j))); | ||
| } | ||
| } | ||
| return false; | ||
| }; | ||
| | ||
| const findPropsTypeAnnotation = body => { | ||
| const property = body.find(isPropsProperty); | ||
| | ||
| return property && property.typeAnnotation.typeAnnotation; | ||
| }; | ||
| | ||
| const buildPureComponentFunction = (name, body, typeAnnotation) => | ||
| j.functionDeclaration( | ||
| j.identifier(name), | ||
| [buildIdentifierWithTypeAnnotation('props', typeAnnotation)], | ||
| body | ||
| ); | ||
| | ||
| const buildPureComponentArrowFunction = (name, body, typeAnnotation) => | ||
| j.variableDeclaration( | ||
| 'const', [ | ||
| j.variableDeclarator( | ||
| j.identifier(name), | ||
| j.arrowFunctionExpression( | ||
| [buildIdentifierWithTypeAnnotation('props', typeAnnotation)], | ||
| body | ||
| ) | ||
| ), | ||
| ] | ||
| ); | ||
| const build = ({ functionType }) => (name, body, typeAnnotation, destructure) => { | ||
| ||
| const identifier = j.identifier(name); | ||
| const propsIdentifier = buildIdentifierWithTypeAnnotation('props', typeAnnotation); | ||
| const propsArg = [(destructure && destructureProps(j(body))) || propsIdentifier]; | ||
| ||
| if (functionType === 'fn') { | ||
| return j.functionDeclaration( | ||
| identifier, | ||
| propsArg, | ||
| body | ||
| ); | ||
| } else { | ||
| return j.variableDeclaration( | ||
| 'const', [ | ||
| j.variableDeclarator( | ||
| identifier, | ||
| j.arrowFunctionExpression( | ||
| propsArg, | ||
| body | ||
| ) | ||
| ), | ||
| ] | ||
| ); | ||
| } | ||
| }; | ||
| | ||
| const buildPureComponentFunction = build({ functionType: 'fn' }); | ||
| | ||
| const buildPureComponentArrowFunction = build({ functionType: 'arrow' }); | ||
| | ||
| const buildStatics = (name, properties) => properties.map(prop => ( | ||
| j.expressionStatement( | ||
| | @@ -154,17 +207,22 @@ module.exports = function(file, api, options) { | |
| const renderBody = renderMethod.value.body; | ||
| const propsTypeAnnotation = findPropsTypeAnnotation(p.value.body.body); | ||
| const statics = p.value.body.body.filter(isStaticProperty); | ||
| const destructure = destructureEnabled && canDestructure(j(renderMethod)); | ||
| | ||
| replaceThisProps(renderBody); | ||
| if (destructureEnabled && !destructure) { | ||
| console.warn(`Unable to destructure ${name} props. Render method references \`this.props\`.`); | ||
| } | ||
| | ||
| replaceThisProps(renderBody); | ||
| | ||
| if (useArrows) { | ||
| return [ | ||
| buildPureComponentArrowFunction(name, renderBody, propsTypeAnnotation), | ||
| buildPureComponentArrowFunction(name, renderBody, propsTypeAnnotation, destructure), | ||
| ...buildStatics(name, statics) | ||
| ]; | ||
| } else { | ||
| return [ | ||
| buildPureComponentFunction(name, renderBody, propsTypeAnnotation), | ||
| buildPureComponentFunction(name, renderBody, propsTypeAnnotation, destructure), | ||
| ...buildStatics(name, statics) | ||
| ]; | ||
| } | ||
| | ||
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.
Could you rename this option to
destructuring? https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment