-
Notifications
You must be signed in to change notification settings - Fork 2k
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
DevDocs/Proptype Index: Generate slug/displayname and capture inline comments #10343
Conversation
return name; | ||
} | ||
|
||
return name |
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 wonder whether lodash's camelCase and upperFirst suffice here?
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.
Probably -- I stole it from from the dev-docs utils However, I didn't want it to log, so I just copy/pasted because I didn't want to cross server/client code. Maybe I'm being a dinosaur, but that was my reasoning. It may be reasonable to change it to use _.camelCase
in both places, or make the util function more reusable. Thoughts?
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. I wonder if we could reuse that module, did it actually work when you tried to import it?
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 almost forgot, there's no import
in vanilla nodejs, so to use the utility function from the client, it either has to be copy/pasted or the utility module has to have 0 dependencies (or use require
).
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.
Maybe a sneaky require( 'babel-register' )
would work?
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.
Good call ... coming right up!
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.
Cool. Does this have any impact on build time?
542beff
to
9d9c762
Compare
9d9c762
to
7ee79fe
Compare
|
||
const root = path.dirname( path.join( __dirname, '..', '..' ) ); | ||
const pathSwap = new RegExp(path.sep, 'g'); | ||
const handlers = [ ...reactDocgen.defaultHandlers, commentHandler ]; |
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 moved handlers out of the loop and to the top of the file ... saved quite a bit of time :)
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.
Nice. But it's still taking quite a long time:
nmda:~/a8c/wp-calypso (fix/devdocs-proptype-index=)$ time make server/devdocs/proptypes-index.json
Building: proptypes-index.json
Time: 33s 280.235ms
Is this because of the sheer amount of files we're processing? Is there any way we could cut this down?
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.
It is ... It would be nice if we only had to index components in the components
or blocks
directory.
A further optimization might be to only process files with an "example" component. This might make things quite a bit faster, since we only have to look for them and process them. Most of the time is spent parsing and building I believe.
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.
It is ... It would be nice if we only had to index components in the components or blocks directory.
I would be fine with this. Any component that is reusable should be already be in components
or blocks
.
A further optimization might be to only process files with an "example" component.
Does a component have to have an example.jsx
to be shown at all? If so, I think this is a good idea :)
Showing a message nudging a move to components
or blocks
, or creating an example.jsx
would be cool.
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.
Cool. I can see theme
inline comments in the JSON file now :) Left some other comments, I'm still concerned by build time.
Also when is the JSON file regenerated?
@@ -1,6 +1,7 @@ | |||
.vscode | |||
assets/ | |||
bin/ |
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 think this needs to change to bin/*
in order for the exception for generate-proptypes-index
to be respected.
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.
Worth adding this change?
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.
whoops!
@@ -4,31 +4,97 @@ | |||
* This file generates an index of proptypes by component displayname, slug and folder name |
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.
The whole file now needs linting :)
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.
:)
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.
done :)
|
||
const root = path.dirname( path.join( __dirname, '..', '..' ) ); | ||
const pathSwap = new RegExp(path.sep, 'g'); | ||
const handlers = [ ...reactDocgen.defaultHandlers, commentHandler ]; |
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.
Nice. But it's still taking quite a long time:
nmda:~/a8c/wp-calypso (fix/devdocs-proptype-index=)$ time make server/devdocs/proptypes-index.json
Building: proptypes-index.json
Time: 33s 280.235ms
Is this because of the sheer amount of files we're processing? Is there any way we could cut this down?
@@ -107,6 +178,7 @@ const writeFile = ( contents ) => { | |||
}; | |||
|
|||
const main = ( () => { | |||
console.log( 'Building: proptypes-index.json' ); |
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.
Maybe something more descriptive, like "Generating component documentation: …"
725ff81
to
b970d80
Compare
It would be interesting to used |
30d47ff
to
8b33636
Compare
* @param {NodePath} nodePath The node path from react-docgen | ||
* @return {null|string} The comment or nothing, if no comment | ||
*/ | ||
function getComments( nodePath ) { |
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.
Do you think some tests would elucidate what's going on here a bit better?
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.
Since this file is self-executing, based off of argv
there's three ways I can think to make it testable:
- Refactor the file into modules that are imported; test the modules -- lots of granularity
- execute the script from the test -- more of an integration test, really
- all of the above
I'm partial to "all of the above" ... what are your thoughts?
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 guess I'd lean towards 1, making generate-prop-types-index
a module of its own maybe?
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.
That's what I'm thinking
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.
Good stuff here!
I've only looked at this based on the file itself, not considering performance at all, at least for now. My takeaways are that, as this file is growing a bit, striving to make the file clear and easy to read is worth it — if nothing else, for other contributors, but also because it makes it easier to spot eventual inefficiencies, since tackling performance is the next step. Little things like grouping functions by logic (e.g. readFile
and writeFile
aren't together) and splitting some larger bits (e.g. computing includePath
outside of processFile
) also help.
*/ | ||
const createIndex = ( parsed ) => { | ||
const cleanIndex = ( parsed ) => { |
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.
Whenever I see a function like f := x => x.filter( y )
or similar, I ask myself whether it's useful to define f
at all, or whether y
is best defined on its own, since the predicate y
is by nature more reusable. In this case,
const hasDisplayname = ( component ) => {
if ( ! component ) {
…
}
This becomes relevant in main
:)
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.
You make a very good point, though IIRC, this function once had much more stuff in it and was cut down to just this singular filter...
* @param {Array} docObjArray The array of docs to filter | ||
* @return {Array} The filtered array | ||
*/ | ||
const onlyWithExample = ( docObjArray ) => { |
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.
A name closer to filterDocsWithExample
might be more descriptive.
let documents = fileList.map( processFile ); | ||
documents = onlyWithExample( documents ); | ||
documents = documents.map( parseDocument ); | ||
documents = cleanIndex( documents ); |
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 would be a great place for lodash#flow
, assuming FP-oriented map
and filter
:
import { flow } from 'lodash';
import { filter, map } from 'lodash/fp';
const documents = flow(
map( processFile ),
filterDocsWithExample,
map( parseDocument ),
filter( hasDisplayName )
)( fileList );
Also, the chain highlights that the process*
and parse*
functions could better reflect their type signatures in their names, for instance fileToDocument
. Alternatively, processFile
could be thought of as readDocument
.
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 swear I learn something new from lodash once a month. That's pretty excellent and simple.
comments = nodePath.node.comments.filter( | ||
comment => comment.leading === false | ||
); | ||
} |
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.
Looks like this could be refactored. Off the top of my mind:
if ( leadingComments || comments ) {
comments = ( leadingComments || comments ).filter( ( comment ) =>
comment.leading === !! leadingComments
);
}
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 don't see how this could work. leadingComments
is an array of leading comments. Such that:
do.something(); // this is a leading comment to the next line, but will have comment.leading set to false
// this is a leading comment to the next line, but will have comment.leading set to true
do.something(); // this is a comment, but will have comment.leading set to false
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.
If you start the method with const { leadingComments, comments } = nodePath.node
then those vars will default to undefined
if they aren't found in the node. Thus, even though an existing leadingComments
would be of type array (eval'ing to true regardless of length), the undefined one would eval to false.
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 think I got my PHP and JS mixed up when I first read this. In PHP, [] == false
.
} | ||
|
||
return null; | ||
} |
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.
Considering the previous comment, refactoring can go further:
function getComments( nodePath ) {
const { leadingComments, comments } = nodePath.node;
if ( ! leadingComments && ! comments ) {
return null;
}
const comments = ( leadingComments || comments ).filter( ( comment ) =>
comment.leading === !! leadingComments
);
return parseDocblock( comments[ comments.length - 1 ].value );
}
which incidentally, I feel, better tells us what is happening.
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.
Oh, me likey.
function parseDocblock( str ) { | ||
const lines = str.split( '\n' ); | ||
for ( let i = 0, l = lines.length; i < l; i++ ) { | ||
lines[ i ] = lines[ i ].replace( /^\s*\*\s?/, '' ); |
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.
Why the for
loop instead of a map
? (str.split( … ).map( … ).join( … ).trim()
)
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.
Interestingly, I originally wrote this against tests I wrote in react-docgen
and written there. @ehg and I pulled it out and adapted it for here. The react-docgen
code base is mostly for-loops and no es6 features. Hence how this code inherited a for-loop. It was just overlooked. Thanks for spotting it :D
@@ -106,7 +199,8 @@ const writeFile = ( contents ) => { | |||
fs.writeFileSync( path.join( root, 'server/devdocs/proptypes-index.json' ), JSON.stringify( contents ) ); | |||
}; | |||
|
|||
const main = ( () => { | |||
( () => { |
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.
Technically, this IIFE doesn't add anything except for some visual separation. This is assumedly my personal preference, but I'd keep the named main
function, and would perhaps move it to the top (or near the top) of the file, then leave its invocation at the bottom. Having main
at the top gives readers an immediate outline of what the program will perform.
const fs = require( 'fs' ); | ||
const path = require( 'path' ); | ||
const reactDocgen = require( 'react-docgen' ); | ||
const { getPropertyName, getMemberValuePath, resolveToValue } = require( 'react-docgen/dist/utils' ); | ||
const util = require( 'client/devdocs/docs-example/util' ); |
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.
util
needs an * Internal dependencies
block
/** | ||
* External Dependencies | ||
*/ | ||
|
||
require( 'babel-register' ); |
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.
Should be above * External dependencies
@mcsf Thanks for the amazing review! |
2fac4cb
to
c44e56f
Compare
c44e56f
to
9fc0ffc
Compare
9fc0ffc
to
4f5c84b
Compare
4f5c84b
to
7b1443d
Compare
7b1443d
to
21919dc
Compare
Let's get this merged. |
21919dc
to
e7b0f57
Compare
While working on #10342, I discovered more than a few components were lacking a "DisplayName" property and React DocGen left it out. We also discovered that there were several components that have inline comments, instead of docblocks, which would be helpful to have.
This PR generates a DisplayName based on the directory name and generates a matching slug. It also captures inline comments and displays some output/timing.
Related to #9919