diff --git a/package.json b/package.json index bc27a865..8abcefb1 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "eslint": "^2.13.1", "fbjs-scripts": "^0.7.1", "jest": "^17.0.3", - "jscodeshift": "^0.3.30" + "jscodeshift": "^0.3.30", + "path": "^0.12.7" }, "jest": { "globals": { diff --git a/transforms/__testfixtures__/class/class-create-class-naming.output.js b/transforms/__testfixtures__/class/class-create-class-naming.output.js index e7f34ffa..85e93850 100644 --- a/transforms/__testfixtures__/class/class-create-class-naming.output.js +++ b/transforms/__testfixtures__/class/class-create-class-naming.output.js @@ -7,8 +7,10 @@ const React = require('react'); const createReactClass__deprecated = require('createReactClass__deprecated'); const Component = createReactClass__deprecated({ + displayName: 'Component', mixins: [{}], + render() { return
; - } + }, }); diff --git a/transforms/__testfixtures__/class/class-displayName.input.js b/transforms/__testfixtures__/class/class-displayName.input.js new file mode 100644 index 00000000..61aac9ce --- /dev/null +++ b/transforms/__testfixtures__/class/class-displayName.input.js @@ -0,0 +1,31 @@ +const React = require('React'); + +let A = React.createClass({ + mixins: [], + render() { + return
; + }, +}); + +A = React.createClass({ + mixins: [], + render() { + return
; + }, +}); + +const obj = { + B: React.createClass({ + mixins: [], + render() { + return
; + }, + }), +}; + +export default React.createClass({ + mixins: [], + render() { + return
; + }, +}); diff --git a/transforms/__testfixtures__/class/class-displayName.output.js b/transforms/__testfixtures__/class/class-displayName.output.js new file mode 100644 index 00000000..d172e0f8 --- /dev/null +++ b/transforms/__testfixtures__/class/class-displayName.output.js @@ -0,0 +1,41 @@ +const React = require('React'); + +const createReactClass = require('react-create-class'); + +let A = createReactClass({ + displayName: 'A', + mixins: [], + + render() { + return
; + }, +}); + +A = createReactClass({ + displayName: 'A', + mixins: [], + + render() { + return
; + }, +}); + +const obj = { + B: createReactClass({ + displayName: 'B', + mixins: [], + + render() { + return
; + }, + }), +}; + +export default createReactClass({ + displayName: 'class-displayName.input', + mixins: [], + + render() { + return
; + }, +}); diff --git a/transforms/__testfixtures__/class/class-initial-state.input.js b/transforms/__testfixtures__/class/class-initial-state.input.js index b5ecfdaf..e726f672 100644 --- a/transforms/__testfixtures__/class/class-initial-state.input.js +++ b/transforms/__testfixtures__/class/class-initial-state.input.js @@ -199,7 +199,8 @@ var DeferStateInitialization = React.createClass({ var helper = () => {}; -var PassGetInitialState = React.createClass({ // bail out here +// fallback +var PassGetInitialState = React.createClass({ getInitialState() { return this.lol(); }, @@ -213,7 +214,8 @@ var PassGetInitialState = React.createClass({ // bail out here }, }); -var UseGetInitialState = React.createClass({ // bail out here +// fallback +var UseGetInitialState = React.createClass({ getInitialState() { return this.lol(); }, @@ -227,7 +229,8 @@ var UseGetInitialState = React.createClass({ // bail out here }, }); -var UseArguments = React.createClass({ // bail out here +// fallback +var UseArguments = React.createClass({ helper() { console.log(arguments); }, @@ -237,7 +240,8 @@ var UseArguments = React.createClass({ // bail out here }, }); -var ShadowingIssue = React.createClass({ // bail out here +// fallback +var ShadowingIssue = React.createClass({ getInitialState() { const props = { x: 123 }; return { x: props.x }; diff --git a/transforms/__testfixtures__/class/class-initial-state.output.js b/transforms/__testfixtures__/class/class-initial-state.output.js index b6a5c3d2..b682c6af 100644 --- a/transforms/__testfixtures__/class/class-initial-state.output.js +++ b/transforms/__testfixtures__/class/class-initial-state.output.js @@ -225,7 +225,10 @@ class DeferStateInitialization extends React.Component { var helper = () => {}; -var PassGetInitialState = createReactClass({ // bail out here +// fallback +var PassGetInitialState = createReactClass({ + displayName: 'PassGetInitialState', + getInitialState() { return this.lol(); }, @@ -239,7 +242,10 @@ var PassGetInitialState = createReactClass({ // bail out here }, }); -var UseGetInitialState = createReactClass({ // bail out here +// fallback +var UseGetInitialState = createReactClass({ + displayName: 'UseGetInitialState', + getInitialState() { return this.lol(); }, @@ -253,7 +259,10 @@ var UseGetInitialState = createReactClass({ // bail out here }, }); -var UseArguments = createReactClass({ // bail out here +// fallback +var UseArguments = createReactClass({ + displayName: 'UseArguments', + helper() { console.log(arguments); }, @@ -263,7 +272,10 @@ var UseArguments = createReactClass({ // bail out here }, }); -var ShadowingIssue = createReactClass({ // bail out here +// fallback +var ShadowingIssue = createReactClass({ + displayName: 'ShadowingIssue', + getInitialState() { const props = { x: 123 }; return { x: props.x }; diff --git a/transforms/__testfixtures__/class/class-prune-react.output.js b/transforms/__testfixtures__/class/class-prune-react.output.js index e6b305c2..81a2f652 100644 --- a/transforms/__testfixtures__/class/class-prune-react.output.js +++ b/transforms/__testfixtures__/class/class-prune-react.output.js @@ -9,7 +9,9 @@ const SomeMixin = { }; export default createReactClass({ + displayName: 'class-prune-react.input', mixins: [SomeMixin], + render: function() { return null; }, diff --git a/transforms/__testfixtures__/class/class-prune-react2.output.js b/transforms/__testfixtures__/class/class-prune-react2.output.js index 8d900de2..8d052dfb 100644 --- a/transforms/__testfixtures__/class/class-prune-react2.output.js +++ b/transforms/__testfixtures__/class/class-prune-react2.output.js @@ -11,7 +11,9 @@ const SomeMixin = { }; export default createReactClass({ + displayName: 'class-prune-react2.input', mixins: [SomeMixin], + render: function() { return
; }, diff --git a/transforms/__testfixtures__/class/class-prune-react3.output.js b/transforms/__testfixtures__/class/class-prune-react3.output.js index a37feb47..f32b8181 100644 --- a/transforms/__testfixtures__/class/class-prune-react3.output.js +++ b/transforms/__testfixtures__/class/class-prune-react3.output.js @@ -11,10 +11,13 @@ const SomeMixin = { }; export default createReactClass({ + displayName: 'class-prune-react3.input', mixins: [SomeMixin], + propTypes: { foo: PropTypes.string, }, + render: function() { return null; }, diff --git a/transforms/__testfixtures__/class/class-pure-mixin3.output.js b/transforms/__testfixtures__/class/class-pure-mixin3.output.js index a2c47b19..32c17741 100644 --- a/transforms/__testfixtures__/class/class-pure-mixin3.output.js +++ b/transforms/__testfixtures__/class/class-pure-mixin3.output.js @@ -5,6 +5,7 @@ var createReactClass = require('react-create-class'); var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin'); var ComponentWithOnlyPureRenderMixin = createReactClass({ + displayName: 'ComponentWithOnlyPureRenderMixin', mixins: [ReactComponentWithPureRenderMixin], getInitialState: function() { diff --git a/transforms/__testfixtures__/class/class-test2.output.js b/transforms/__testfixtures__/class/class-test2.output.js index 8e175a9b..25df310d 100644 --- a/transforms/__testfixtures__/class/class-test2.output.js +++ b/transforms/__testfixtures__/class/class-test2.output.js @@ -50,6 +50,7 @@ module.exports = class extends React.Component { }; var ComponentWithInconvertibleMixins = createReactClass({ + displayName: 'ComponentWithInconvertibleMixins', mixins: [ReactComponentWithPureRenderMixin, FooBarMixin], getInitialState: function() { @@ -68,6 +69,7 @@ var ComponentWithInconvertibleMixins = createReactClass({ var listOfInconvertibleMixins = [ReactComponentWithPureRenderMixin, FooBarMixin]; var ComponentWithInconvertibleMixins2 = createReactClass({ + displayName: 'ComponentWithInconvertibleMixins2', mixins: listOfInconvertibleMixins, getInitialState: function() { diff --git a/transforms/__testfixtures__/class/class.output.js b/transforms/__testfixtures__/class/class.output.js index cc681f32..34f7fcd0 100644 --- a/transforms/__testfixtures__/class/class.output.js +++ b/transforms/__testfixtures__/class/class.output.js @@ -110,6 +110,7 @@ class MyComponent3 extends React.Component { } var MyComponent4 = createReactClass({ + displayName: 'MyComponent4', foo: callMeMaybe(), render: function() {}, }); diff --git a/transforms/__tests__/class-test.js b/transforms/__tests__/class-test.js index f710cce2..a582c65e 100644 --- a/transforms/__tests__/class-test.js +++ b/transforms/__tests__/class-test.js @@ -57,3 +57,4 @@ defineTest(__dirname, 'class', { 'create-class-module-name': 'createReactClass__deprecated', 'create-class-variable-name': 'createReactClass__deprecated', }, 'class/class-create-class-naming'); +defineTest(__dirname, 'class', null, 'class/class-displayName'); diff --git a/transforms/class.js b/transforms/class.js index 5853f652..0f016dee 100644 --- a/transforms/class.js +++ b/transforms/class.js @@ -10,6 +10,8 @@ 'use strict'; +const { basename, extname, dirname } = require('path'); + module.exports = (file, api, options) => { const j = api.jscodeshift; @@ -1083,14 +1085,65 @@ module.exports = (file, api, options) => { ); }; + const addDisplayName = (displayName, specPath) => { + const props = specPath.properties; + let safe = true; + + for (let i = 0; i < props.length; i++) { + const prop = props[i]; + if (prop.key.name === 'displayName') { + safe = false; + break; + } + } + + if (safe) { + props.unshift(j.objectProperty(j.identifier('displayName'), j.stringLiteral(displayName))); + } + }; + const fallbackToCreateClassModule = (classPath) => { const comments = getComments(classPath); + const specPath = ReactUtils.directlyGetCreateClassSpec(classPath); + + if (specPath) { + // Add a displayName property to the spec object + let path = classPath; + let displayName; + while (path && displayName === undefined) { + switch (path.node.type) { + case 'ExportDefaultDeclaration': + displayName = basename(file.path, extname(file.path)); + if (displayName === 'index') { + // ./{module name}/index.js + displayName = basename(dirname(file.path)); + } + break; + case 'VariableDeclarator': + displayName = path.node.id.name; + break; + case 'AssignmentExpression': + displayName = path.node.left.name; + break; + case 'Property': + displayName = path.node.key.name; + break; + case 'Statement': + displayName = null; + break; + } + path = path.parent; + } + if (displayName) { + addDisplayName(displayName, specPath); + } + } + withComments( j(classPath).replaceWith( - j.callExpression( - j.identifier(CREATE_CLASS_VARIABLE_NAME), - classPath.value.arguments - ) + specPath + ? j.callExpression(j.identifier(CREATE_CLASS_VARIABLE_NAME), [specPath]) + : j.callExpression(j.identifier(CREATE_CLASS_VARIABLE_NAME), classPath.value.arguments) ), {comments}, ); diff --git a/yarn.lock b/yarn.lock index 6bb0e5b7..701a777f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -153,10 +153,6 @@ ast-types@0.8.12: version "0.8.12" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.12.tgz#a0d90e4351bb887716c83fd637ebf818af4adfcc" -ast-types@0.8.15: - version "0.8.15" - resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.15.tgz#8eef0827f04dff0ec8857ba925abe3fea6194e52" - ast-types@0.8.18: version "0.8.18" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.18.tgz#c8b98574898e8914e9d8de74b947564a9fe929af" @@ -2069,6 +2065,10 @@ inherits@2, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + inquirer@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" @@ -3139,6 +3139,13 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path@^0.12.7: + version "0.12.7" + resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f" + dependencies: + process "^0.11.1" + util "^0.10.3" + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -3177,6 +3184,10 @@ process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" +process@^0.11.1: + version "0.11.9" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1" + progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -3263,7 +3274,7 @@ readline2@^1.0.1: is-fullwidth-code-point "^1.0.0" mute-stream "0.0.5" -recast@0.10.33: +recast@0.10.33, recast@^0.10.10: version "0.10.33" resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.33.tgz#942808f7aa016f1fa7142c461d7e5704aaa8d697" dependencies: @@ -3272,15 +3283,6 @@ recast@0.10.33: private "~0.1.5" source-map "~0.5.0" -recast@^0.10.10: - version "0.10.43" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.43.tgz#b95d50f6d60761a5f6252e15d80678168491ce7f" - dependencies: - ast-types "0.8.15" - esprima-fb "~15001.1001.0-dev-harmony-fb" - private "~0.1.5" - source-map "~0.5.0" - recast@^0.11.11, recast@^0.11.17: version "0.11.22" resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.22.tgz#dedeb18fb001a2bbc6ac34475fda53dfe3d47dfa" @@ -3819,6 +3821,12 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" +util@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + dependencies: + inherits "2.0.1" + uuid@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728"