Skip to content
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

Add displayName to more types of React components. #5679

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

roncohen
Copy link

@roncohen roncohen commented Apr 30, 2017

Q A
Patch: Bug Fix? no
Major: Breaking Change? no
Minor: New Feature? yes
Deprecations? no
Spec Compliancy? no
Tests Added/Pass? yes
Fixed Tickets
License MIT
Doc PR no
Dependency Changes no

This adds support for the two other kinds of React component definitions not previously covered by the existing displayName plugin:

  • ES6-classes style components
  • Stateless components that return JSX

See #4663 for a brief discussion.

Heuristics

ES6 classes are considered to be React components if they have a render method. Functions are considered components if they return JSX on the last line.

In our testing, these are quite accurate heuristics. Besides, the cost of erroneously determining a function/class to be a component and then proceeding to set displayName on a non-component should not be a big problem.

There are some nuances involved to make it work. Please see the tests for details.

This adds support for the two other kinds of React component definitions not previously covered by the existing displayName plugin:

- ES6-classes style components
- Stateless components that return JSX
@mention-bot
Copy link

@roncohen, thanks for your PR! By analyzing the history of the files in this pull request, we identified @hzoo, @existentialism and @loganfsmyth to be potential reviewers.

@codecov
Copy link

codecov bot commented Apr 30, 2017

Codecov Report

❗ No coverage uploaded for pull request base (7.0@6850064). Click here to learn what that means.
The diff coverage is 88.42%.

Impacted file tree graph

@@          Coverage Diff           @@
##             7.0    #5679   +/-   ##
======================================
  Coverage       ?   84.62%           
======================================
  Files          ?      283           
  Lines          ?     9802           
  Branches       ?     2758           
======================================
  Hits           ?     8295           
  Misses         ?      988           
  Partials       ?      519
Impacted Files Coverage Δ
...l-plugin-transform-react-display-name/src/index.js 88.59% <88.42%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6850064...fbd4dbc. Read the comment docs.

const name = pathMod.basename(state.file.opts.filename, extension);

const id = path.scope.generateUidIdentifier("uid");
path.node.id = id;
Copy link
Member

@Jessidhia Jessidhia May 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually transforms the declaration to export default function _uid () {}, unlike what the comment says. It works the same, though.

However, given how this transform, when enabled, would run before any module or function naming transform, it would change the actual function name to something other than "default" :/

Maybe wrapping it like this could work:

var _uid = /*#__PURE__*/(function () {
  var func = { 'default': function () { ... } }['default']
  func.displayName = 'default' // or whatever you inferred
  return func
})()
export { _uid as default }

You can use babel-template to generate the replacement AST.

Note, export { _uid as default } and not export default _uid.

// to `var _uid1 = function () { .. }; export default __uid;`
// then add displayName to _uid1
const extension = pathMod.extname(state.file.opts.filename);
const name = pathMod.basename(state.file.opts.filename, extension);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could lead to an awful amount of components with index as the displayName 😄

Though if you didn't use any heuristics at all they'd just be named the equally unhelpful default.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about using the folder name if the filename is index?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable 👍


function doesReturnJSX (body) {
if (!body) return false;
if (body.type === "JSXElement") {
Copy link
Member

@Jessidhia Jessidhia May 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since React 15 null is also a valid return type, and with React 16 so will an array of JSXElement; though missing these for now is probably fine as it's just a heuristic.

const members = node.node.body.body;
for (let i = 0; i < members.length; i++) {
if (members[i].type == "ClassMethod" && members[i].key.name == "render") {
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking if the class has a non-null extends clause could help reduce false positives, as well as checking if render doesReturnJSX.

},
ClassDeclaration: function (path) {
if (classHasRenderMethod(path)) {
setDisplayNameAfter(path, path.node.id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be able to skip this if the class has a static displayName = (or static get displayName () {}).

Supporting static displayName = would add a soft dependency on running before the class properties transform, though; but that should be the case in most setups.

@smably
Copy link

smably commented Jun 20, 2017

@roncohen Do you still intend to come back to this? I might be able to take this over if you don't have time.

@roncohen
Copy link
Author

@smably still intend to take a look at this, just been super busy. Hopefully in the weekend!

@danez danez closed this Aug 31, 2017
@danez danez reopened this Aug 31, 2017
@danez danez changed the base branch from 7.0 to master August 31, 2017 18:20
@taion
Copy link
Contributor

taion commented Jan 25, 2018

Is this PR stalled now?

@hzoo
Copy link
Member

hzoo commented Jan 30, 2018

Looks like it, someone can do a new pr + rebase it?

@ljharb
Copy link
Member

ljharb commented Aug 29, 2018

“classes are considered to be react components if they have a render method” will also target Backbone views.

@mAAdhaTTah
Copy link
Contributor

@ljharb Not really; you can't use class MyView extends Backbone.View because their built-in extension method doesn't play nice with ES6 class (jashkenas/backbone#3560).

@ljharb
Copy link
Member

ljharb commented Aug 29, 2018

@mAAdhaTTah it's bitten eslint-plugin-react before ¯\_(ツ)_/¯ component detection is hard.

@loganfsmyth
Copy link
Member

@mAAdhaTTah I'll chip in and say that at my last job, I converted our entire Backbone codebase to ES6 classes, so this would definitely have affected that codebase. Whether or not the new property would actually hurt anything, or if users are enabling preset-react in those cases, is a tough question.

@mAAdhaTTah
Copy link
Contributor

Fair enough, I stand corrected. :)

@jlurena
Copy link

jlurena commented Aug 21, 2019

Sooo when is this getting merged in? 👀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet