-
Notifications
You must be signed in to change notification settings - Fork 12
Code Style
⚠⚠ ⚠⚠ ⚠⚠
This wiki served in the early days of CKEditor 5 development and can be severely outdated.
Refer to the official CKEditor 5 documentation for up-to-date information.
General
Whitespace
Indentation
Braces
Multi-line statements and calls
Strict Mode
Naming
Strings
Enumerables
Comments
Linter (JSCS, JSHint) Annotations
Visibility Levels (public, protected and private)
Getters
Order Within Class Definition
Tests
- LF for line endings. Never CRLF.
- Maximum recommended line length is 120 chars, but can't be longer than 140 chars.
- No trailing spaces. Empty lines should not contain any spaces.
Whitespace inside parenthesis and before and after operators:
function foo( a, b, c, d, e ) {
if ( a > b ) {
c = ( d + e ) * 2;
}
}
foo( bar() );
( () => {
// Closure code
} )();
No whitespace for empty parenthesis:
let a = () => {
// Statements
};
a();
No whitespace before colon and semicolon:
let a, b;
a( 1, 2, 3 );
for ( let i = 0; i < 100; i++ ) {
// Statements
}
- Indentation with TAB, for both code and comments. Never use spaces.
- If you want to have the code readable, set TAB to 4 spaces in your IDE.
( function() {
function a() {
while ( b in a ) {
if ( b == c ) {
// Statements
}
}
}
} )();
Multiple lines condition. Use one tab for each line:
if ( some != really.complex &&
condition || with &&
( multiple == lines ) )
while ( some != really.complex &&
condition || with &&
( multiple == lines ) )
Braces start at the same line as the head statement and end aligned with it:
function a() {
// Statements
}
if ( a ) {
// Statements
} else if ( b ) {
// Statements
} else {
// Statements
}
try {
// Statements
} catch ( e ) {
// Statements
}
Whenever there is a multi-line function call:
- put the first param in a new line
- put every param in a separate line indented by one tab
- put the last closing parenthesis in new line, at the same indendation as the call beginning
Examples:
let myObj = new MyClass(
'Some long params',
'To make this',
'Multi line'
);
fooBar(
function() {
// ... code
}
);
fooBar(
new MyClass(
'Some long params',
'To make this',
'Multi line'
)
);
fooBar(
'A very long string',
function() {
// ... some kind
// ... of a
// ... callback
},
5,
new MyClass(
'It looks well',
paramA,
paramB,
new ShortClass( 2, 3, 5 ),
'Even when nested'
)
);
- Strict mode is automatically enabled in ES2105 modules, so explicit
'use strict';
must not be used. - Strict mode must be enforced in all code outside ES2015 modules (e.g. in CommonJS modules or in normal
<script>
tags). The following must be the first line (after header comments if they exist):'use strict';
.
The following are lexical rules for naming. Check Naming Guidelines for further details on conventions.
Variable, functions, namespaces, parameters and all undocumented cases must be named in lowerCamelCase:
let a;
let myLongNamedVariable = true;
function foo() {
}
function longNamedFunction( example, longNamedParameter ) {
}
CKEDITOR.ui; // Namespace
CKEDITOR.longNamedNamespace;
Classes must be named in UpperCamelCase:
class MyClass() {
}
let a = new MyClass();
Mixins must be named in UpperCamelCase, postfixed with "Mixin":
let SomeMixin = new Mixin( {
method1: ...,
method2: ...
} );
SomeMixin.mixin( target );
Global namespacing variables must be named in ALLCAPS:
// The main entry point of the CKEditor API
const CKEDITOR = {};
Use single quotes:
let a = 'I\'m an example for quotes';
Long strings should be concatenated with plus (+
):
let html = 'Line 1\n' +
'Line 2\n' +
'Line 3';
or template strings can be used (note that lines 2nd and 3rd will be indented in this case):
let html = `Line 1
Line 2
Line 3`;
Strings of HTML should use indentation for readability:
let html =
`<p>
<span>${ a }</span>
</p>`;
Enumerable constants (i.e. used as flag parameters or returned enums) should be defined as strings and documented using @typedef
:
/**
* Enum type that specifies relation between two positions.
*
* Possible values: `'after'`, `'before'`, `'same'`.
*
* @typedef {String} PositionRelation
*/
/**
* @returns {PositionRelation} Relation between given positions.
*/
function comparePositions( a, b ) {
// ...
return 'after';
// ...
return 'before';
// ...
return 'same';
}
// ...
if ( comparePositions( a, b ) == 'same' ) {
// ...
}
- Comments are always preceded by a blank line.
- Comments start with a capital first letter, but don't require a period at the end, unless you're writing full sentences.
- There must be a single space at the start of the text, right after the comment token.
Block comments (/* ... */
) are used for documentation only. Asterisks aligned with space:
/**
* Documentation for the following method.
*
* @returns {Object} Something.
*/
someMethod() {
// Statements
}
All other comments use line comments (//
):
// Comment about the following statement.
foo();
// Multiple line comments
// go through several
// line comments as well.
Comments related to tickets/issues, should not describe the whole issue fully. A short description should be used, together with the ticket number in parenthesis:
// Do this otherwise because of an IE bug. (#123)
foo();
When using comments in order to change linter settings, follow these rules:
- When defining globals (e.g. native DOM objects) use
/* globals document, Range */
. For the sake of simplicity, do not use:false
after global names (which prohibits overriding those variables). - When wanting to ignore a specific line use:
foo(); // jscs:ignore
Or:
// jscs:disable requireCurlyBraces
foo();
// jscs:enable requireCurlyBraces
Never use jscs:disable
without following jscs:enable
. Therefore, it's recommended to use jscs:ignore
.
Each class property (including methods, symbols, getters/setters) can be public, protected or private. The default visibility is public, so you shouldn't (because there's no need) document that a property is public.
Additional rules apply to private properties:
- names of private and protected properties which are exposed in a class prototype (or in any other way) should be prefixed with an underscore,
- when documenting a private variable which isn't added to a class prototype (or exposed in any other way) then
//
comments should be used, - symbol property (e.g.
this[ Symbol( 'symbolName' ) ]
) should be documented as@property {Type} _symbolName
.
Example:
class Foo {
/**
* The constructor (public, as its visibility isn't defined).
*/
constructor() {
/**
* Public property.
*/
this.foo = 1;
/**
* Protected property.
*
* @protected
*/
this._bar = 1;
/**
* @private
* @property {Number} _bom
*/
this[ Symbol( 'bom' ) ] = 1;
}
/**
* @private
*/
_somePrivateMethod() {}
}
// Some private helper.
//
// @private
// @returns {Number}
function doSomething() {
return 1;
}
Properties accessibility:
| Class | Package | Subclass | World
——————————————————————————————————————————————————
@public | y | y | y | y
——————————————————————————————————————————————————
@protected | y | y | y | n
——————————————————————————————————————————————————
@private | y | n | n | n
(y – accessible, n – not accessible)
For instance, a protected property is accessible from its own class in which it was defined, its whole package and from its subclasses (even if not in the same package).
You can use ES6 getters to simplify class API:
class Position {
// ...
get offset() {
return this.path[ this.path.length - 1 ];
}
}
Getter should feel like a natural property. There are several recommendations to follow when creating getters:
- they should be fast,
- they should not throw,
- they should not change object state,
- they should not return new instances of an object every time (so
foo.bar == foo.bar
is true); it is okay to create a new instance for the first call and cache it if it's possible.
Within class definition the methods and properties should be ordered as follows:
- constructor,
- getters/setters,
- iterators,
- public instance methods,
- public static methods,
- protected instance methods,
- protected static methods,
- private instance methods,
- private static methods.
Order within each group is left for the implementor.
There are some special rules for tests.
The outer most describe()
calls should create meaningful groups, so when all tests are run together a failing TC can be identified within the code base. For example:
describe( 'Editor', () => {
describe( 'constructor()', () => {
it( ... );
} );
...
} );