Tachyon Lint Rules
Lint rules developed for both the Tachyon repo and Twitch in general.
Exports Configs / Presets
While this plugin contains some rules that are mostly specific to the Tachyon
monorepo, some of the rules are generally applicable to any modern Twitch
project. These general rules are exported under the tachyon:recommended
config
(aka preset). It includes the following rules:
- no-bare-react-relay-import
- no-mock-clear
- no-react-import
- no-use-immutable-callback
- styled-components-prefix
To use this config, add it the extends list of an ESLint config:
extends: [
'plugin:tachyon/recommended',
],
Rules
bowser-parser-fallback
This rule forces all usages of Bowser.getParser
to include a fallback to the
value BOWSER_USER_AGENT_FALLBACK
. Bowser will throw an error when getParser
receives a falsy value (including empty strings), which can happen on the
server, in test environments, or on weird devices. This makes usage safer and
eliminates the need for any other special casing or error handling.
Bowser.getParser(window.navigator.userAgent); // error
Bowser.getParser(window.navigator.userAgent || BOWSER_USER_AGENT_FALLBACK); // allowed
no-aliased-absolute-imports
This rule is meant to help ensure valid imports in Tachyon apps and packages, which prefer relative imports and barrels instead of aliased absolute paths. It does still allow relative imports from outside of the main code directory, which is useful for things like integration tests outside of that directory.
It takes a configuration of an array of the names of any root directories you want to target:
'tachyon/no-aliased-absolute-imports': ['error', ['src']]
which would result in:
import { foo } from 'src/foo'; // error
import { foo } from '../foo'; // allowed
import { foo } from '../src/foo'; // allowed (for things like tests outside of src)
no-bare-react-relay-import (recommended)
This rule is meant to help with the transition from the legacy React-Relay
container HOCs to the new hooks APIs. It forces imports to either come from
react-relay/hooks
or react-relay/legacy
so you can more easily track
conversion progress.
import { useFragment, createFragmentContainer } from 'react-relay'; // error
import { useFragment } from 'react-relay/hooks'; // allowed
import { createFragmentContainer } from 'react-relay/legacy'; // allowed
no-disable-rules
This rule disables the ability to disable a target set of other ESLint rules. It is meant to either ban disabling certain rules entirely, or to force such overrides to happen via allowlisting in the central ESLint config (which can then more easily interact with things like codeowners to escalate reviews). It works for all of the various ESLint magic comments.
It takes a configuration of an object, with the rules
key specifying the names
of any lint rules you want to target:
'tachyon/no-disable-rules': ['error', {
rules: ['react/no-danger'],
}]
which would result in:
// eslint-disable-next-line react/no-danger // error
It has an additional configuration option to prevent comments that disable all rules:
'tachyon/no-disable-rules': ['error', {
noEmptyDisables: true,
rules: ...,
}]
which would result in:
/* eslint-disable */ // error
// eslint-disable-next-line // error
// eslint-disable-line // error
no-mock-clear (recommended)
This rule prevents the unnecessary usage of mockFoo.mockClear()
or
jest.clearAllMocks()
when you have clearMocks: true
in your
Jest config, as they
are unnecessary noise in your tests at that point and also add to the confusion
between clearing and resetting mocks. The lint error messages include tips
explaining why you might want to reset mocks and how to do that if that was the
intention.
const mockFoo = jest.fn();
mockFoo.mockClear(); // error
jest.clearAllMocks(); // error
no-react-import (recommended)
This rule is meant to help the transition to the new React JSX transform.
This rules prevents importing the "default" React import as it is no longer necessary:
import React from 'react'; // error
Use import destructuring instead:
import { Component, ReactNode, ... } from 'react'; // allowed
This also prevents using the React qualifier (since some type/lint setups
consider React
to be ambiently available due to how the old transform works):
const Foo: React.FC = () => {...}; // error
Use the destructured import directly:
const Foo: FC = () => {...}; // allowed
no-rollup-package-import
This rule prevents importing rollup convenience packages. This is useful when
you have a suite of sub-packages (like in a monorepo) that you also re-expose
via a single rollup package for apps that use all of them together. An example
of this is the tachyon-utils
suite that contains specific sub-packages, and
other packages shouldn't consume the rollup to avoid bringing in unnecessary
transitive dependencies. This is the opposite of
tachyon/no-sub-package-import
.
It takes a configuration of an array of the names of any rollup packages you want to target:
'tachyon/no-rollup-package-import': ['error', ['tachyon-utils']],
which would result in:
import { once } from 'tachyon-utils'; // error
import { once } from 'tachyon-utils-stdlib'; // allowed
no-single-child-directional-navs (auto-fix)
This rule prevents the usage of directional nav areas (Horizontal, Vertical,
Grid, etc) with an elementCount
of 1, preventing arbitrary choice and making
code structure more semantic. This rule comes with an auto-fix that replaces any
such directional nav areas with NodeNav
and cleans up the imports as well.
<HorizontalNav focusIndex={2} elementCount={1} /> // error
<NodeNav focusIndex={2} /> // allowed
Note: The autofixer relies on prettier to clean up whitespace after it applies its fixes, so it is intended to be used alongside that plugin.
no-sub-package-import (auto-fix)
This rule prevents importing rollup convenience packages. This is useful when
you have a suite of sub-packages (like in a monorepo) that you also re-expose
via a single rollup package for apps that use all of them together. An example
of this is the tachyon-utils
suite that contains specific sub-packages, which
is used throughout the Tachyon apps since they use all of the underlying
packages (and their dependencies). This rule comes with an auto-fix that will
truncate the sub-package extension. This is the opposite of
tachyon/no-rollup-package-import
.
It takes a configuration of an array of the names of any rollup packages you want to target:
'tachyon/no-sub-package-import': ['error', ['tachyon-utils']],
import { once } from 'tachyon-utils-stdlib'; // error
import { once } from 'tachyon-utils'; // allowed
prefer-use-const (recommended)
This rule encourages the use of the useConst
hook over useState
for saved
data that will not change, as useConst
is functionally equivalent but more
self-documenting. This does not affect useState
usage that utilizes the
updater function.
const [foo] = useState(() => getSomeState()); // error
const foo = useConst(() => getSomeState()); // allowed
const [bar, setBar] = useState(() => ...); // still allowed
styled-components-prefix (recommended, auto-fix)
This rule enforces that the names given to styled components adopt a Sc
prefix
to help ease the quick understanding of component functionality/source while
looking through JSX. This rule comes with an auto-fix to apply the prefix
appropriately:
const Foo = styled.div`...`;
...
<Foo>
<Foo />
</Foo>
will be turned into
const ScFoo = styled.div`...`;
...
<ScFoo>
<ScFoo />
</ScFoo>
Creating New Rules
To create a new rule, make a new JS file in the rules/
directory and a
matching entry in index.js
. Then either add the rule to the recommended
config (if it is general enough) or enable it in the // tachyon
section of
.eslintrc.js
(or an appropriate app's override section if it is highly
targeted).
For help creating rules, use the AST Explorer with
the parser set to @typescript-eslint/parser
and transform set to ESLint v4
.
Make sure that these settings are reflected in the upper right of the window
before proceeding because the site has some unexpected order-based side effects.
You can then add sample code in the top left pane, view the AST in the top right
pane, write your rule in the bottom left, and view the output in the bottom
right. Note that ESLint is now on v7, so there are minor API differences but for
the most part you can get a long way using this tool. The main difference that
comes up is the range API, which was an object in v4 and is a tuple in v7.
The ESLint docs on rule development are pretty useful for revealing the APIs, especially when working on autofix logic, but they don't offer much guidance so it can useful to combine them with AST Explorer and looking at other rules' source to get started. There's also a recorded demo from Tachyon office hours showing getting started with lint rule development.