55 | ``
56 | ```
57 |
58 | And for `noRender` set to `false`.
59 |
60 | ```
61 | @playground
62 | Fixie
63 | !noRenderFalse!
64 | ``
65 |
66 |
67 | ``
68 | ```
69 |
70 | ***NOTE***: In order to generate documentation correctly there should not be any statements between your documentation comment and React class declaration, for e.g.
71 |
72 | ```
73 | // GOOD
74 |
75 | const foo = [];
76 |
77 | /*JSDoc comment*/
78 |
79 | class Foo extends React.Component {...}
80 | ```
81 |
82 | ```
83 | // BAD
84 |
85 | /*JSDoc comment*/
86 |
87 | const foo = [];
88 |
89 | class Foo extends React.Component {...}
90 | ```
91 |
92 | ## Auto generated demo
93 |
94 | ```
95 | /// start imports
96 | /// end imports
97 | ```
98 |
99 | ```
100 | /// start render
101 | /// end render
102 | ```
103 |
104 | ## Issues
105 |
106 | Before submitting an issue, please see the [Issue Submission Guidelines](https://github.com/electrode-io/electrode-docgen/blob/master/DEVGUIDE.md#submitting-issues)
107 |
108 | ## Contributing
109 |
110 | If you're interested in contributing, see the [Developer Guide's Contribution Guide](https://github.com/electrode-io/electrode-docgen/blob/master/DEVGUIDE.md#contributing)
111 |
112 | Built with :heart: by [Team Electrode](https://github.com/orgs/electrode-io/people) @WalmartLabs.
113 |
114 | [Developer Guide]: https://github.com/electrode-io/electrode-docgen/blob/master/DEVGUIDE.md
115 |
--------------------------------------------------------------------------------
/ES6GUIDE.md:
--------------------------------------------------------------------------------
1 | es6-guide
2 | =========
3 |
4 | A guide to ES6
5 |
6 | ## Functions
7 |
8 | ### Arrow functions
9 |
10 | Arrow functions are shorthand for an anonymous function that keep the current context. E.g.
11 |
12 | ```
13 | // Drastically simplified for effect
14 | this.a = 2;
15 |
16 | let multiply = function (num) {
17 | return num * this.a;
18 | }.bind(this);
19 |
20 | console.log(multiply(3)) // outputs 6
21 | ```
22 |
23 | Can be written as
24 |
25 | ```
26 | this.a = 2;
27 |
28 | let multiply = num => num * this.a;
29 |
30 | console.log(multiply(3)) // outputs 6
31 | ```
32 |
33 | This is most useful for cases like map or reduce:
34 |
35 | ```
36 | let numbers = [1, 2, 3, 4];
37 | let doubled = numbers.map(number => number * 2); // [2, 4, 6, 8]
38 | ```
39 |
40 | ### Arrow function syntax
41 |
42 | Arrow functions take the following form: `() => `. When there is only a single argument, the parens are optional e.g. `(x) => x * x` and `x => x * x` are both valid. When there are 0 or 2 or more arguments, parens are required. e.g. `() => "blah"` or `(x, y) => x * y`
43 |
44 | ### Concise methods
45 |
46 | In object literals and classes we can condense `render: function() {}` to `render() {}`
47 |
48 | ### Generators
49 |
50 | Generators functions take the following form
51 |
52 | ```
53 | function* name() {}
54 |
55 | // or the preferred
56 | let name = function* () {};
57 | // ...since we avoid function declarations in favor of function expressions
58 |
59 | ```
60 |
61 | Calling a generator doesn't actually run any of it's contents. A call to a generator returns a generator instance.
62 |
63 | ```
64 | let foo = function* () {
65 | console.log("foo");
66 | }
67 |
68 | // No console output here
69 | let bar = foo();
70 |
71 | // bar is now an instance of the generator and the console.log has never been run.
72 | ```
73 |
74 | To use a generator instance we have to call `.next()`
75 |
76 | ```
77 | let foo = function* () {
78 | console.log("foo");
79 | }
80 |
81 | // No console output here
82 | let bar = foo();
83 |
84 | // outputs "foo"
85 | bar.next();
86 | ```
87 |
88 | `next()` returns an object that looks like this:
89 | ```
90 | {
91 | value: undefined,
92 | done: true
93 | }
94 | ```
95 |
96 | What do `value` and `done` mean? Glad you asked, `done` means that the generator doesn't have any more code to execute. AKA we are past the last `yield` statement. To understand `value`, we have to look at `yield`.
97 |
98 | ```
99 | let foo = function* (x) {
100 | yield x;
101 | }
102 |
103 | let bar = foo(3);
104 |
105 | console.log(bar.next());
106 | // Output is {value: 3, done: false}
107 |
108 | console.log(bar.next());
109 | // Output is {value: undefined, done: true}
110 | ```
111 |
112 | A `yield` statement tells the generator to stop executing and return the following value. You can have `yield` statements without a return value. The generator will return `done: true` on the subsequent call to `next` after the last `yield` statement (see above).
113 |
114 | `yield` statements can also be used to pass in new information to the generator.
115 |
116 | ```
117 | let foo = function* (x) {
118 | let y = x + yield x;
119 |
120 | return y;
121 | }
122 |
123 | let bar = foo(5);
124 |
125 | console.log(bar.next().value) // outputs 5 from 'yield x'
126 |
127 | console.log(bar.next(8).value) //outputs 13 from 'let y = + '
128 | ```
129 |
130 | A little confusing right? What happens when we hit the first `yield` is we pass out `x` as `value`. The `yield x` statement then becomes whatever is passed into `.next()`. `x` is still `5` but `yield x` is now `8`.
131 |
132 | Note: `return` statements in generators are not a good idea. Although they are easy to reason about for `done: true`, they don't show up in `for..of` loops. See below
133 |
134 | Generators can be used with `for..of` loops for iterating to completion
135 |
136 | ```
137 | // Oversimplified example for effect
138 | let foo = function* (x) {
139 | yield x + 1;
140 | yield x + 2;
141 | yield x + 3;
142 |
143 | // Any return statement here would be ignored by the for..of loop
144 | }
145 |
146 | for (let y of foo(6)) {
147 | console.log(y);
148 | }
149 | // Output
150 | // 7
151 | // 8
152 | // 9
153 | ```
154 |
155 | For a more detailed overview of generators see http://davidwalsh.name/es6-generators
156 |
157 | ## Object literals
158 |
159 | ### Shorthand
160 |
161 | `{name: name, title: title}` can be condensed to `{name, title}`
162 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var fs = require('fs');
4 | var path = require('path');
5 | var reactDocs = require('react-docgen');
6 | var doctrine = require('doctrine');
7 | var program = require('commander');
8 | var glob = require('glob');
9 | var async = require('async');
10 | var generateMarkdown = require('./generate-markdown');
11 | var markdown = require('markdown-js').makeHtml;
12 | var indentString = require('indent-string');
13 |
14 | var _mergeProps = function(comp1, comp2) {
15 | for(var k in comp2.props) {
16 | if(comp1.props[k] === undefined) {
17 | comp1.props[k] = comp2.props[k];
18 | }
19 | }
20 | };
21 |
22 | var _parsePlayground = function(str) {
23 | var code = str.match(/(``|```)([\s\S]*)(``|```)/);
24 | if (code) {
25 | code = code[0];
26 | code = code.replace(/(``|```)\s*\n/g,'');
27 | code = code.replace(/(```|``)/g,'');
28 | }
29 | str = str.replace(/(``|```)([\s\S]*)(``|```)/, '');
30 | str = str.replace(/(``|```)/g, '');
31 | var flags = {};
32 | str = str.replace(/!([\s\S]*)!/, function(found) {
33 | found = found.replace(/!/g, '');
34 | flags[found] = true;
35 | return '';
36 | });
37 | str = str.replace(/^\s+/, '');
38 | str = str.replace(/\s+$/, '');
39 | return {
40 | title: str,
41 | flags: flags,
42 | code: code
43 | };
44 | };
45 |
46 | var _parseTags = function(str, target) {
47 | if (str === undefined) {
48 | return;
49 | }
50 | delete target.description;
51 | var parsed = doctrine.parse(str);
52 | target.description = parsed.description;
53 | for (var t in parsed.tags) {
54 | var tk = parsed.tags[t].title;
55 | var tv = parsed.tags[t].description;
56 | if (tk === 'playground') {
57 | if (target[tk] === undefined) {
58 | target[tk] = [];
59 | }
60 | target[tk].push(_parsePlayground(tv));
61 | } else if (tk === 'synonym') {
62 | if (target[tk]) {
63 | target[tk].push(tv);
64 | } else {
65 | target[tk] = [tv];
66 | }
67 | } else {
68 | if (target[tk]) {
69 | target[tk] = [target[tk]];
70 | target[tk].push(tv);
71 | } else {
72 | target[tk] = tv;
73 | }
74 | }
75 | }
76 | };
77 |
78 | function _createDemo(dir, done) {
79 | glob(dir + '/data/*.*', function(er, files) {
80 | var items = [];
81 | for(var f in files) {
82 | var file = files[f];
83 | console.log(file);
84 | items.push(" " +
85 | path.basename(file, path.extname(file)) +
86 | ": require(\"./data/" +
87 | path.basename(file) +
88 | "\")");
89 | }
90 | var data = "module.exports = {\n" +
91 | items.join(",\n") +
92 | "\n};\n";
93 | console.log('Writing ' + dir + '/data.jsx');
94 | fs.writeFileSync(dir + '/data.jsx', data);
95 | done();
96 | });
97 | }
98 |
99 | function _createImages(dir, done) {
100 | glob(dir + '/images/*.*', function(er, files) {
101 | var items = [];
102 | for(var f in files) {
103 | var file = files[f];
104 | console.log(file);
105 | items.push(" \"" +
106 | path.basename(file, path.extname(file)) +
107 | "\": require(\"./images/" +
108 | path.basename(file) +
109 | "\")");
110 | }
111 | var images = "module.exports = {\n" +
112 | items.join(",\n") +
113 | "\n};\n";
114 | console.log('Writing ' + dir + '/images.jsx');
115 | fs.writeFileSync(dir + '/images.jsx', images);
116 | done();
117 | });
118 | }
119 |
120 | program
121 | .version('0.0.17')
122 | .option('-s --src ', 'Source location', null, 'src')
123 | .option('-p --package ', 'Package file', null, 'package.json')
124 | .option('--metadata ', 'Metadata output file')
125 | .option('--markdown ', 'Markdown output file')
126 | .option('--demo ', 'Demo output directory')
127 | .parse(process.argv);
128 |
129 | var pkgJSON = fs.readFileSync(program.package);
130 | var pkgInfo = JSON.parse(pkgJSON);
131 |
132 | var metadata = {
133 | library: pkgInfo.name,
134 | description: pkgInfo.description,
135 | title: pkgInfo.title,
136 | components: []
137 | };
138 |
139 | glob(program.src + '/*/**.jsx', function(er, files) {
140 | async.each(files, function(file, done) {
141 | var src = fs.readFileSync(file);
142 | try {
143 | var componentInfo = reactDocs.parse(src);
144 | componentInfo.fileName = file;
145 | if (componentInfo.description) {
146 | _parseTags(componentInfo.description, componentInfo);
147 | }
148 | delete componentInfo.props.children;
149 | delete componentInfo.props.className;
150 | delete componentInfo.props.style;
151 | componentInfo.hasProps = false;
152 | if (componentInfo.props) {
153 | for (var p in componentInfo.props) {
154 | var pr = componentInfo.props[p];
155 | componentInfo.hasProps = true;
156 | _parseTags(pr.description, pr);
157 | }
158 | }
159 | if (componentInfo.private === undefined) {
160 | metadata.components.push(componentInfo);
161 | }
162 | } catch(e) {
163 | // console.log(e.stack);
164 | // console.log(e);
165 | }
166 | done();
167 | }, function() {
168 | metadata.components = metadata.components.sort(function(a, b) {
169 | if (a.component < b.component ) {
170 | return -1;
171 | } else {
172 | return 1;
173 | }
174 | });
175 |
176 | var componentsByName = {};
177 | for(var i in metadata.components) {
178 | var comp = metadata.components[i];
179 | componentsByName[comp.component] = comp;
180 | }
181 |
182 | for(var i in metadata.components) {
183 | var comp = metadata.components[i];
184 | if(comp.wraps) {
185 | var wrappedComp = componentsByName[comp.wraps];
186 | if(wrappedComp) {
187 | _mergeProps(comp, wrappedComp);
188 | }
189 | }
190 | }
191 |
192 | if (program.metadata) {
193 | fs.writeFileSync(program.metadata,
194 | JSON.stringify(metadata, null, 2));
195 | }
196 |
197 | if (program.markdown) {
198 | fs.writeFileSync(program.markdown, generateMarkdown.generateLibraryDocs(metadata));
199 | }
200 |
201 | if (program.demo) {
202 | async.series([
203 | function(done) { _createDemo(program.demo, done); },
204 | function(done) { _createImages(program.demo, done); }
205 | ]);
206 | }
207 | });
208 | });
209 |
--------------------------------------------------------------------------------
/DEVGUIDE.md:
--------------------------------------------------------------------------------
1 | Walmart React Dev Guide
2 | =========================
3 |
4 | - [Goals](#goals)
5 | - [Submitting Issues](#submitting-issues)
6 | - [Contributing](#contributing)
7 | - [Running the code](#running-the-code)
8 | - [Testing](#testing)
9 | - [PR Process](#pr-process)
10 | - [Before You PR!](#before-you-pr)
11 | - [Submitting a PR](#submitting-a-pr)
12 | - [Assign your PRs](#assign-your-prs)
13 | - [Conventions](#conventions)
14 | - [Walmart React Code Style](#walmart-react-code-style)
15 | - [Five Lines](#five-lines)
16 | - [File Naming](#file-naming)
17 | - [Method Organizations](#method-organizations)
18 | - [displayName](#displayname)
19 | - [Conditional HTML](#conditional-html)
20 | - [JSX as a Variable or Return Value](#jsx-as-a-variable-or-return-value)
21 | - [Self-Closing Tags](#self-closing-tags)
22 | - [List Iterations](#list-iterations)
23 | - [Formatting Attributes](#formatting-attributes)
24 | - [ES6](#es6)
25 | - [When In Doubt](#when-in-doubt)
26 |
27 | ## Goals
28 |
29 | The high level goals and ideals of this are:
30 |
31 | * Give developers a resource of best practices and processes
32 | * Make it clear about what we expect from good code.
33 | * Deliver consistency across the code base.
34 | * Make it easier for engineers to get through the PR process the first time.
35 | * Create code that works, is modern and performant, is maintainable, and clearly expresses its function and the intent of the author.
36 |
37 | ## Submitting Issues
38 |
39 | When submitting an issue to any `electrode-io/*` projects, please provide a good description of your issue along with the following information:
40 |
41 | - `Node` version (`node -v`)
42 | - `npm` version (`npm -v`)
43 | - Relevant logs from the output of your issue
44 | - Any `error` output in either terminal or the browser console
45 | - Browser & browser version you experience issue in (if applicable)
46 |
47 | This will help the developers of that project have the information they need to move forward to fix your issue.
48 |
49 | ## Contributing
50 |
51 | The purpose of these libraries is to provide a set of React components to simplify development on top of the Electrode. This includes wrapping all of the controls, as well as responsive helpers, and form validation. The guiding philosophy is; *We write more code so that you write less code*.
52 |
53 | Be sure to read the [PR process](#pr-process) before submitting code for review.
54 |
55 | ### Running the code
56 |
57 | Run the demo with watch task.
58 | ```sh
59 | % gulp hot
60 | ```
61 |
62 | Run the demo without watch task.
63 | ```sh
64 | % gulp demo
65 | ```
66 |
67 | These will launch the demo page on http://localhost:4000/
68 |
69 | ### Testing
70 |
71 | We want all of the components in this library to be fully tested and have at least 80% function and conditional coverage. We use mocha for the tests and there is one test specification per component.
72 |
73 | To run the tests:
74 |
75 | ```sh
76 | % gulp test
77 | ```
78 |
79 | ## PR Process
80 |
81 | ### Before You PR!
82 |
83 | ```sh
84 | % gulp test
85 | ```
86 |
87 | Make sure your code *has tests*, passes those tests, and that all of the other components pass their tests, and that the lint runs completely clean. It is acceptable to disable `new-cap`, `no-unused-vars`, `guard-for-in` and `no-unused-expressions` if required. But it not acceptable to disable eslint entirely.
88 |
89 | Functional coverage must be above 90% overall.
90 |
91 | ### Submitting a PR
92 |
93 | #### Assign your PRs
94 |
95 | Assign your PRs to a single person. If needed, tag that person on the pull request.
96 |
97 | #### Conventions
98 |
99 | PRs should follow the naming convention of `[MAJOR]`, `[MINOR]`, `[PATCH]` followed by a short description, ending with any relevant Jira or Github issue. E.g. `[MINOR] add color property github199`.
100 |
101 | The prefix of the title should be based on how the PR will impact the [semantic version](http://semver.org/) of the package. The size of the changes are unimportant. The only things that matters is how the API has changed - in short:
102 |
103 | * Use `[MAJOR]` when you make incompatible API changes. This includes changing type signatures on returns types or arguments, or renaming or deleting components or methods -- even if they are "not used". A good rule of thumb is to ask if running the previous release's tests against the current candidate's code will break.
104 | * Use `[MINOR]` when you add functionality in a backwards-compatible manner. For example adding methods, components or optional arguments.
105 | * Use `[PATCH]` version when you make backwards-compatible non-feature changes (i.e. bug fixes, performance enhancements et cetera).
106 |
107 | In the PR comment include a short description of changes, any relevant screenshots, and a `cc/` list of people who should see the PR. Put the assigned reviewer's name first.
108 |
109 |
110 | ## Walmart React Code Style
111 |
112 | This is our recommendation for React code. It's based on [https://reactjsnews.com/react-style-guide-patterns-i-like](https://reactjsnews.com/react-style-guide-patterns-i-like).
113 |
114 | ### Five Lines
115 |
116 | How big should a method be? How long should a section of JSX be? Think no more than 5 lines. At the point at which you hit five lines think, should I refactor this? And then refactor it.
117 |
118 | ### File Naming
119 |
120 | All JSX files should have the extension `.jsx` instead of `.js` for ease of identification, compiling and so on.
121 |
122 | ### Method Organizations
123 |
124 | We lay out the methods of a component in life-cycle order:
125 |
126 | ```js
127 | React.createClass({
128 | displayName : '',
129 | mixins: [],
130 | propTypes: {},
131 | statics: {},
132 | getDefaultProps() {},
133 | getInitialState() {},
134 | componentWillMount() {},
135 | componentDidMount() {},
136 | componentWillReceiveProps() {},
137 | shouldComponentUpdate() {},
138 | componentWillUpdate() {},
139 | componentDidUpdate() {},
140 | componentWillUnmount() {},
141 | _getFoo() {},
142 | _fooHelper() {},
143 | _onClick() {},
144 | render() {}
145 | });
146 | ```
147 |
148 | Event handlers go before the render. Ideally `render` is always the last method. Sub-renders, like `_renderItem()` would go above the main `render()` method.
149 |
150 | Prepend custom functions with an underscore.
151 |
152 | ### displayName
153 |
154 | `displayName` is required and should match the class/file name and how it would appear in JSX. E.g. `displayName: 'Accordion'` or `displayName: 'RadioGroup'`.
155 |
156 | Nested components need to have a `displayName` that matches the way it would appear in JSX e.g. `displayName: 'Accordion.Item'`
157 |
158 | ### Conditional HTML
159 |
160 | For hiding/showing content we most often choose to use conditional CSS classes, usually with a `hide-content` class, so that there is minimal impact on the DOM as the element is hidden and shown.
161 |
162 | However, in the case where you want to remove an item from the DOM follow this pattern when the code is fairly simple:
163 |
164 | ```js
165 | {this.state.show && 'This is Shown'}
166 | ```
167 |
168 | Or
169 |
170 | ```js
171 | {this.state.on ? 'On' : 'Off'}
172 | ```
173 |
174 | For more complex examples create a local variable and add that to the render:
175 |
176 | ```js
177 | let dinosaurHtml = '';
178 | if (this.state.showDinosaurs) {
179 | dinosaurHtml = (
180 |
181 |
182 |
183 |
184 | );
185 | }
186 |
187 | return (
188 |
189 | ...
190 | {dinosaurHtml}
191 | ...
192 |
193 | );
194 | ```
195 |
196 | ### JSX as a Variable or Return Value
197 |
198 | JSX spanning multiple lines should be wrapped in parentheses like so:
199 |
200 | ```js
201 | const multilineJsx = (
202 |
203 |
204 |
205 |
206 | );
207 | ```
208 |
209 | JSX spanning a single line can disregard the parentheses,
210 |
211 | ```
212 | const singleLineJsx = Simple JSX;
213 | ```
214 |
215 | but anything complicated or with a likeliness of expanding could be wrapped in parentheses for readability/convenience.
216 |
217 | ### Self-Closing Tags
218 |
219 | Components without children should simply close themselves, as above with Logo,
220 |
221 | ```js
222 |
223 | ```
224 |
225 | as opposed to the unnecessarily more verbose
226 |
227 | ```js
228 |
229 | ```
230 |
231 | ### List Iterations
232 |
233 | I used to do my list iterations like above in dinosaurHtml. I've realized that list iterations are better done inline, especially if each list item will be rendered as a component. You may even be able to reduce to one line with fat arrows:
234 |
235 | ```js
236 | render() {
237 | return (
238 |
241 | );
242 | }
243 | ```
244 |
245 | Super clean ES6 syntax. So we like that a lot.
246 |
247 | ### Formatting Attributes
248 |
249 | Instead of the long input element above, a cleaner and easier indentation would be:
250 |
251 | ```js
252 |
256 | ```
257 |
258 | Note the two space indent. This makes sense for us because our component names can get long.
259 |
260 |
261 | ## ES6
262 |
263 | We like ES6 a lot and we prefer its use over the often more verbose ES5. For details check out the ES6 guide https://github.com/electrode-io/electrode-docgen/blob/master/ES6GUIDE.md
264 |
265 | * We prefer `const` over `let` over `var` - `var` is considered deprecated.
266 | * We prefer dropping the `function` keyword when possible.
267 | * We prefer dropping `function` and using the fat arrow/dash rocket (`=>`) syntax instead.
268 | * We prefer `import` and `export` over `require` and `module.exports`.
269 | * We like the spread operator. And prefer to see `{... this.props}` when it's appropriate in the `render`.
270 |
271 |
272 | ## When In Doubt
273 |
274 | * Breathe.
275 | * Do what works.
276 | * Do what is simple.
277 | * Ask the question on github. We are friendly. We don't bite.
278 | * Drink lots of water and eat leafy greens.
279 | * Realize that life is fleeting.
280 | * Go for a run.
281 | * Grill.
282 |
--------------------------------------------------------------------------------