eslint-plugin-canonical
ESLint rules for Canonical ruleset.
Installation
npm install eslint --save-dev
npm install @babel/eslint-parser --save-dev
npm install eslint-plugin-canonical --save-dev
Configuration
- Set
parser
property to babel-eslint
. - Add
plugins
section and specify eslint-plugin-canonical
as a plugin. - Enable rules.
{
"parser": "@babel/eslint-parser",
"plugins": [
"canonical"
],
"rules": {
"canonical/filename-match-exported": 0,
"canonical/filename-match-regex": 0,
"canonical/filename-no-index": 0,
"canonical/id-match": [
2,
"(^[A-Za-z]+(?:[A-Z][a-z]*)*\\d*$)|(^[A-Z]+(_[A-Z]+)*(_\\d$)*$)|(^(_|\\$)$)",
{
"ignoreDestructuring": true,
"ignoreNamedImports": true,
"onlyDeclarations": true,
"properties": true
}
],
"canonical/no-restricted-strings": 0,
"canonical/no-use-extend-native": 2,
"canonical/prefer-inline-type-import": 2,
"canonical/sort-keys": [
2,
"asc",
{
"caseSensitive": false,
"natural": true
}
]
}
}
Shareable configurations
Recommended
This plugin exports a recommended configuration that enforces Canonical type good practices.
To enable this configuration use the extends property in your .eslintrc
config file:
{
"extends": [
"plugin:canonical/recommended"
],
"plugins": [
"canonical"
]
}
See ESLint documentation for more information about extending configuration files.
Rules
destructuring-property-newline
Like object-property-newline
, but for destructuring.
The following patterns are considered problems:
const {a,b} = obj;
const [a,b] = obj;
const {a,b,c} = obj;
const {
a,b} = obj;
({a,b}) => {};
The following patterns are not considered problems:
const {a,
b} = obj;
const {a} = obj;
const {
a
} = obj;
({a,
b}) => {};
const [a,,b] = obj;
export-specifier-newline
Forces every export specifier to be on a new line.
Tip: Combine this rule with object-curly-newline
to have every specifier on its own line.
"object-curly-newline": [
2,
{
"ExportDeclaration": "always"
}
],
Working together, both rules will produces exports such as:
export {
a,
b,
c
};
The following patterns are considered problems:
const a = 1; const b = 2; const c = 3; export { a, b, c };
const a = 1; const b = 2; const c = 3; export { a, b, c, };
const a = 1; const b = 2; export { a as default, b }
The following patterns are not considered problems:
export {
a,
b,
c
} from 'foo'
const a = 1; const b = 2; const c = 3; export {
a,
b,
c
};
export * from 'foo'
filename-match-exported
Match the file name against the default exported value in the module. Files that don't have a default export will be ignored. The exports of index.js
are matched against their parent directory.
export default function foo() {}
module.exports = class Foo() {}
module.exports = someVariable;
export default { foo: "bar" };
If your filename policy doesn't quite match with your variable naming policy, you can add one or multiple transforms:
"canonical/filename-match-exported": [ 2, "kebab" ]
Now, in your code:
export default function variableName;
Available transforms:
For multiple transforms simply specify an array like this (null in this case stands for no transform):
"canonical/filename-match-exported": [2, [ null, "kebab", "snake" ] ]
If you prefer to use suffixes for your files (e.g. Foo.react.js
for a React component file), you can use a second configuration parameter. It allows you to remove parts of a filename matching a regex pattern before transforming and matching against the export.
"canonical/filename-match-exported": [ 2, null, "\\.react$" ]
Now, in your code:
export default function variableName;
If you also want to match exported function calls you can use the third option (a boolean flag).
"canonical/filename-match-exported": [ 2, null, null, true ]
Now, in your code:
export default functionName();
The following patterns are considered problems:
module.exports = exported;
module.exports = class Foo {};
module.exports = class Foo { render() { return <span>Test Class</span>; } };
module.exports = function foo() {};
module.exports = function foo() { return <span>Test Fn</span> };
export default exported;
export default class Foo {};
export default class Foo { render() { return <span>Test Class</span>; } };
export default function foo() {};
export default function foo() { return <span>Test Fn</span> };
module.exports = exported;
module.exports = class Foo { render() { return <span>Test Class</span>; } };
module.exports = variableName;
export default variableName;
export default variableName;
export default variableName;
export default class Foo { render() { return <span>Test Class</span>; } };
export default class Foo { render() { return <span>Test Class</span>; } };
module.exports = foo();
The following patterns are not considered problems:
module.exports = function() {};
var foo = 'bar';
export default foo();
module.exports = exported;
module.exports = class Foo {};
module.exports = class Foo { render() { return <span>Test Class</span>; } };
module.exports = function foo() {};
module.exports = foo();
module.exports = function foo() { return <span>Test Fn</span> };
export default exported;
export default class Foo {};
export default class Foo { render() { return <span>Test Class</span>; } };
export default function foo() {};
export default function foo() { return <span>Test Fn</span> };
export default function foo() {};
export default function foo() { return <span>Test Fn</span> };
export default function index() {};
module.exports = variableName;
module.exports = variableName;
module.exports = variableName;
module.exports = variable_name;
export default variableName;
export default variableName;
export default variable_name;
export default variable_name;
export default variable_name;
export default variable_name;
module.exports = class Foo { render() { return <span>Test Class</span>; } };
export default class Foo { render() { return <span>Test Class</span>; } };
module.exports = foo();
filename-match-regex
A rule to enforce a certain file naming convention using a regular expression.
The convention can be configured using a regular expression (the default is camelCase.js
). Additionally
exporting files can be ignored with a second configuration parameter.
"canonical/filename-match-regex": [2, "^[a-z_]+$", true]
With these configuration options, camelCase.js
will be reported as an error while snake_case.js
will pass.
Additionally the files that have a named default export (according to the logic in the match-exported
rule) will be
ignored. They could be linted with the match-exported
rule. Please note that exported function calls are not
respected in this case.
The following patterns are considered problems:
var foo = 'bar';
var foo = 'bar';
var foo = 'bar';
var foo = 'bar';
The following patterns are not considered problems:
var foo = 'bar';
var foo = 'bar';
var foo = 'bar';
var foo = 'bar';
var foo = 'bar';
var foo = 'bar';
module.exports = foo
module.exports = foo
module.exports = foo()
filename-no-index
Having a bunch of index.js
files can have negative influence on developer experience, e.g. when
opening files by name. When enabling this rule. index.js
files will always be considered a problem.
The following patterns are considered problems:
var foo = 'bar';
var foo = 'bar';
The following patterns are not considered problems:
var foo = 'bar';
var foo = 'bar';
var foo = 'bar';
var foo = 'bar';
id-match
The --fix
option on the command line automatically fixes problems reported by this rule.
Note: This rule is equivalent to id-match
, except for addition of ignoreNamedImports
.
This rule requires identifiers in assignments and function
definitions to match a specified regular expression.
Options
"properties": false
(default) does not check object properties"properties": true
requires object literal properties and member expression assignment properties to match the specified regular expression"classFields": false
(default) does not class field names"classFields": true
requires class field names to match the specified regular expression"onlyDeclarations": false
(default) requires all variable names to match the specified regular expression"onlyDeclarations": true
requires only var
, function
, and class
declarations to match the specified regular expression"ignoreDestructuring": false
(default) enforces id-match
for destructured identifiers"ignoreDestructuring": true
does not check destructured identifiers"ignoreNamedImports": false
(default) enforces id-match
for named imports"ignoreNamedImports": true
does not check named imports
The following patterns are considered problems:
var __foo = "Matthieu"
first_name = "Matthieu"
first_name = "Matthieu"
Last_Name = "Larcher"
var obj = {key: no_under}
function no_under21(){}
obj.no_under22 = function(){};
no_under23.foo = function(){};
[no_under24.baz]
if (foo.bar_baz === boom.bam_pow) { [no_under25.baz] }
foo.no_under26 = boom.bam_pow
var foo = { no_under27: boom.bam_pow }
foo.qux.no_under28 = { bar: boom.bam_pow }
var o = {no_under29: 1}
obj.no_under30 = 2;
var { category_id: category_alias } = query;
var { category_id: category_alias } = query;
var { category_id: categoryId, ...other_props } = query;
var { category_id } = query;
var { category_id = 1 } = query;
import no_camelcased from "external-module";
import * as no_camelcased from "external-module";
export * as no_camelcased from "external-module";
import { no_camelcased as no_camel_cased } from "external module";
import { camelCased as no_camel_cased } from "external module";
import { camelCased, no_camelcased } from "external-module";
import { no_camelcased as camelCased, another_no_camelcased } from "external-module";
import camelCased, { no_camelcased } from "external-module";
import no_camelcased, { another_no_camelcased as camelCased } from "external-module";
function foo({ no_camelcased }) {};
function foo({ no_camelcased = 'default value' }) {};
const no_camelcased = 0; function foo({ camelcased_value = no_camelcased }) {}
const { bar: no_camelcased } = foo;
function foo({ value_1: my_default }) {}
function foo({ isCamelcased: no_camelcased }) {};
var { foo: bar_baz = 1 } = quz;
const { no_camelcased = false } = bar;
class x { _foo() {} }
class x { _foo = 1; }
import { no_camelcased } from "external-module";
The following patterns are not considered problems:
__foo = "Matthieu"
firstname = "Matthieu"
first_name = "Matthieu"
firstname = "Matthieu"
last_Name = "Larcher"
param = "none"
function noUnder(){}
no_under()
foo.no_under2()
var foo = bar.no_under3;
var foo = bar.no_under4.something;
foo.no_under5.qux = bar.no_under6.something;
if (bar.no_under7) {}
var obj = { key: foo.no_under8 };
var arr = [foo.no_under9];
[foo.no_under10]
var arr = [foo.no_under11.qux];
[foo.no_under12.nesting]
if (foo.no_under13 === boom.no_under14) { [foo.no_under15] }
var myArray = new Array(); var myDate = new Date();
var x = obj._foo;
var obj = {key: no_under}
var {key_no_under: key} = {}
var { category_id } = query;
var { category_id: category_id } = query;
var { category_id = 1 } = query;
var o = {key: 1}
var o = {no_under16: 1}
obj.no_under17 = 2;
var obj = {
no_under18: 1
};
obj.no_under19 = 2;
obj.no_under20 = function(){};
var x = obj._foo2;
class x { foo() {} }
class x { #foo() {} }
import { no_camelcased } from "external-module";
import-specifier-newline
Forces every import specifier to be on a new line.
Tip: Combine this rule with object-curly-newline
to have every specifier on its own line.
"object-curly-newline": [
2,
{
"ImportDeclaration": "always"
}
],
Working together, both rules will produces imports such as:
import {
a,
b,
c
} from 'foo';
The following patterns are considered problems:
import {a, b} from 'foo';
import a, {b, c} from 'foo';
The following patterns are not considered problems:
import {a,
b} from 'foo'
import a, {b,
c} from 'foo'
no-restricted-strings
Disallow specified strings.
The following patterns are considered problems:
var foo = "bar"
const foo = `bar ${baz}`;
The following patterns are not considered problems:
const foo = "bar";
Options
The 1st option is an array of strings that cannot be contained in the codebase.
no-use-extend-native
The following patterns are considered problems:
Array.prototype.custom
Array.to
Array.to()
[].length()
'unicorn'.green
[].custom()
({}).custom()
/match_this/.custom()
'string'.custom()
console.log('foo'.custom)
console.log('foo'.custom())
('str' + 'ing').custom()
('str' + 'i' + 'ng').custom()
(1 + 'ing').custom()
(/regex/ + 'ing').custom()
(1 + 1).toLowerCase()
(1 + 1 + 1).toLowerCase()
(function testFunction() {}).custom()
new Array().custom()
new ArrayBuffer().custom()
new Boolean().custom()
new DataView().custom()
new Date().custom()
new Error().custom()
new Float32Array().custom()
new Float64Array().custom()
new Function().custom()
new Int16Array().custom()
new Int32Array().custom()
new Int8Array().custom()
new Map().custom()
new Number().custom()
new Object().custom()
new Promise().custom()
new RegExp().custom()
new Set().custom()
new String().custom()
new Symbol().custom()
new Uint16Array().custom()
new Uint32Array().custom()
new Uint8Array().custom()
new Uint8ClampedArray().custom()
new WeakMap().custom()
new WeakSet().custom()
new Array()['custom']
new Array()['custom']()
The following patterns are not considered problems:
error.plugin
error.plugn()
array.custom
Object.assign()
Object.keys
Object.keys()
gulp.task()
Custom.prototype.custom
Array.prototype.map
Array.prototype.map.call([1,2,3], function (x) { console.log(x) })
Array.apply
Array.call(null, 1, 2, 3)
[].push(1)
[][0]
{}[i]
{}[3]
{}[j][k]
({foo: {bar: 1, baz: 2}}[i][j])
({}).toString()
/match_this/.test()
'foo'.length
'hi'.padEnd
'hi'.padEnd()
console.log('foo'.length)
console.log('foo'.toString)
console.log('foo'.toString())
console.log(gulp.task)
console.log(gulp.task())
'string'.toString()
(1).toFixed()
1..toFixed()
1.0.toFixed()
('str' + 'ing').toString()
('str' + 'i' + 'ng').toString()
(1 + 1).valueOf()
(1 + 1 + (1 + 1)).valueOf()
(1 + 1 + 1).valueOf()
(1 + 'string').toString()
(/regex/ + /regex/).toString()
(/regex/ + 1).toString()
([1] + [2]).toString()
(function testFunction() {}).toString()
Test.prototype
new Array().toString()
new ArrayBuffer().constructor()
new Boolean().toString()
new DataView().buffer()
new Date().getDate()
new Error().message()
new Error().stack
new Error().stack.slice(1)
new Float32Array().values()
new Float64Array().values()
new Function().toString()
new Int16Array().values()
new Int32Array().values()
new Int8Array().values()
new Map().clear()
new Number().toString()
new Object().toString()
new Object().toString
new Promise().then()
new RegExp().test()
new Set().values()
new String().toString()
new Symbol().toString()
new Uint16Array().values()
new Uint32Array().values()
new Uint8ClampedArray().values()
new WeakMap().get()
new WeakSet().has()
new Array()['length']
new Array()['toString']()
prefer-inline-type-import
The --fix
option on the command line automatically fixes problems reported by this rule.
TypeScript 4.5 introduced type modifiers that allow to inline type imports as opposed to having dedicated import type
. This allows to remove duplicate type imports. This rule enforces use of import type modifiers.
The following patterns are considered problems:
import type {foo} from 'bar'
import type {foo, baz} from 'bar'
The following patterns are not considered problems:
import {type foo} from 'bar'
import type Foo from 'bar'
import type * as Foo from 'bar'
prefer-use-mount
In React, it is common to use useEffect
without dependencies when the intent is to run the effect only once (on mount and unmount). However, just doing that may lead to undesired side-effects, such as the effect being called twice in Strict Mode. For this reason, it is better to use an abstraction such as useMount
that handles this use case.
The following patterns are considered problems:
useEffect(() => {}, [])
The following patterns are not considered problems:
useEffect(() => {}, [foo])
useMount(() => {}, [])
sort-keys
The --fix
option on the command line automatically fixes problems reported by this rule.
Note: This rule is equivalent to sort-keys
, except that it is fixable.
This rule requires identifiers in assignments and function
definitions to match a specified regular expression.
Options
The 1st option is "asc" or "desc".
- "asc" (default) - enforce properties to be in ascending order.
- "desc" - enforce properties to be in descending order.
The 2nd option is an object which has 3 properties.
caseSensitive
- if true, enforce properties to be in case-sensitive order. Default is true.minKeys
- Specifies the minimum number of keys that an object should have in order for the object's unsorted keys to produce an error. Default is 2, which means by default all objects with unsorted keys will result in lint errors.natural
- if true, enforce properties to be in natural order. Default is false. Natural Order compares strings containing combination of letters and numbers in the way a human being would sort. It basically sorts numerically, instead of sorting alphabetically. So the number 10 comes after the number 3 in Natural Sorting.
The following patterns are considered problems:
var obj = {
a:1,
_:2,
b:3
}
var obj = {
a:1,
_:2,
b:3
}
var obj = {a:1, _:2, b:3}
var obj = {a:1, c:2, b:3}
var obj = {b_:1, a:2, b:3}
var obj = {b_:1, c:2, C:3}
var obj = {$:1, _:2, A:3, a:4}
var obj = {1:1, 2:4, A:3, '11':2}
var obj = {'#':1, À:3, 'Z':2, è:4}
var obj = {...z, c:1, b:1}
var obj = {...z, ...c, d:4, b:1, ...y, ...f, e:2, a:1}
var obj = {c:1, b:1, ...a}
var obj = {...z, ...a, c:1, b:1}
var obj = {...z, b:1, a:1, ...d, ...c}
var obj = {...z, a:2, b:0, ...x, ...c}
var obj = {...z, a:2, b:0, ...x}
var obj = {...z, '':1, a:2}
var obj = {a:1, [b+c]:2, '':3}
var obj = {'':1, [b+c]:2, a:3}
var obj = {b:1, [f()]:2, '':3, a:4}
var obj = {a:1, b:3, [a]: -1, c:2}
var obj = {a:1, c:{y:1, x:1}, b:1}
var obj = {a:1, _:2, b:3}
var obj = {a:1, c:2, b:3}
var obj = {b_:1, a:2, b:3}
var obj = {b_:1, c:2, C:3}
var obj = {$:1, _:2, A:3, a:4}
var obj = {1:1, 2:4, A:3, '11':2}
var obj = {'#':1, À:3, 'Z':2, è:4}
var obj = {a:1, _:2, b:3}
var obj = {a:1, c:2, b:3}
var obj = {b_:1, a:2, b:3}
var obj = {$:1, A:3, _:2, a:4}
var obj = {1:1, 2:4, A:3, '11':2}
var obj = {'#':1, À:3, 'Z':2, è:4}
var obj = {a:1, _:2, b:3}
var obj = {a:1, c:2, b:3}
var obj = {b_:1, a:2, b:3}
var obj = {b_:1, c:2, C:3}
var obj = {$:1, A:3, _:2, a:4}
var obj = {1:1, 2:4, A:3, '11':2}
var obj = {'#':1, À:3, 'Z':2, è:4}
var obj = {a:1, _:2, b:3}
var obj = {a:1, c:2, b:3}
var obj = {b_:1, a:2, b:3}
var obj = {$:1, A:3, _:2, a:4}
var obj = {1:1, '11':2, 2:4, A:3}
var obj = {'#':1, À:3, 'Z':2, è:4}
var obj = {a:1, _:2, b:3}
var obj = {a:1, c:2, b:3}
var obj = {b_:1, a:2, b:3}
var obj = {b_:1, c:2, C:3}
var obj = {$:1, _:2, A:3, a:4}
var obj = {1:1, 2:4, A:3, '11':2}
var obj = {'#':1, À:3, 'Z':2, è:4}
var obj = {a:1, _:2, b:3}
var obj = {a:1, c:2, b:3}
var obj = {b_:1, a:2, b:3}
var obj = {b_:1, c:2, C:3}
var obj = {$:1, _:2, A:3, a:4}
var obj = {1:1, 2:4, A:3, '11':2}
var obj = {'#':1, À:3, 'Z':2, è:4}
var obj = {a:1, _:2, b:3}
var obj = {a:1, c:2, b:3}
var obj = {b_:1, a:2, b:3}
var obj = {b_:1, c:2, C:3}
var obj = {$:1, _:2, A:3, a:4}
var obj = {1:1, 2:4, A:3, '11':2}
var obj = {'#':1, À:3, 'Z':2, è:4}
var obj = {a:1, _:2, b:3}
var obj = {a:1, c:2, b:3}
var obj = {b_:1, a:2, b:3}
var obj = {b_:1, c:2, C:3}
var obj = {$:1, _:2, A:3, a:4}
var obj = {1:1, 2:4, '11':2, A:3}
var obj = {'#':1, À:3, 'Z':2, è:4}
The following patterns are not considered problems:
var obj = {_:2, a:1, b:3}
var obj = {a:1, b:3, c:2}
var obj = {a:2, b:3, b_:1}
var obj = {C:3, b_:1, c:2}
var obj = {$:1, A:3, _:2, a:4}
var obj = {1:1, '11':2, 2:4, A:3}
var obj = {'#':1, 'Z':2, À:3, è:4}
var obj = {a:1, b:3, [a + b]: -1, c:2}
var obj = {'':1, [f()]:2, a:3}
var obj = {a:1, [b++]:2, '':3}
var obj = {a:1, ...z, b:1}
var obj = {b:1, ...z, a:1}
var obj = {...a, b:1, ...c, d:1}
var obj = {...a, b:1, ...d, ...c, e:2, z:5}
var obj = {b:1, ...c, ...d, e:2}
var obj = {a:1, ...z, '':2}
var obj = {'':1, ...z, 'a':2}
var obj = {...z, a:1, b:1}
var obj = {...z, ...c, a:1, b:1}
var obj = {a:1, b:1, ...z}
var obj = {...z, ...x, a:1, ...c, ...d, f:5, e:4}
function fn(...args) { return [...args].length; }
function g() {}; function f(...args) { return g(...args); }
let {a, b} = {}
var obj = {a:1, b:{x:1, y:1}, c:1}
var obj = {_:2, a:1, b:3}
var obj = {a:1, b:3, c:2}
var obj = {a:2, b:3, b_:1}
var obj = {C:3, b_:1, c:2}
var obj = {$:1, A:3, _:2, a:4}
var obj = {1:1, '11':2, 2:4, A:3}
var obj = {'#':1, 'Z':2, À:3, è:4}
var obj = {_:2, a:1, b:3}
var obj = {a:1, b:3, c:2}
var obj = {a:2, b:3, b_:1}
var obj = {b_:1, C:3, c:2}
var obj = {b_:1, c:3, C:2}
var obj = {$:1, _:2, A:3, a:4}
var obj = {1:1, '11':2, 2:4, A:3}
var obj = {'#':1, 'Z':2, À:3, è:4}
var obj = {_:2, a:1, b:3}
var obj = {a:1, b:3, c:2}
var obj = {a:2, b:3, b_:1}
var obj = {C:3, b_:1, c:2}
var obj = {$:1, _:2, A:3, a:4}
var obj = {1:1, 2:4, '11':2, A:3}
var obj = {'#':1, 'Z':2, À:3, è:4}
var obj = {_:2, a:1, b:3}
var obj = {a:1, b:3, c:2}
var obj = {a:2, b:3, b_:1}
var obj = {b_:1, C:3, c:2}
var obj = {b_:1, c:3, C:2}
var obj = {$:1, _:2, A:3, a:4}
var obj = {1:1, 2:4, '11':2, A:3}
var obj = {'#':1, 'Z':2, À:3, è:4}
var obj = {b:3, a:1, _:2}
var obj = {c:2, b:3, a:1}
var obj = {b_:1, b:3, a:2}
var obj = {c:2, b_:1, C:3}
var obj = {a:4, _:2, A:3, $:1}
var obj = {A:3, 2:4, '11':2, 1:1}
var obj = {è:4, À:3, 'Z':2, '#':1}
var obj = {b:3, a:1, _:2}
var obj = {c:2, b:3, a:1}
var obj = {b_:1, b:3, a:2}
var obj = {c:2, C:3, b_:1}
var obj = {C:2, c:3, b_:1}
var obj = {a:4, A:3, _:2, $:1}
var obj = {A:3, 2:4, '11':2, 1:1}
var obj = {è:4, À:3, 'Z':2, '#':1}
var obj = {b:3, a:1, _:2}
var obj = {c:2, b:3, a:1}
var obj = {b_:1, b:3, a:2}
var obj = {c:2, b_:1, C:3}
var obj = {a:4, A:3, _:2, $:1}
var obj = {A:3, '11':2, 2:4, 1:1}
var obj = {è:4, À:3, 'Z':2, '#':1}
var obj = {b:3, a:1, _:2}
var obj = {c:2, b:3, a:1}
var obj = {b_:1, b:3, a:2}
var obj = {c:2, C:3, b_:1}
var obj = {C:2, c:3, b_:1}
var obj = {a:4, A:3, _:2, $:1}
var obj = {A:3, '11':2, 2:4, 1:1}
var obj = {è:4, À:3, 'Z':2, '#':1}