mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* Remove angular styleguide * Remove SASS reference only file It's still linked in the STYLEGUIDE.md document * Cleanup HTML styleguide * More cleanup * Cleanup API style guide * Remove architecture style guide * Merge into STYLEGUIDE.md * add attribution back on * Change doclink to style guide * Change wording and remove more rules * Update STYLEGUIDE.md Fix typos Co-Authored-By: Stacey Gammon <gammon@elastic.co>
This commit is contained in:
parent
4d5f62542e
commit
12a76df1b1
9 changed files with 685 additions and 1362 deletions
698
STYLEGUIDE.md
698
STYLEGUIDE.md
|
@ -3,25 +3,695 @@
|
|||
This guide applies to all development within the Kibana project and is
|
||||
recommended for the development of all Kibana plugins.
|
||||
|
||||
- [JavaScript](style_guides/js_style_guide.md)
|
||||
- [Angular](style_guides/angular_style_guide.md)
|
||||
- [React](style_guides/react_style_guide.md)
|
||||
- [SASS](https://elastic.github.io/eui/#/guidelines/sass)
|
||||
- [HTML](style_guides/html_style_guide.md)
|
||||
- [API](style_guides/api_style_guide.md)
|
||||
- [Architecture](style_guides/architecture_style_guide.md)
|
||||
- [Accessibility](style_guides/accessibility_guide.md)
|
||||
Besides the content in this style guide, the following style guides may also apply
|
||||
to all development within the Kibana project. Please make sure to also read them:
|
||||
|
||||
## Filenames
|
||||
- [Accessibility style guide](style_guides/accessibility_guide.md)
|
||||
- [SASS style guide](https://elastic.github.io/eui/#/guidelines/sass)
|
||||
|
||||
## General
|
||||
|
||||
### Filenames
|
||||
|
||||
All filenames should use `snake_case`.
|
||||
|
||||
**Right:** `src/kibana/index_patterns/index_pattern.js`
|
||||
|
||||
**Wrong:** `src/kibana/IndexPatterns/IndexPattern.js`
|
||||
|
||||
### Do not comment out code
|
||||
|
||||
We use a version management system. If a line of code is no longer needed,
|
||||
remove it, don't simply comment it out.
|
||||
|
||||
### Prettier and Linting
|
||||
|
||||
We are gradually moving the Kibana code base over to Prettier. All TypeScript code
|
||||
and some JavaScript code (check `.eslintrc.js`) is using Prettier to format code. You
|
||||
can run `node script/eslint --fix` to fix linting issues and apply Prettier formatting.
|
||||
We recommend you to enable running ESLint via your IDE.
|
||||
|
||||
Whenever possible we are trying to use Prettier and linting over written style guide rules.
|
||||
Consider every linting rule and every Prettier rule to be also part of our style guide
|
||||
and disable them only in exceptional cases and ideally leave a comment why they are
|
||||
disabled at that specific place.
|
||||
|
||||
## HTML
|
||||
|
||||
This part contains style guide rules around general (framework agnostic) HTML usage.
|
||||
|
||||
### Camel case `id` and `data-test-subj`
|
||||
|
||||
Use camel case for the values of attributes such as `id` and `data-test-subj` selectors.
|
||||
|
||||
```html
|
||||
<button
|
||||
id="veryImportantButton"
|
||||
data-test-subj="clickMeButton"
|
||||
>
|
||||
Click me
|
||||
</button>
|
||||
```
|
||||
|
||||
The only exception is in cases where you're dynamically creating the value, and you need to use
|
||||
hyphens as delimiters:
|
||||
|
||||
```jsx
|
||||
buttons.map(btn => (
|
||||
<button
|
||||
id={`veryImportantButton-${btn.id}`}
|
||||
data-test-subj={`clickMeButton-${btn.id}`}
|
||||
>
|
||||
{btn.label}
|
||||
</button>
|
||||
)
|
||||
```
|
||||
|
||||
### Capitalization in HTML and CSS should always match
|
||||
|
||||
It's important that when you write CSS/SASS selectors using classes, IDs, and attributes
|
||||
(keeping in mind that we should _never_ use IDs and attributes in our selectors), that the
|
||||
capitalization in the CSS matches that used in the HTML. HTML and CSS follow different case sensitivity rules, and we can avoid subtle gotchas by ensuring we use the
|
||||
same capitalization in both of them.
|
||||
|
||||
## API endpoints
|
||||
|
||||
The following style guide rules are targeting development of server side API endpoints.
|
||||
|
||||
### Paths
|
||||
|
||||
API routes must start with the `/api/` path segment, and should be followed by the plugin id if applicable:
|
||||
|
||||
**Right:** `/api/marvel/nodes`
|
||||
|
||||
**Wrong:** `/marvel/api/nodes`
|
||||
|
||||
### snake_case
|
||||
|
||||
Kibana uses `snake_case` for the entire API, just like Elasticsearch. All urls, paths, query string parameters, values, and bodies should be `snake_case` formatted.
|
||||
|
||||
*Right:*
|
||||
- `src/kibana/index_patterns/index_pattern.js`
|
||||
```
|
||||
POST /api/kibana/index_patterns
|
||||
{
|
||||
"id": "...",
|
||||
"time_field_name": "...",
|
||||
"fields": [
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
*Wrong:*
|
||||
- `src/kibana/IndexPatterns/IndexPattern.js`
|
||||
## TypeScript/JavaScript
|
||||
|
||||
## TypeScript vs JavaScript
|
||||
The following style guide rules apply for working with TypeScript/JavaScript files.
|
||||
|
||||
Whenever possible, write code in TypeScript instead of javascript, especially if it's new code. Check out [TYPESCRIPT.md](TYPESCRIPT.md) for help with this process.
|
||||
### TypeScript vs. JavaScript
|
||||
|
||||
Whenever possible, write code in TypeScript instead of JavaScript, especially if it's new code.
|
||||
Check out [TYPESCRIPT.md](TYPESCRIPT.md) for help with this process.
|
||||
|
||||
### Prefer modern JavaScript/TypeScript syntax
|
||||
|
||||
You should prefer modern language features in a lot of cases, e.g.:
|
||||
|
||||
* Prefer `class` over `prototype` inheritance
|
||||
* Prefer template strings over string concatenation
|
||||
|
||||
### Avoid mutability and state
|
||||
|
||||
Wherever possible, do not rely on mutable state. This means you should not
|
||||
reassign variables, modify object properties, or push values to arrays.
|
||||
Instead, create new variables, and shallow copies of objects and arrays:
|
||||
|
||||
```js
|
||||
// good
|
||||
function addBar(foos, foo) {
|
||||
const newFoo = {...foo, name: 'bar'};
|
||||
return [...foos, newFoo];
|
||||
}
|
||||
|
||||
// bad
|
||||
function addBar(foos, foo) {
|
||||
foo.name = 'bar';
|
||||
foos.push(foo);
|
||||
}
|
||||
```
|
||||
|
||||
### Return/throw early from functions
|
||||
|
||||
To avoid deep nesting of if-statements, always return a function's value as early
|
||||
as possible. And where possible, do any assertions first:
|
||||
|
||||
```js
|
||||
// good
|
||||
function doStuff(val) {
|
||||
if (val > 100) {
|
||||
throw new Error('Too big');
|
||||
}
|
||||
|
||||
if (val < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... stuff
|
||||
}
|
||||
|
||||
// bad
|
||||
function doStuff(val) {
|
||||
if (val >= 0) {
|
||||
if (val < 100) {
|
||||
// ... stuff
|
||||
} else {
|
||||
throw new Error('Too big');
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Use object destructuring
|
||||
|
||||
This helps avoid temporary references and helps prevent typo-related bugs.
|
||||
|
||||
```js
|
||||
// best
|
||||
function fullName({ first, last }) {
|
||||
return `${first} ${last}`;
|
||||
}
|
||||
|
||||
// good
|
||||
function fullName(user) {
|
||||
const { first, last } = user;
|
||||
return `${first} ${last}`;
|
||||
}
|
||||
|
||||
// bad
|
||||
function fullName(user) {
|
||||
const first = user.first;
|
||||
const last = user.last;
|
||||
return `${first} ${last}`;
|
||||
}
|
||||
```
|
||||
|
||||
### Use array destructuring
|
||||
|
||||
Directly accessing array values via index should be avoided, but if it is
|
||||
necessary, use array destructuring:
|
||||
|
||||
```js
|
||||
const arr = [1, 2, 3];
|
||||
|
||||
// good
|
||||
const [first, second] = arr;
|
||||
|
||||
// bad
|
||||
const first = arr[0];
|
||||
const second = arr[1];
|
||||
```
|
||||
|
||||
### Declare one variable per line, wherever it makes the most sense
|
||||
|
||||
This makes it easier to re-order the lines. However, declari variables
|
||||
wherever it makes sense (not necessairly on the top of a function).
|
||||
|
||||
```js
|
||||
// good
|
||||
const keys = ['foo', 'bar'];
|
||||
const values = [23, 42];
|
||||
|
||||
// bad
|
||||
const keys = ['foo', 'bar'],
|
||||
values = [23, 42];
|
||||
```
|
||||
|
||||
### Prefix private class methods with an underscore `JS only`
|
||||
|
||||
Identifying private class methods makes it easier to differentiate a class's public and internal
|
||||
APIs, and makes private methods easier to mark as `private` when the code is migrated to TypeScript.
|
||||
|
||||
```js
|
||||
// good
|
||||
class BankAccount {
|
||||
addFunds() {}
|
||||
|
||||
_calculateInterest() {}
|
||||
}
|
||||
```
|
||||
|
||||
If using TypeScript you should use the `private` modifier instead.
|
||||
|
||||
### Magic numbers/strings
|
||||
|
||||
These are numbers (or other values) simply used in line in your code. *Do not
|
||||
use these*, give them a variable name so they can be understood and changed
|
||||
easily.
|
||||
|
||||
```js
|
||||
// good
|
||||
const minWidth = 300;
|
||||
|
||||
if (width < minWidth) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
if (width < 300) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Modules
|
||||
|
||||
Module dependencies should be written using native ES2015 syntax wherever
|
||||
possible (which is almost everywhere):
|
||||
|
||||
```js
|
||||
// good
|
||||
import { mapValues } from 'lodash';
|
||||
export mapValues;
|
||||
|
||||
// bad
|
||||
const _ = require('lodash');
|
||||
module.exports = _.mapValues;
|
||||
|
||||
// worse
|
||||
define(['lodash'], function (_) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
In those extremely rare cases where you're writing server-side JavaScript in a
|
||||
file that does not pass run through webpack, then use CommonJS modules.
|
||||
|
||||
In those even rarer cases where you're writing client-side code that does not
|
||||
run through webpack, then do not use a module loader at all.
|
||||
|
||||
#### Import only top-level modules
|
||||
|
||||
The files inside a module are implementation details of that module. They
|
||||
should never be imported directly. Instead, you must only import the top-level
|
||||
API that's exported by the module itself.
|
||||
|
||||
Without a clear mechanism in place in JS to encapsulate protected code, we make
|
||||
a broad assumption that anything beyond the root of a module is an
|
||||
implementation detail of that module.
|
||||
|
||||
On the other hand, a module should be able to import parent and sibling
|
||||
modules.
|
||||
|
||||
```js
|
||||
// good
|
||||
import foo from 'foo';
|
||||
import child from './child';
|
||||
import parent from '../';
|
||||
import ancestor from '../../../';
|
||||
import sibling from '../foo';
|
||||
|
||||
// bad
|
||||
import inFoo from 'foo/child';
|
||||
import inSibling from '../foo/child';
|
||||
```
|
||||
|
||||
### Global definitions
|
||||
|
||||
Don't do this. Everything should be wrapped in a module that can be depended on
|
||||
by other modules. Even things as simple as a single value should be a module.
|
||||
|
||||
### Function definitions
|
||||
|
||||
Use function declarations over function expressions, so that their names will
|
||||
show up in stack traces, making errors easier to debug.
|
||||
|
||||
Also, keep function definitions above other code instead of relying on function
|
||||
hoisting.
|
||||
|
||||
```js
|
||||
// good
|
||||
function myFunc() {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
const myFunc = function () {
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
### Arrow functions
|
||||
|
||||
If you must use a function expression, then use an arrow function:
|
||||
|
||||
```js
|
||||
// good
|
||||
[1, 2, 3].map((n) => {
|
||||
const m = doSomething(n);
|
||||
return m - n;
|
||||
});
|
||||
|
||||
// bad
|
||||
[1, 2, 3].map(function (n) {
|
||||
const m = doSomething(n);
|
||||
return m - n;
|
||||
});
|
||||
```
|
||||
|
||||
If your arrow function is only returning an object literal, then wrap the
|
||||
object in parentheses rather than using an explicit return:
|
||||
|
||||
```js
|
||||
// good
|
||||
() => ({
|
||||
foo: 'bar'
|
||||
})
|
||||
|
||||
// bad
|
||||
() => {
|
||||
return {
|
||||
foo: 'bar'
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Object / Array iterations, transformations and operations
|
||||
|
||||
Use native methods to iterate and transform arrays and objects where possible.
|
||||
Avoid `for` and `while` loops as they introduce the possibility of infinite
|
||||
loops and break out of our preferred convention of declarative programming.
|
||||
|
||||
Use descriptive variable names in the closures.
|
||||
|
||||
Use a utility library as needed and where it will make code more
|
||||
comprehensible.
|
||||
|
||||
```js
|
||||
// best
|
||||
const userNames = users.map(user => user.name);
|
||||
|
||||
// ok
|
||||
import { pluck } from 'lodash';
|
||||
const userNames = pluck(users, 'name');
|
||||
|
||||
// bad
|
||||
const userNames = [];
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
userNames.push(users[i].name);
|
||||
}
|
||||
```
|
||||
|
||||
### Use the spread operator (`...`) for copying arrays
|
||||
|
||||
This helps with expressiveness and readability.
|
||||
|
||||
```js
|
||||
const arr = [1, 2, 3];
|
||||
|
||||
// good
|
||||
const arrCopy = [...arr];
|
||||
|
||||
// bad
|
||||
const arrCopy = arr.slice();
|
||||
```
|
||||
|
||||
### Only use ternary operators for small, simple code
|
||||
|
||||
And *never* use multiple ternaries together, because they make it more
|
||||
difficult to reason about how different values flow through the conditions
|
||||
involved. Instead, structure the logic for maximum readability.
|
||||
|
||||
```js
|
||||
// good, a situation where only 1 ternary is needed
|
||||
const foo = (a === b) ? 1 : 2;
|
||||
|
||||
// bad
|
||||
const foo = (a === b) ? 1 : (a === c) ? 2 : 3;
|
||||
```
|
||||
|
||||
### Do not extend built-in prototypes
|
||||
|
||||
Do not extend the prototype of native JavaScript objects. Your future self will
|
||||
be forever grateful.
|
||||
|
||||
```js
|
||||
// bad
|
||||
Array.prototype.empty = function () {
|
||||
return !this.length;
|
||||
}
|
||||
```
|
||||
|
||||
### Use descriptive conditions
|
||||
|
||||
Any non-trivial conditions should be converted to functions or assigned to
|
||||
descriptively named variables. By breaking up logic into smaller,
|
||||
self-contained blocks, it becomes easier to reason about the higher-level
|
||||
logic. Additionally, these blocks become good candidates for extraction into
|
||||
their own modules, with unit-tests.
|
||||
|
||||
```js
|
||||
// best
|
||||
function isShape(thing) {
|
||||
return thing instanceof Shape;
|
||||
}
|
||||
function notSquare(thing) {
|
||||
return !(thing instanceof Square);
|
||||
}
|
||||
if (isShape(thing) && notSquare(thing)) {
|
||||
...
|
||||
}
|
||||
|
||||
// good
|
||||
const isShape = thing instanceof Shape;
|
||||
const notSquare = !(thing instanceof Square);
|
||||
if (isShape && notSquare) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
if (thing instanceof Shape && !(thing instanceof Square)) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Name regular expressions
|
||||
|
||||
```js
|
||||
// good
|
||||
const validPassword = /^(?=.*\d).{4,}$/;
|
||||
|
||||
if (password.length >= 4 && validPassword.test(password)) {
|
||||
console.log('password is valid');
|
||||
}
|
||||
|
||||
// bad
|
||||
if (password.length >= 4 && /^(?=.*\d).{4,}$/.test(password)) {
|
||||
console.log('losing');
|
||||
}
|
||||
```
|
||||
|
||||
### Write small functions
|
||||
|
||||
Keep your functions short. A good function fits on a slide that the people in
|
||||
the last row of a big room can comfortably read. So don't count on them having
|
||||
perfect vision and limit yourself to ~15 lines of code per function.
|
||||
|
||||
### Use "rest" syntax rather than built-in `arguments`
|
||||
|
||||
For expressiveness sake, and so you can be mix dynamic and explicit arguments.
|
||||
|
||||
```js
|
||||
// good
|
||||
function something(foo, ...args) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
function something(foo) {
|
||||
const args = Array.from(arguments).slice(1);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Default argument syntax
|
||||
|
||||
Always use the default argument syntax for optional arguments.
|
||||
|
||||
```js
|
||||
// good
|
||||
function foo(options = {}) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
function foo(options) {
|
||||
if (typeof options === 'undefined') {
|
||||
options = {};
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
And put your optional arguments at the end.
|
||||
|
||||
```js
|
||||
// good
|
||||
function foo(bar, options = {}) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
function foo(options = {}, bar) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Use thunks to create closures, where possible
|
||||
|
||||
For trivial examples (like the one that follows), thunks will seem like
|
||||
overkill, but they encourage isolating the implementation details of a closure
|
||||
from the business logic of the calling code.
|
||||
|
||||
```js
|
||||
// good
|
||||
function connectHandler(client, callback) {
|
||||
return () => client.connect(callback);
|
||||
}
|
||||
setTimeout(connectHandler(client, afterConnect), 1000);
|
||||
|
||||
// not as good
|
||||
setTimeout(() => {
|
||||
client.connect(afterConnect);
|
||||
}, 1000);
|
||||
|
||||
// bad
|
||||
setTimeout(() => {
|
||||
client.connect(() => {
|
||||
...
|
||||
});
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
### Use slashes for comments
|
||||
|
||||
Use slashes for both single line and multi line comments. Try to write
|
||||
comments that explain higher level mechanisms or clarify difficult
|
||||
segments of your code. *Don't use comments to restate trivial things*.
|
||||
|
||||
*Exception:* Comment blocks describing a function and its arguments
|
||||
(docblock) should start with `/**`, contain a single `*` at the beginning of
|
||||
each line, and end with `*/`.
|
||||
|
||||
|
||||
```js
|
||||
// good
|
||||
|
||||
// 'ID_SOMETHING=VALUE' -> ['ID_SOMETHING=VALUE', 'SOMETHING', 'VALUE']
|
||||
const matches = item.match(/ID_([^\n]+)=([^\n]+)/));
|
||||
|
||||
/**
|
||||
* Fetches a user from...
|
||||
* @param {string} id - id of the user
|
||||
* @return {Promise}
|
||||
*/
|
||||
function loadUser(id) {
|
||||
// This function has a nasty side effect where a failure to increment a
|
||||
// redis counter used for statistics will cause an exception. This needs
|
||||
// to be fixed in a later iteration.
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
const isSessionValid = (session.expires < Date.now());
|
||||
if (isSessionValid) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
|
||||
// Execute a regex
|
||||
const matches = item.match(/ID_([^\n]+)=([^\n]+)/));
|
||||
|
||||
// Usage: loadUser(5, function() { ... })
|
||||
function loadUser(id, cb) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Check if the session is valid
|
||||
const isSessionValid = (session.expires < Date.now());
|
||||
// If the session is valid
|
||||
if (isSessionValid) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Do not alias `this`
|
||||
|
||||
Try not to rely on `this` at all, but if you must, then use arrow functions
|
||||
instead of aliasing it.
|
||||
|
||||
```js
|
||||
// good
|
||||
class Users {
|
||||
add(user) {
|
||||
return createUser(user)
|
||||
.then(response => this.users.push(response.user));
|
||||
}
|
||||
}
|
||||
|
||||
// bad
|
||||
class Users {
|
||||
add(user) {
|
||||
const self = this;
|
||||
return createUser(user).then(function (response) {
|
||||
self.users.push(response.user);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Getters and Setters
|
||||
|
||||
Feel free to use getters that are free from [side effects][sideeffect], like
|
||||
providing a length property for a collection class.
|
||||
|
||||
Do not use setters, they cause more problems than they can solve.
|
||||
|
||||
[sideeffect]: http://en.wikipedia.org/wiki/Side_effect_(computer_science)
|
||||
|
||||
## React
|
||||
|
||||
The following style guide rules are specific for working with the React framework.
|
||||
|
||||
### Prefer reactDirective over react-component
|
||||
|
||||
When using `ngReact` to embed your react components inside Angular HTML, prefer the
|
||||
`reactDirective` service over the `react-component` directive.
|
||||
You can read more about these two ngReact methods [here](https://github.com/ngReact/ngReact#features).
|
||||
|
||||
Using `react-component` means adding a bunch of components into angular, while `reactDirective` keeps them isolated, and is also a more succinct syntax.
|
||||
|
||||
**Good:**
|
||||
```html
|
||||
<hello-component fname="person.fname" lname="person.lname" watch-depth="reference"></hello-component>
|
||||
```
|
||||
|
||||
**Bad:**
|
||||
```html
|
||||
<react-component name="HelloComponent" props="person" watch-depth="reference" />
|
||||
```
|
||||
|
||||
### Action function names and prop function names
|
||||
|
||||
Name action functions in the form of a strong verb and passed properties in the form of on<Subject><Change>. E.g:
|
||||
|
||||
```jsx
|
||||
<sort-button onClick={action.sort}/>
|
||||
<pagerButton onPageNext={action.turnToNextPage} />
|
||||
```
|
||||
|
||||
## Attribution
|
||||
|
||||
Parts of the JavaScript style guide were initially forked from the
|
||||
[node style guide](https://github.com/felixge/node-style-guide) created by [Felix Geisendörfer](http://felixge.de/) which is
|
||||
licensed under the [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/)
|
||||
license.
|
||||
|
|
|
@ -75,7 +75,7 @@ Reviewers are not simply evaluating the code itself, they are also evaluating th
|
|||
|
||||
Having a relatively consistent codebase is an important part of us building a sustainable project. With dozens of active contributors at any given time, we rely on automation to help ensure consistency - we enforce a comprehensive set of linting rules through CI. We're also rolling out prettier to make this even more automatic.
|
||||
|
||||
For things that can't be easily automated, we maintain various link:https://github.com/elastic/kibana/tree/master/style_guides[styleguides] that authors should adhere to and reviewers should keep in mind when they review a pull request.
|
||||
For things that can't be easily automated, we maintain a link:https://github.com/elastic/kibana/blob/master/STYLEGUIDE.md[style guide] that authors should adhere to and reviewers should keep in mind when they review a pull request.
|
||||
|
||||
Beyond that, we're into subjective territory. Statements like "this isn't very readable" are hardly helpful since they can't be qualified, but that doesn't mean a reviewer should outright ignore code that is hard to understand due to how it is written. There isn't one definitively "best" way to write any particular code, so pursuing such shouldn't be our goal. Instead, reviewers and authors alike must accept that there are likely many different appropriate ways to accomplish the same thing with code, and so long as the contribution is utilizing one of those ways, then we're in good shape.
|
||||
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
# Angular Style Guide
|
||||
|
||||
Kibana is written in Angular, and uses several utility methods to make using
|
||||
Angular easier.
|
||||
|
||||
## Defining modules
|
||||
|
||||
Angular modules are defined using a custom require module named `ui/modules`.
|
||||
It is used as follows:
|
||||
|
||||
```js
|
||||
const app = require('ui/modules').get('app/namespace');
|
||||
```
|
||||
|
||||
`app` above is a reference to an Angular module, and can be used to define
|
||||
controllers, providers and anything else used in Angular. While you can use
|
||||
this module to create/get any module with ui/modules, we generally use the
|
||||
"kibana" module for everything.
|
||||
|
||||
## Promises
|
||||
|
||||
A more robust version of Angular's `$q` service is available as `Promise`. It
|
||||
can be used in the same way as `$q`, but it comes packaged with several utility
|
||||
methods that provide many of the same useful utilities as Bluebird.
|
||||
|
||||
```js
|
||||
app.service('CustomService', (Promise, someFunc) => {
|
||||
new Promise((resolve, reject) => {
|
||||
...
|
||||
});
|
||||
|
||||
const promisedFunc = Promise.cast(someFunc);
|
||||
|
||||
return Promise.resolve('value');
|
||||
});
|
||||
```
|
||||
|
||||
### Routes
|
||||
|
||||
Angular routes are defined using a custom require module named `routes` that
|
||||
removes much of the required boilerplate.
|
||||
|
||||
```js
|
||||
import routes from 'ui/routes';
|
||||
|
||||
routes.when('/my/object/route/:id?', {
|
||||
// angular route code goes here
|
||||
});
|
||||
```
|
||||
|
||||
## Private modules
|
||||
|
||||
A service called `Private` is available to load any function as an angular
|
||||
module without needing to define it as such. It is used as follows:
|
||||
|
||||
```js
|
||||
import PrivateExternalClass from 'path/to/some/class';
|
||||
app.controller('myController', function($scope, otherDeps, Private) {
|
||||
const ExternalClass = Private(PrivateExternalClass);
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
**Note:** Use this sparingly. Whenever possible, your modules should not be
|
||||
coupled to the angular application itself.
|
|
@ -1,32 +0,0 @@
|
|||
|
||||
# API Style Guide
|
||||
|
||||
## Paths
|
||||
|
||||
API routes must start with the `/api/` path segment, and should be followed by the plugin id if applicable:
|
||||
|
||||
*Right:* `/api/marvel/nodes`
|
||||
*Wrong:* `/marvel/api/nodes`
|
||||
|
||||
## Versions
|
||||
|
||||
Kibana won't be supporting multiple API versions, so API's should not define a version.
|
||||
|
||||
*Right:* `/api/kibana/index_patterns`
|
||||
*Wrong:* `/api/kibana/v1/index_patterns`
|
||||
|
||||
## snake_case
|
||||
|
||||
Kibana uses `snake_case` for the entire API, just like Elasticsearch. All urls, paths, query string parameters, values, and bodies should be `snake_case` formatted.
|
||||
|
||||
*Right:*
|
||||
```
|
||||
POST /api/kibana/index_patterns
|
||||
{
|
||||
"id": "...",
|
||||
"time_field_name": "...",
|
||||
"fields": [
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
|
@ -1,70 +0,0 @@
|
|||
# Architecture Style Guide
|
||||
|
||||
## Plugin Architecture
|
||||
|
||||
These are a collection of architectural styles to follow when building Kibana plugins. This includes applications meant to be accessed from the Kibana sidebar, as applications are simply one type of plugin.
|
||||
|
||||
### File and Folder Structure
|
||||
|
||||
Kibana plugins, both in core and those developed independent of Kibana, should follow this basic file and folder structure:
|
||||
|
||||
```
|
||||
plugin_root:
|
||||
|
||||
.
|
||||
├── common/
|
||||
├── public/
|
||||
├── server/
|
||||
└── index.js
|
||||
```
|
||||
|
||||
<dl>
|
||||
<dt>index.js</dt>
|
||||
<dd>The entry point to your application, this file is loaded when your code is being used in Kibana. This module should simply export a function that takes the <code>kibana</code> server object and initializes your application. This is where you define things like the <em>id</em>, any <em>uiExports</em>, dependencies on other plugins and applications, any configuration, any initialization code, and the location of the <em>public</em> folder.</dd>
|
||||
|
||||
<dt>public</dt>
|
||||
<dd>This folder is where you place client-side code for your application. Anything that is run in the browser belongs in here.</dd>
|
||||
<dd><strong>NOTE</strong>: An alias for this folder is created at <code>plugins/{id}</code>, where <code>id</code> is what you defined in the plugin entry point. If your application's <code>id</code> is <code>utile</code>, and you were trying to import a module from <code>public/lib/formatter.js</code>, you could import the module as <code>plugins/utile/lib/formatter</code>.
|
||||
</dd>
|
||||
|
||||
<dt>server</dt>
|
||||
<dd>This folder is where server code belongs. Things like custom routes, data models, or any other code that should only be executed on the server should go here.</dd>
|
||||
|
||||
<dt>common</dt>
|
||||
<dd>This folder is where code that is useful on both the client and the server belongs. A consistent example of this is constants, but this could apply to helper modules as well.</dd>
|
||||
<dd><strong>NOTE</strong>: If you'd like to avoid adding <code>../common</code> to your public code, you could use <em>webpackShims</em> to resolve the path without traversing backwards.</dd>
|
||||
</dl>
|
||||
|
||||
## Subdirectories
|
||||
|
||||
As code gets more complex, it becomes important to organize it into subdirectories. Each subdirectory should contain an `index.js` file that exposes the contents of that directory.
|
||||
|
||||
```
|
||||
plugin_root:
|
||||
|
||||
.
|
||||
├── common/
|
||||
├── public/
|
||||
├───── component_one/
|
||||
├──────── component_one.js
|
||||
├──────── component_one.html
|
||||
├──────── component_one_helper.js
|
||||
├──────── index.js
|
||||
├───── index.js
|
||||
├── server/
|
||||
└── index.js
|
||||
```
|
||||
|
||||
```
|
||||
public/component_one/index.js:
|
||||
|
||||
import './component_one';
|
||||
```
|
||||
|
||||
```
|
||||
public/index.js (consumer of component_one):
|
||||
|
||||
import './component_one';
|
||||
```
|
||||
|
||||
NOTE: There is currently a Webpack plugin that allows import statements to resolve in multiple ways. The statement `import './component_one'` in the `public/index.js` file above would successfully resolve to both `/public/component_one/component_one.js` and `/public/component_one/index.js`. If there is both a named file and an `index.js` file, Webpack will resolve to the `index.js` file. This functionality will be removed in the future, and when that happens, Webpack will only resolve to the `index.js` file.
|
|
@ -1,84 +0,0 @@
|
|||
# HTML Style Guide
|
||||
|
||||
## Camel case ID and other attribute values
|
||||
|
||||
Use camel case for the values of attributes such as IDs and data test subject selectors.
|
||||
|
||||
```html
|
||||
<button
|
||||
id="veryImportantButton"
|
||||
data-test-subj="clickMeButton"
|
||||
>
|
||||
Click me
|
||||
</button>
|
||||
```
|
||||
|
||||
The only exception is in cases where you're dynamically creating the value, and you need to use
|
||||
hyphens as delimiters:
|
||||
|
||||
```html
|
||||
<button
|
||||
ng-repeat="button in buttons"
|
||||
id="{{ 'veryImportantButton-' + button.id }}"
|
||||
data-test-subj="{{ 'clickMeButton-' + button.id }}"
|
||||
>
|
||||
{{ button.label }}
|
||||
</button>
|
||||
```
|
||||
|
||||
## Capitalization in HTML and CSS should always match
|
||||
|
||||
It's important that when you write CSS selectors using classes, IDs, and attributes
|
||||
(keeping in mind that we should _never_ use IDs and attributes in our selectors), that the
|
||||
capitalization in the CSS matches that used in the HTML. [HTML and CSS follow different case sensitivity rules](http://reference.sitepoint.com/css/casesensitivity), and we can avoid subtle gotchas by ensuring we use the
|
||||
same capitalization in both of them.
|
||||
|
||||
## Multiple attribute values
|
||||
|
||||
When an element has multiple attributes, each attribute including the first should be on its own line with a single indent.
|
||||
This makes the attributes easier to scan and compare across similar elements.
|
||||
|
||||
The closing bracket should be on its own line. This allows attributes to be shuffled and edited without having to move the bracket around. It also makes it easier to scan vertically and match opening and closing brackets. This format
|
||||
is inspired by the positioning of the opening and closing parentheses in [Pug/Jade](https://pugjs.org/language/attributes.html#multiline-attributes).
|
||||
|
||||
```html
|
||||
<div
|
||||
attribute1="value1"
|
||||
attribute2="value2"
|
||||
attribute3="value3"
|
||||
>
|
||||
Hello
|
||||
</div>
|
||||
```
|
||||
|
||||
If the element doesn't have children, add the closing tag on the same line as the opening tag's closing bracket.
|
||||
|
||||
```html
|
||||
<div
|
||||
attribute1="value1"
|
||||
attribute2="value2"
|
||||
attribute3="value3"
|
||||
></div>
|
||||
```
|
||||
|
||||
## Nested elements belong on multiple lines
|
||||
|
||||
Putting nested elements on multiple lines makes it easy to scan and identify tags, attributes, and text
|
||||
nodes, and to distinguish elements from one another. This is especially useful if there are multiple
|
||||
similar elements which appear sequentially in the markup.
|
||||
|
||||
### Do
|
||||
|
||||
```html
|
||||
<div>
|
||||
<span>
|
||||
hi
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Don't
|
||||
|
||||
```html
|
||||
<div><span>hi</span></div>
|
||||
```
|
|
@ -1,873 +0,0 @@
|
|||
# JavaScript Style Guide
|
||||
|
||||
## Attribution
|
||||
|
||||
This JavaScript guide forked from the [node style guide](https://github.com/felixge/node-style-guide) created by [Felix Geisendörfer](http://felixge.de/) and is
|
||||
licensed under the [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/)
|
||||
license.
|
||||
|
||||
## Prettier
|
||||
|
||||
We are gradually moving the Kibana code base over to Prettier.
|
||||
|
||||
Prettier is set up to run with ESLint, and to add new code paths to Prettier,
|
||||
see `.eslintrc.js` in the root of the Kibana repo. This also means that if you
|
||||
get ESLint errors related to Prettier, run `node scripts/eslint --fix` from the
|
||||
root of the Kibana repo to fix these.
|
||||
|
||||
## 2 Spaces for indention
|
||||
|
||||
Use 2 spaces for indenting your code and swear an oath to never mix tabs and
|
||||
spaces - a special kind of hell is awaiting you otherwise.
|
||||
|
||||
## Newlines
|
||||
|
||||
Use UNIX-style newlines (`\n`), and a newline character as the last character
|
||||
of a file. Windows-style newlines (`\r\n`) are forbidden inside any repository.
|
||||
|
||||
## No trailing whitespace
|
||||
|
||||
Just like you brush your teeth after every meal, you clean up any trailing
|
||||
whitespace in your JS files before committing. Otherwise the rotten smell of
|
||||
careless neglect will eventually drive away contributors and/or co-workers.
|
||||
|
||||
## Use Semicolons
|
||||
|
||||
According to [scientific research][hnsemicolons], the usage of semicolons is
|
||||
a core value of our community. Consider the points of [the opposition][], but
|
||||
be a traditionalist when it comes to abusing error correction mechanisms for
|
||||
cheap syntactic pleasures.
|
||||
|
||||
[the opposition]: http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding
|
||||
[hnsemicolons]: http://news.ycombinator.com/item?id=1547647
|
||||
|
||||
## 100 characters per line
|
||||
|
||||
You should limit your lines to 100 chars. Prettier will check for that line width where enabled.
|
||||
|
||||
## Use `const` for variables
|
||||
|
||||
Your variable references should rarely be mutable, so use `const` for almost
|
||||
everything. If you absolutely *must* mutate a reference, use `let`.
|
||||
|
||||
```js
|
||||
// good
|
||||
const foo = 'bar';
|
||||
|
||||
// if absolutely necessary, OK
|
||||
let foo;
|
||||
|
||||
// bad
|
||||
var foo = 'bar';
|
||||
```
|
||||
|
||||
## Use single quotes for fixed strings
|
||||
|
||||
Use single quotes, unless you are writing JSON.
|
||||
|
||||
```js
|
||||
// good
|
||||
const foo = 'bar';
|
||||
|
||||
// bad
|
||||
const foo = "bar";
|
||||
```
|
||||
|
||||
## Use template strings to interpolate variables into strings
|
||||
|
||||
```js
|
||||
// good
|
||||
const foo = `Hello, ${name}`;
|
||||
|
||||
// bad
|
||||
const foo = 'Hello, ' + name;
|
||||
```
|
||||
|
||||
## Use template strings to avoid escaping single quotes
|
||||
|
||||
Because readability is paramount.
|
||||
|
||||
```js
|
||||
// good
|
||||
const foo = `You won't believe this.`;
|
||||
|
||||
// bad
|
||||
const foo = 'You won\'t believe this.';
|
||||
const foo = "You won't believe this.";
|
||||
```
|
||||
|
||||
## Use object destructuring
|
||||
|
||||
This helps avoid temporary references and helps prevent typo-related bugs.
|
||||
|
||||
```js
|
||||
// best
|
||||
function fullName({ first, last }) {
|
||||
return `${first} ${last}`;
|
||||
}
|
||||
|
||||
// good
|
||||
function fullName(user) {
|
||||
const { first, last } = user;
|
||||
return `${first} ${last}`;
|
||||
}
|
||||
|
||||
// bad
|
||||
function fullName(user) {
|
||||
const first = user.first;
|
||||
const last = user.last;
|
||||
return `${first} ${last}`;
|
||||
}
|
||||
```
|
||||
|
||||
## Use array destructuring
|
||||
|
||||
Directly accessing array values via index should be avoided, but if it is
|
||||
necessary, use array destructuring:
|
||||
|
||||
```js
|
||||
const arr = [1, 2, 3];
|
||||
|
||||
// good
|
||||
const [first, second] = arr;
|
||||
|
||||
// bad
|
||||
const first = arr[0];
|
||||
const second = arr[1];
|
||||
```
|
||||
|
||||
## Opening braces go on the same line
|
||||
|
||||
Your opening braces go on the same line as the statement.
|
||||
|
||||
```js
|
||||
// good
|
||||
if (true) {
|
||||
console.log('winning');
|
||||
}
|
||||
|
||||
// bad
|
||||
if (true)
|
||||
{
|
||||
console.log('losing');
|
||||
}
|
||||
```
|
||||
|
||||
Also, notice the use of whitespace before and after the condition statement.
|
||||
|
||||
## Always use braces for conditionals and loops
|
||||
|
||||
```js
|
||||
// good
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
// bad
|
||||
if (err) cb(err);
|
||||
|
||||
// bad
|
||||
if (err)
|
||||
return cb(err);
|
||||
```
|
||||
|
||||
## Declare one variable per line, wherever it makes the most sense
|
||||
|
||||
This makes it easier to re-order the lines. However, ignore
|
||||
[Crockford][crockfordconvention] when it comes to declaring variables deeper
|
||||
inside a function, just put the declarations wherever they make sense.
|
||||
|
||||
```js
|
||||
// good
|
||||
const keys = ['foo', 'bar'];
|
||||
const values = [23, 42];
|
||||
|
||||
// bad
|
||||
const keys = ['foo', 'bar'],
|
||||
values = [23, 42];
|
||||
```
|
||||
|
||||
[crockfordconvention]: http://javascript.crockford.com/code.html
|
||||
|
||||
## Use lowerCamelCase for variables, properties and function names
|
||||
|
||||
Variables, properties and function names should use `lowerCamelCase`. They
|
||||
should also be descriptive. Single character variables and uncommon
|
||||
abbreviations should generally be avoided.
|
||||
|
||||
```js
|
||||
// good
|
||||
const adminUser = getAdmin();
|
||||
|
||||
// bad
|
||||
const admin_user = getAdmin();
|
||||
```
|
||||
|
||||
## Use UpperCamelCase for class names (constructors)
|
||||
|
||||
Class names should be capitalized using `UpperCamelCase`.
|
||||
|
||||
```js
|
||||
// good
|
||||
class BankAccount {}
|
||||
|
||||
// bad
|
||||
class bank_account {}
|
||||
class bankAccount {}
|
||||
```
|
||||
|
||||
## Prefix private class methods with an underscore
|
||||
|
||||
Identifying private class methods makes it easier to differentiate a class's public and internal
|
||||
APIs, and makes private methods easier to mark as `private` when the code is migrated to TypeScript.
|
||||
|
||||
```js
|
||||
// good
|
||||
class BankAccount {
|
||||
addFunds() {}
|
||||
|
||||
_calculateInterest() {}
|
||||
}
|
||||
```
|
||||
|
||||
## Magic numbers/strings
|
||||
|
||||
These are numbers (or other values) simply used in line in your code. *Do not
|
||||
use these*, give them a variable name so they can be understood and changed
|
||||
easily.
|
||||
|
||||
```js
|
||||
// good
|
||||
const minWidth = 300;
|
||||
|
||||
if (width < minWidth) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
if (width < 300) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Object properties and functions
|
||||
|
||||
Use object method shorthand syntax for functions on objects:
|
||||
|
||||
```js
|
||||
// good
|
||||
const foo = {
|
||||
bar() {
|
||||
...
|
||||
}
|
||||
};
|
||||
|
||||
// bad
|
||||
const foo = {
|
||||
bar: function () {
|
||||
...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Use property value shorthand syntax for properties that share a name with a
|
||||
variable. And put them at the beginning:
|
||||
|
||||
```js
|
||||
const bar = true;
|
||||
|
||||
// good
|
||||
const foo = {
|
||||
bar
|
||||
};
|
||||
|
||||
// bad
|
||||
const foo = {
|
||||
bar: bar
|
||||
};
|
||||
|
||||
// also bad (bar should be first)
|
||||
const foo = {
|
||||
baz: false,
|
||||
bar
|
||||
};
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
Module dependencies should be written using native ES2015 syntax wherever
|
||||
possible (which is almost everywhere):
|
||||
|
||||
```js
|
||||
// good
|
||||
import { mapValues } from 'lodash';
|
||||
export mapValues;
|
||||
|
||||
// bad
|
||||
const _ = require('lodash');
|
||||
module.exports = _.mapValues;
|
||||
|
||||
// worse
|
||||
define(['lodash'], function (_) {
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
In those extremely rare cases where you're writing server-side JavaScript in a
|
||||
file that does not pass run through webpack, then use CommonJS modules.
|
||||
|
||||
In those even rarer cases where you're writing client-side code that does not
|
||||
run through webpack, then do not use a module loader at all.
|
||||
|
||||
## Import only top-level modules
|
||||
|
||||
The files inside a module are implementation details of that module. They
|
||||
should never be imported directly. Instead, you must only import the top-level
|
||||
API that's exported by the module itself.
|
||||
|
||||
Without a clear mechanism in place in JS to encapsulate protected code, we make
|
||||
a broad assumption that anything beyond the root of a module is an
|
||||
implementation detail of that module.
|
||||
|
||||
On the other hand, a module should be able to import parent and sibling
|
||||
modules.
|
||||
|
||||
```js
|
||||
// good
|
||||
import foo from 'foo';
|
||||
import child from './child';
|
||||
import parent from '../';
|
||||
import ancestor from '../../../';
|
||||
import sibling from '../foo';
|
||||
|
||||
// bad
|
||||
import inFoo from 'foo/child';
|
||||
import inSibling from '../foo/child';
|
||||
```
|
||||
|
||||
## Use named exports only
|
||||
|
||||
Favor named exports over default exports. See [#8641](https://github.com/elastic/kibana/issues/8641) for more background on this decision.
|
||||
|
||||
```js
|
||||
// good
|
||||
import { foo } from 'foo';
|
||||
export foo;
|
||||
|
||||
// bad
|
||||
import myDefaultModule from 'foo/child';
|
||||
export default myDefaultModule;
|
||||
```
|
||||
|
||||
## Global definitions
|
||||
|
||||
Don't do this. Everything should be wrapped in a module that can be depended on
|
||||
by other modules. Even things as simple as a single value should be a module.
|
||||
|
||||
## Function definitions
|
||||
|
||||
Use function declarations over function expressions, so that their names will
|
||||
show up in stack traces, making errors easier to debug.
|
||||
|
||||
Also, keep function definitions above other code instead of relying on function
|
||||
hoisting.
|
||||
|
||||
```js
|
||||
// good
|
||||
function myFunc() {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
const myFunc = function () {
|
||||
...
|
||||
};
|
||||
```
|
||||
|
||||
## Arrow functions
|
||||
|
||||
If you must use a function expression, then use an arrow function:
|
||||
|
||||
```js
|
||||
// good
|
||||
[1, 2, 3].map((n) => {
|
||||
const m = doSomething(n);
|
||||
return m - n;
|
||||
});
|
||||
|
||||
// bad
|
||||
[1, 2, 3].map(function (n) {
|
||||
const m = doSomething(n);
|
||||
return m - n;
|
||||
});
|
||||
```
|
||||
|
||||
If your function body does not include braces and only accepts one argument,
|
||||
then omit the argument parentheses:
|
||||
|
||||
```js
|
||||
// good
|
||||
[1, 2, 3].map(n => n + 1);
|
||||
|
||||
// bad
|
||||
[1, 2, 3].map((n) => n + 1);
|
||||
|
||||
// bad
|
||||
[1, 2, 3].map(n => {
|
||||
return n + 1;
|
||||
});
|
||||
```
|
||||
|
||||
If your arrow function is only returning an object literal, then wrap the
|
||||
object in parentheses rather than using an explicit return:
|
||||
|
||||
```js
|
||||
// good
|
||||
() => ({
|
||||
foo: 'bar'
|
||||
})
|
||||
|
||||
// bad
|
||||
() => {
|
||||
return {
|
||||
foo: 'bar'
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Object / Array creation
|
||||
|
||||
Use trailing commas and put *short* declarations on a single line. Only quote
|
||||
keys when your interpreter complains:
|
||||
|
||||
```js
|
||||
// good
|
||||
const a = ['hello', 'world'];
|
||||
const b = {
|
||||
good: 'code',
|
||||
'is generally': 'pretty'
|
||||
};
|
||||
|
||||
// bad
|
||||
const a = [
|
||||
'hello', 'world'
|
||||
];
|
||||
const b = {'good': 'code'
|
||||
, is generally: 'pretty'
|
||||
};
|
||||
```
|
||||
|
||||
## Object / Array iterations, transformations and operations
|
||||
|
||||
Use native methods to iterate and transform arrays and objects where possible.
|
||||
Avoid `for` and `while` loops as they introduce the possibility of infinite
|
||||
loops and break out of our preferred convention of declarative programming.
|
||||
|
||||
Use descriptive variable names in the closures.
|
||||
|
||||
Use a utility library as needed and where it will make code more
|
||||
comprehensible.
|
||||
|
||||
```js
|
||||
// best
|
||||
const userNames = users.map(user => user.name);
|
||||
|
||||
// ok
|
||||
import { pluck } from 'lodash';
|
||||
const userNames = pluck(users, 'name');
|
||||
|
||||
// bad
|
||||
const userNames = [];
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
userNames.push(users[i].name);
|
||||
}
|
||||
```
|
||||
|
||||
## Use the spread operator (...) for copying arrays
|
||||
|
||||
This helps with expressiveness and readability.
|
||||
|
||||
```js
|
||||
const arr = [1, 2, 3];
|
||||
|
||||
// good
|
||||
const arrCopy = [...arr];
|
||||
|
||||
// bad
|
||||
const arrCopy = arr.slice();
|
||||
```
|
||||
|
||||
## Use the === operator
|
||||
|
||||
Programming is not about remembering [stupid rules][comparisonoperators]. Use
|
||||
the triple equality operator as it will work just as expected.
|
||||
|
||||
```js
|
||||
const a = 0;
|
||||
|
||||
// good
|
||||
if (a !== '') {
|
||||
console.log('winning');
|
||||
}
|
||||
|
||||
// bad
|
||||
if (a == '') {
|
||||
console.log('losing');
|
||||
}
|
||||
```
|
||||
|
||||
[comparisonoperators]: https://developer.mozilla.org/en/JavaScript/Reference/Operators/Comparison_Operators
|
||||
|
||||
## Only use ternary operators for small, simple code
|
||||
|
||||
And *never* use multiple ternaries together, because they make it more
|
||||
difficult to reason about how different values flow through the conditions
|
||||
involved. Instead, structure the logic for maximum readability.
|
||||
|
||||
```js
|
||||
// good, a situation where only 1 ternary is needed
|
||||
const foo = (a === b) ? 1 : 2;
|
||||
|
||||
// bad
|
||||
const foo = (a === b) ? 1 : (a === c) ? 2 : 3;
|
||||
```
|
||||
|
||||
## Do not extend built-in prototypes
|
||||
|
||||
Do not extend the prototype of native JavaScript objects. Your future self will
|
||||
be forever grateful.
|
||||
|
||||
```js
|
||||
// bad
|
||||
Array.prototype.empty = function () {
|
||||
return !this.length;
|
||||
}
|
||||
```
|
||||
|
||||
## Use descriptive conditions
|
||||
|
||||
Any non-trivial conditions should be converted to functions or assigned to
|
||||
descriptively named variables. By breaking up logic into smaller,
|
||||
self-contained blocks, it becomes easier to reason about the higher-level
|
||||
logic. Additionally, these blocks become good candidates for extraction into
|
||||
their own modules, with unit-tests.
|
||||
|
||||
```js
|
||||
// best
|
||||
function isShape(thing) {
|
||||
return thing instanceof Shape;
|
||||
}
|
||||
function notSquare(thing) {
|
||||
return !(thing instanceof Square);
|
||||
}
|
||||
if (isShape(thing) && notSquare(thing)) {
|
||||
...
|
||||
}
|
||||
|
||||
// good
|
||||
const isShape = thing instanceof Shape;
|
||||
const notSquare = !(thing instanceof Square);
|
||||
if (isShape && notSquare) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
if (thing instanceof Shape && !(thing instanceof Square)) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Name regular expressions
|
||||
|
||||
```js
|
||||
// good
|
||||
const validPassword = /^(?=.*\d).{4,}$/;
|
||||
|
||||
if (password.length >= 4 && validPassword.test(password)) {
|
||||
console.log('password is valid');
|
||||
}
|
||||
|
||||
// bad
|
||||
if (password.length >= 4 && /^(?=.*\d).{4,}$/.test(password)) {
|
||||
console.log('losing');
|
||||
}
|
||||
```
|
||||
|
||||
## Write small functions
|
||||
|
||||
Keep your functions short. A good function fits on a slide that the people in
|
||||
the last row of a big room can comfortably read. So don't count on them having
|
||||
perfect vision and limit yourself to ~15 lines of code per function.
|
||||
|
||||
## Use "rest" syntax rather than built-in `arguments`
|
||||
|
||||
For expressiveness sake, and so you can be mix dynamic and explicit arguments.
|
||||
|
||||
```js
|
||||
// good
|
||||
function something(foo, ...args) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
function something(foo) {
|
||||
const args = Array.from(arguments).slice(1);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Default argument syntax
|
||||
|
||||
Always use the default argument syntax for optional arguments.
|
||||
|
||||
```js
|
||||
// good
|
||||
function foo(options = {}) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
function foo(options) {
|
||||
if (typeof options === 'undefined') {
|
||||
options = {};
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
And put your optional arguments at the end.
|
||||
|
||||
```js
|
||||
// good
|
||||
function foo(bar, options = {}) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
function foo(options = {}, bar) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Return/throw early from functions
|
||||
|
||||
To avoid deep nesting of if-statements, always return a function's value as early
|
||||
as possible. And where possible, do any assertions first:
|
||||
|
||||
```js
|
||||
// good
|
||||
function doStuff(val) {
|
||||
if (val > 100) {
|
||||
throw new Error('Too big');
|
||||
}
|
||||
|
||||
if (val < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... stuff
|
||||
}
|
||||
|
||||
// bad
|
||||
function doStuff(val) {
|
||||
if (val >= 0) {
|
||||
if (val < 100) {
|
||||
// ... stuff
|
||||
} else {
|
||||
throw new Error('Too big');
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Chaining operations
|
||||
|
||||
When using a chaining syntax, indent the subsequent chained operations.
|
||||
|
||||
Also, if the chain is long, each method should be on a new line.
|
||||
|
||||
```js
|
||||
// good
|
||||
$http.get('/info')
|
||||
.then(({ data }) => this.transformInfo(data))
|
||||
.then((transformed) => $http.post('/new-info', transformed))
|
||||
.then(({ data }) => console.log(data));
|
||||
|
||||
// bad
|
||||
$http.get('/info')
|
||||
.then(({ data }) => this.transformInfo(data))
|
||||
.then((transformed) => $http.post('/new-info', transformed))
|
||||
.then(({ data }) => console.log(data));
|
||||
```
|
||||
|
||||
## Avoid mutability and state
|
||||
|
||||
Wherever possible, do not rely on mutable state. This means you should not
|
||||
reassign variables, modify object properties, or push values to arrays.
|
||||
Instead, create new variables, and shallow copies of objects and arrays:
|
||||
|
||||
```js
|
||||
// good
|
||||
function addBar(foos, foo) {
|
||||
const newFoo = {...foo, name: 'bar'};
|
||||
return [...foos, newFoo];
|
||||
}
|
||||
|
||||
// bad
|
||||
function addBar(foos, foo) {
|
||||
foo.name = 'bar';
|
||||
foos.push(foo);
|
||||
}
|
||||
```
|
||||
|
||||
## Use thunks to create closures, where possible
|
||||
|
||||
For trivial examples (like the one that follows), thunks will seem like
|
||||
overkill, but they encourage isolating the implementation details of a closure
|
||||
from the business logic of the calling code.
|
||||
|
||||
```js
|
||||
// good
|
||||
function connectHandler(client, callback) {
|
||||
return () => client.connect(callback);
|
||||
}
|
||||
setTimeout(connectHandler(client, afterConnect), 1000);
|
||||
|
||||
// not as good
|
||||
setTimeout(() => {
|
||||
client.connect(afterConnect);
|
||||
}, 1000);
|
||||
|
||||
// bad
|
||||
setTimeout(() => {
|
||||
client.connect(() => {
|
||||
...
|
||||
});
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
## Use slashes for comments
|
||||
|
||||
Use slashes for both single line and multi line comments. Try to write
|
||||
comments that explain higher level mechanisms or clarify difficult
|
||||
segments of your code. *Don't use comments to restate trivial things*.
|
||||
|
||||
*Exception:* Comment blocks describing a function and its arguments
|
||||
(docblock) should start with `/**`, contain a single `*` at the beginning of
|
||||
each line, and end with `*/`.
|
||||
|
||||
|
||||
```js
|
||||
// good
|
||||
|
||||
// 'ID_SOMETHING=VALUE' -> ['ID_SOMETHING=VALUE', 'SOMETHING', 'VALUE']
|
||||
const matches = item.match(/ID_([^\n]+)=([^\n]+)/));
|
||||
|
||||
/**
|
||||
* Fetches a user from...
|
||||
* @param {string} id - id of the user
|
||||
* @return {Promise}
|
||||
*/
|
||||
function loadUser(id) {
|
||||
// This function has a nasty side effect where a failure to increment a
|
||||
// redis counter used for statistics will cause an exception. This needs
|
||||
// to be fixed in a later iteration.
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
const isSessionValid = (session.expires < Date.now());
|
||||
if (isSessionValid) {
|
||||
...
|
||||
}
|
||||
|
||||
// bad
|
||||
|
||||
// Execute a regex
|
||||
const matches = item.match(/ID_([^\n]+)=([^\n]+)/));
|
||||
|
||||
// Usage: loadUser(5, function() { ... })
|
||||
function loadUser(id, cb) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Check if the session is valid
|
||||
const isSessionValid = (session.expires < Date.now());
|
||||
// If the session is valid
|
||||
if (isSessionValid) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Do not comment out code
|
||||
|
||||
We use a version management system. If a line of code is no longer needed,
|
||||
remove it, don't simply comment it out.
|
||||
|
||||
## Classes/Constructors and Inheritance
|
||||
|
||||
If you must use a constructor, then use the native `class` syntax. *Never* use
|
||||
third party "class" utilities, and never mutate prototypes.
|
||||
|
||||
```js
|
||||
// best (no local state at all)
|
||||
function addUser(users, user) {
|
||||
return [...users, user];
|
||||
}
|
||||
const users = addUser([], { name: 'foo' });
|
||||
|
||||
// good
|
||||
class Users {
|
||||
add(user) {
|
||||
...
|
||||
}
|
||||
}
|
||||
const users = new Users();
|
||||
users.add({ name: 'foo' });
|
||||
|
||||
// bad
|
||||
function Users() {
|
||||
...
|
||||
}
|
||||
Users.prototype.add = function () {
|
||||
...
|
||||
};
|
||||
const users = new Users();
|
||||
users.add({ name: 'foo' });
|
||||
```
|
||||
|
||||
## Do not alias `this`
|
||||
|
||||
Try not to rely on `this` at all, but if you must, then use arrow functions
|
||||
instead of aliasing it.
|
||||
|
||||
```js
|
||||
// good
|
||||
class Users {
|
||||
add(user) {
|
||||
return createUser(user)
|
||||
.then(response => this.users.push(response.user));
|
||||
}
|
||||
}
|
||||
|
||||
// bad
|
||||
class Users {
|
||||
add(user) {
|
||||
const self = this;
|
||||
return createUser(user).then(function (response) {
|
||||
self.users.push(response.user);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Getters and Setters
|
||||
|
||||
Feel free to use getters that are free from [side effects][sideeffect], like
|
||||
providing a length property for a collection class.
|
||||
|
||||
Do not use setters, they cause more problems than they can solve.
|
||||
|
||||
[sideeffect]: http://en.wikipedia.org/wiki/Side_effect_(computer_science)
|
|
@ -1,219 +0,0 @@
|
|||
# React Style Guide
|
||||
|
||||
### Prefer Stateless functional components where possible.
|
||||
Stateless function components are more concise, and there are plans for react to increase performance of them.
|
||||
Good:
|
||||
```
|
||||
export function KuiButton(props) {
|
||||
return <button className="kuiButton" {...props} />
|
||||
};
|
||||
```
|
||||
Bad:
|
||||
```
|
||||
export class KuiButton extends React.Component {
|
||||
render() {
|
||||
return <button className="kuiButton" {...this.props} />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### When state is involved, use ES6 style React Classes over ES5.
|
||||
Good:
|
||||
```
|
||||
export class ClickCounter extends React.Component {
|
||||
state = { clickCount: 0 };
|
||||
|
||||
onClick = () => {
|
||||
this.setState(prevState => ({
|
||||
clickCount: prevState.clickCount + 1
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
return <button className="kuiButton" onClick={this.onClick} />
|
||||
}
|
||||
}
|
||||
```
|
||||
Bad:
|
||||
```
|
||||
export const ClickCounter = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
clickCount: 0
|
||||
};
|
||||
},
|
||||
onClick() {
|
||||
this.setState(prevState => ({
|
||||
clickCount: prevState.clickCount + 1
|
||||
}));
|
||||
},
|
||||
render() {
|
||||
return <button className="kuiButton" onClick={this.onClick} />
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### When a state change involves the previous state or props, pass setState a function instead of an object.
|
||||
https://facebook.github.io/react/docs/react-component.html#setstate
|
||||
|
||||
Good:
|
||||
```
|
||||
this.setState((prevState, props) => ({
|
||||
clickCount: prevState.clickCount + props.incrementValue
|
||||
}));
|
||||
```
|
||||
|
||||
Bad:
|
||||
```
|
||||
this.setState({ clickCount: this.state.clickCount + this.props.incrementValue });
|
||||
```
|
||||
|
||||
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
|
||||
- https://facebook.github.io/react/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
|
||||
|
||||
This will be even more important when the fibers-based implementation is released:
|
||||
- https://github.com/acdlite/react-fiber-architecture
|
||||
- https://www.youtube.com/watch?v=ZCuYPiUIONs
|
||||
|
||||
### Prefer reactDirective over react-component
|
||||
When using ngReact to embed your react components inside angular html, prefer
|
||||
reactDirective over react-component. You can read more about these two ngReact methods [here](https://github.com/ngReact/ngReact#features).
|
||||
Using `react-component` means adding a bunch of components into angular, while `reactDirective` keeps them isolated,
|
||||
and is also a more succinct syntax.
|
||||
|
||||
Good:
|
||||
```
|
||||
<hello-component fname="person.fname" lname="person.lname" watch-depth="reference"></hello-component>
|
||||
```
|
||||
Bad:
|
||||
```
|
||||
<react-component name="HelloComponent" props="person" watch-depth="reference" />
|
||||
```
|
||||
|
||||
### Prefix ui_framework elements with kui, but not their file names.
|
||||
Good:
|
||||
```
|
||||
button.js:
|
||||
export function KuiButton(props) {
|
||||
return <button className="kuiButton" {...props} />
|
||||
};
|
||||
```
|
||||
Bad:
|
||||
```
|
||||
button.js:
|
||||
export function Button(props) {
|
||||
return <button className="kuiButton" {...props} />
|
||||
};
|
||||
```
|
||||
The filenames leave it off because snake casing already increases file name length.
|
||||
|
||||
### Action function names and prop function names
|
||||
|
||||
Name action functions in the form of a strong verb and passed properties in the form of on<Subject><Change>. E.g:
|
||||
```
|
||||
<sort-button onClick={action.sort}/>
|
||||
<pagerButton onPageNext={action.turnToNextPage} />
|
||||
```
|
||||
|
||||
### Avoid creating a function and passing that as a property, in render functions.
|
||||
Best (relies on [stage 2 proposal](https://github.com/tc39/proposal-class-public-fields)):
|
||||
```
|
||||
export class ClickCounter extends React.Component {
|
||||
state = { clickCount: 0 };
|
||||
|
||||
// This syntax ensures `this` is bound within handleClick
|
||||
onClick = () => {
|
||||
this.setState(prevState => { clickCount: prevState.clickCount + 1 });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <button className="kuiButton" onClick={this.onClick} />
|
||||
}
|
||||
}
|
||||
```
|
||||
Good:
|
||||
```
|
||||
export class ClickCounter extends React.Component {
|
||||
constructor() {
|
||||
this.state = { clickCount: 0 };
|
||||
this.onClick = this.onClick.bind(this);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.setState(prevState => { clickCount: prevState.clickCount + 1 });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <button className="kuiButton" onClick={this.onClick} />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Bad:
|
||||
```
|
||||
export class ClickCounter extends React.Component {
|
||||
state = { clickCount: 0 };
|
||||
|
||||
onClick() {
|
||||
this.setState(prevState => { clickCount: prevState.clickCount + 1 });
|
||||
}
|
||||
|
||||
render() {
|
||||
return <button className="kuiButton" onClick={() => this.onClick()} />
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Also Bad:
|
||||
```
|
||||
render() {
|
||||
return <button className="kuiButton" onClick={this.onClick.bind(this)} />
|
||||
}
|
||||
```
|
||||
|
||||
Background: https://facebook.github.io/react/docs/handling-events.html
|
||||
There is also an eslint rule we should be able to turn on for this.
|
||||
|
||||
### Never mutate state directly
|
||||
Good:
|
||||
```
|
||||
this.setState(prevState => { clickCount: prevState.clickCount + 1 });
|
||||
```
|
||||
Bad:
|
||||
```
|
||||
this.state.clickCount += 1;
|
||||
```
|
||||
|
||||
### Prefer primitives over objects when storing in state.
|
||||
Good:
|
||||
```
|
||||
this.setState({
|
||||
currentPage: 0,
|
||||
selectedIds: []
|
||||
});
|
||||
```
|
||||
|
||||
Discouraged:
|
||||
```
|
||||
this.setState({
|
||||
pager: new Pager(),
|
||||
selectedIds: new SelectedIds()
|
||||
});
|
||||
```
|
||||
|
||||
### Favor spread operators
|
||||
```
|
||||
render() {
|
||||
return <button className="kuiButton" {...this.props} />
|
||||
}
|
||||
```
|
||||
```
|
||||
export function Button({ className, ...rest }) {
|
||||
const classNames = classNames('KuiButton', className);
|
||||
return <button className={classNames} {...rest} />
|
||||
};
|
||||
```
|
||||
|
||||
## General Guidelines
|
||||
### Prefer pure functions when possible
|
||||
Pure functions are easier to understand. We don't want to have to think about side effects or mutated state. When invoking a pure function, all we have to think about is what goes in and what comes out.
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
# SCSS Style Guide
|
||||
|
||||
Our style guide is maintained in [EUI Sass Guidelines](https://elastic.github.io/eui/#/guidelines/sass).
|
Loading…
Add table
Add a link
Reference in a new issue