Skip to content

Instantly share code, notes, and snippets.

@louis-young
Last active December 1, 2025 11:55
Show Gist options
  • Select an option

  • Save louis-young/e03363821e2142eb86c5c1663831cc7f to your computer and use it in GitHub Desktop.

Select an option

Save louis-young/e03363821e2142eb86c5c1663831cc7f to your computer and use it in GitHub Desktop.
import javascript from "@eslint/js";
import { defineConfig } from "eslint/config";
import { FILE_EXTENSION_GLOB_PATTERNS } from "../constants/glob-patterns";
import { flattenFilesArrays } from "../utilities/flatten-files-arrays";
export const javascriptConfig = defineConfig({
extends: [javascript.configs.recommended],
files: flattenFilesArrays(
FILE_EXTENSION_GLOB_PATTERNS.javascript,
FILE_EXTENSION_GLOB_PATTERNS.javascriptJsx,
FILE_EXTENSION_GLOB_PATTERNS.typescript,
FILE_EXTENSION_GLOB_PATTERNS.typescriptJsx,
),
rules: {
/**
* What: Disallow the omission of curly braces.
* Why: Reduce the possibility of bugs and increase code clarity.
*/
curly: ["error", "all"],
/**
* What: Enforce the use of strict equality checks (`===` and `!==`).
* Why: Avoid unexpected type coercion behavior from loose equality checks (`==` and `!=`),
* ensuring comparisons are safer, more predictable, and more explicit.
*/
eqeqeq: ["error", "always"],
/**
* What: Prefer the use of function declarations where reasonable.
* Why: Ensure consistency and make functions easier to differentiate from other variables.
*/
"func-style": ["error", "declaration"],
/**
* What: Disallow the use of bitwise operators.
* Why: The use of bitwise operators is uncommon and often a mistake.
*/
"no-bitwise": "error",
},
});
import { defineConfig } from "eslint/config";
import globals from "globals";
// TODO: Reevaluate how globals are made available. Applications contain modules that run exclusively in the browser, others that run exclusively in Node.js, and some that are isomorphic, executing in both environments.
export const baseConfig = defineConfig({
languageOptions: {
ecmaVersion: "latest",
globals: {
...globals.browser,
...globals.node,
},
sourceType: "module",
},
linterOptions: {
reportUnusedDisableDirectives: "error",
reportUnusedInlineConfigs: "error",
},
});
import type { Linter } from "eslint";
import { defineConfig } from "eslint/config";
import typescript from "typescript-eslint";
import { FILE_EXTENSION_GLOB_PATTERNS } from "../constants/glob-patterns";
import { flattenFilesArrays } from "../utilities/flatten-files-arrays";
export const typescriptConfig = defineConfig(
{
/**
* `typescript-eslint` intentionally - and, frankly, questionably and slightly annoyingly - diverges from ESLint’s type definitions, expecting consumers to use their own `FlatConfig.Config` types and `config` function, which is not always practical - especially when authoring a distributed ESLint config.
*
* The crux of the issue is that `typescript-eslint`'s `FlatConfig.Config` defines `languageOptions.parser` using a `LooseParserModule`, which isn’t assignable to ESLint's `Linter.Config` due to stricter type constraints, despite being compatible at runtime.
*
* We use a type assertion here to work around the mismatch, knowing the config is valid at runtime despite the type-system-level incompatibility.
*
* See: https://github.com/typescript-eslint/typescript-eslint/issues/8613 and https://github.com/typescript-eslint/typescript-eslint/issues/9724.
*/
extends: [
typescript.configs.strict as Linter.Config[],
typescript.configs.stylistic as Linter.Config[],
],
files: flattenFilesArrays(
FILE_EXTENSION_GLOB_PATTERNS.typescript,
FILE_EXTENSION_GLOB_PATTERNS.typescriptJsx,
),
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: process.cwd(),
},
},
rules: {
/**
* What: Enforce the use of `type` over `interface`.
* Why: See: https://www.totaltypescript.com/type-vs-interface-which-should-you-use#quick-explanation.
*/
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
},
},
{
files: flattenFilesArrays(
FILE_EXTENSION_GLOB_PATTERNS.typescriptDeclaration,
),
rules: {
/**
* What: Allow the use of `interface` in declaration files.
* Why: Interface declaration merging is often required in declaration files.
*/
"@typescript-eslint/consistent-type-definitions": "off",
},
},
);
import type { Linter } from "eslint";
import { defineConfig } from "eslint/config";
import { baseConfig } from "../../configs/base";
import { cssConfig } from "../../configs/css";
import { eslintCommentsConfig } from "../../configs/eslint-comments";
import { ignoresConfig } from "../../configs/ignores";
import { importConfig } from "../../configs/import";
import { javascriptConfig } from "../../configs/javascript";
import { jestConfig } from "../../configs/jest";
import { jsdocConfig } from "../../configs/jsdoc";
import { jsonConfig } from "../../configs/json";
import { jsxAccessibilityConfig } from "../../configs/jsx-accessibility";
import { markdownConfig } from "../../configs/markdown";
import { nextJsConfig } from "../../configs/next-js";
import { perfectionistConfig } from "../../configs/perfectionist";
import { playwrightConfig } from "../../configs/playwright";
import { prettierConfig } from "../../configs/prettier";
import { promiseConfig } from "../../configs/promise";
import { reactConfig } from "../../configs/react";
import { reactHooksConfig } from "../../configs/react-hooks";
import { reactTestingLibraryConfig } from "../../configs/react-testing-library";
import { regularExpressionConfig } from "../../configs/regular-expression";
import { storybookConfig } from "../../configs/storybook";
import { tailwindCssConfig } from "../../configs/tailwind-css";
import { tanstackQueryConfig } from "../../configs/tanstack-query";
import { typescriptConfig } from "../../configs/typescript";
import { vitestConfig } from "../../configs/vitest";
import { yamlConfig } from "../../configs/yaml";
const additionalTechnologyToConfigsMap = {
css: [cssConfig],
jest: [jestConfig],
"next-js": [nextJsConfig],
playwright: [playwrightConfig],
react: [reactConfig, reactHooksConfig, jsxAccessibilityConfig],
"react-testing-library": [reactTestingLibraryConfig],
storybook: [storybookConfig],
"tailwind-css": [tailwindCssConfig],
"tanstack-query": [tanstackQueryConfig],
vitest: [vitestConfig],
} satisfies Record<string, Linter.Config[][]>;
type AdditionalTechnology = keyof typeof additionalTechnologyToConfigsMap;
type CreateConfigParameters = {
additionalTechnologies?: AdditionalTechnology[];
customConfig?: Parameters<typeof defineConfig>;
};
export function createConfig({
additionalTechnologies,
customConfig,
}: CreateConfigParameters = {}): ReturnType<typeof defineConfig> {
const config: Linter.Config[][] = [
eslintCommentsConfig,
ignoresConfig,
importConfig,
javascriptConfig,
jsdocConfig,
jsonConfig,
markdownConfig,
perfectionistConfig,
prettierConfig,
promiseConfig,
regularExpressionConfig,
typescriptConfig,
yamlConfig,
baseConfig, // The base config must remain last so that its settings (specifically, its language options) reliably merge with those in the preceding configs.
];
if (additionalTechnologies) {
const uniqueAdditionalTechnologies = new Set(additionalTechnologies);
for (const additionalTechnology of uniqueAdditionalTechnologies) {
const additionalTechnologyConfigs =
additionalTechnologyToConfigsMap[additionalTechnology];
for (const additionalTechnologyConfig of additionalTechnologyConfigs) {
config.push(additionalTechnologyConfig);
}
}
}
if (customConfig) {
config.push(defineConfig(customConfig)); // Run the custom config through `defineConfig` internally as an abstraction to enable support for `extends`, without requiring consumers to import and call the function themselves.
}
return defineConfig(config);
}
<!-- Not everything in this file applies to the repo yet, as some settings are still pending addition. -->
# Contributing
We welcome contributions to our project! If you have an idea for a new feature
or a bug fix, please open a pull request or get in touch at #phone-product.
## Shortcut
Before you create a pull request or even start writing code, make sure there is
a ticket outlining your work in Shortcut. We generally do not accept pull
requests without an associated ticket.
When creating a Shortcut ticket, view our [project
board](https://app.shortcut.com/slicelife/stories/space/541942?team_scope_id=v2%3At%3A5911da63-8cda-4bf9-84b6-35b74f475dfa%3A66740e93-19b9-4552-82f9-fb41b3173c2e)
and verify that there is no existing ticket for the work you plan to do.
## Guidelines
### TypeScript
Please write any new code in TypeScript, using the narrowest applicable types.
Avoid type assertions, non-null assertions, and the `any` type when possible.
Add appropriate comments when you need to use those features and the need is not
obvious.
You can run `pnpm types:check` in your terminal to check the entire project.
### Linting and Formatting
This project uses ESLint and Prettier to enforce consistent formatting and code
style. We use [Husky](https://github.com/typicode/husky) to automatically
install a local git pre-commit hook to run `eslint`, and `prettier` on all
staged files. If there are any linting or formatting issues, git will abort the commit.
Please try to resolve all code issues are before committing, but
if you really need to commit in-progress work, you may run `git commit
--no-verify` to skip the pre-commit hook. Keep in mind that we also have code
checks in CI that need to pass before we can accept a pull request.
You will encounter fewer surprises if you configure your editor to run Prettier
on save and show ESLint diagnostics in real time. If you are using VSCode,
you'll want the following extensions:
- [vscode-eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- [prettier-vscode](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
You can run `pnpm lint:fix` in your terminal to lint the entire project and `pnpm
format:fix` to fix formatting issues.
### Tests
This project uses [Vitest](https://vitejs.dev/guide/) and [Testing
Library](https://testing-library.com/docs/) for unit tests. While there are no
strict test coverage requirements, please try to make sure that all components
and functions have appropriate tests. If you fix a bug, please add regression
tests. If you come across code that does not have tests, please create a ticket
in Shortcut to add those tests or consider adding them to an existing ticket.
All tests must pass before deploying new code. You can run `pnpm test:run` locally
to ensure passing tests before opening a pull request.
### Conventional Commits
This project uses [commitlint](https://commitlint.js.org/) locally and in CI to
enforce the [Conventional
Commits](https://www.conventionalcommits.org/en/v1.0.0/) format. Please follow
this format for all commits in your pull request and the title of the pull
request.
## Pull Request Process
1. Clone the repository and create a new branch for your feature or bug fix.
Please use the Git Helpers tool in the Shortcut ticket to create an
appropriate branch name.
1. Set the state of your Shortcut ticket to "In Development".
1. Make your changes and ensure that all code checks pass. You can run `pnpm
verify:check` locally to run the same code checks as those in CI. `pnpm
verify:fix` will automatically fix many issues in addition to performing the
code checks.
1. Open a pull request and describe the changes you've made following the
template.
1. Set the state of your Shortcut ticket to "Ready for Review".
1. The phone engineering team will review your pull request and provide feedback.
1. After 2 team members approve your pull request, you can hand it off to
the QA team by adjusting the status of the Shortcut ticket to "Ready for QA".
1. After QA approves the changes using the acceptance criteria in the pull
request, you can then merge the pull request into the main branch and deploy
the change.
1. When deploying a change, if you need to ask for assistance or block the
pipeline, please post in the #phone-goods-eng or #phone-eng-endor channel.
## Git Etiquette
- Our primary branch is called `main`.
- We only accept new code through pull requests.
- We require all automated checks to pass before you can merge a pull request.
- We require approval from at least 2 team members before you can merge a pull
request.
- We require all pull requests to contain a link to the associated Shortcut
ticket in the description.
- Shortcut can automatically link a pull request to a ticket using the git
branch name, so we use the branch naming convention of
`{username}/{shortcut_ticket_id}/ticket title`.
- The pull request description is the primary source of technical information
about your changes. Please include sufficient details about any trade-offs or
technical decisions you made.
- We want to keep the history of our `main` branch useful and readable. We
only allow squash merging to the `main` branch. GitHub will perform a squash
merge for you by default.
- Please review and edit the commit message that GitHub automatically creates
for you before finalizing your merge. For a pull request with many commits,
the default message and description are usually not helpful.
- Keep the pull request number prefix that Github generates for you - `[#1234]`.
- The commit message should be a short description of the bug fix or feature
the pull request is implementing.
- You may provide an extended description of the change, but the pull
request link GitHub adds will be best way to get the full details of
change.
- If your pull request contains multiple commits, GitHub will automatically
create a description with the commit message of each commit preceded by
and asterisk. Please delete this description and optionally write a better
one.
### About Approvals and Commits
GitHub will automatically dismiss pull request approvals if new code is
committed. We do not recommend rebasing once your pull request has received
reviews unless you intend to significantly refactor your pull request. Rebasing
doesn't allow reviewers to look at just the new commits since their last review.
Merging the latest changes from the `main` branch using `git merge` will not
cause GitHub to dismiss approvals as long as there are no conflicts. In general,
don't worry about how "messy" the git history of your pull request branch is. It
can contain lots of merge commits and checkpoint commits. All of your commits
will be squashed into one when merging to `main`, so the git history of the pull
request branch is mostly irrelevant.
## Pull Request Target Resolution Times
We strive to review and merge all open pull requests (that are not marked as a
draft) within the following time window:
- For **small changes**: 1 business day (~1-99 lines of code)
- For **medium-sized** changes: 3 business days (~100-499 lines of code)
- For **larger or complex** code changes: 5 business days (>500 lines of code)
> It is not always wise to equate complexity to size; as such, it is at the
> developer's discretion what "size" a PR should be considered and if it
> requires reviews from specific authors or additional authors before merging.
Please label pull requests appropriately to aid in generating metrics for pull
request resolution times. For example a pull request may be `small`, `complex`
etc. so all appropriate labels can be applied:
- `pr-small`
- `pr-medium`
- `pr-large`
- `pr-complex`
Please keep in mind that the time windows above are estimates and that the
actual resolution times will vary depending on the complexity of the changes and
the availability of the maintainers. We only review code during business hours
(except during emergencies), so holidays and weekends will extend resolution
times.
# eslint-config-slice
A distributed ESLint configuration for use at Slice.
## Prerequisites
### Consuming Private Packages
Private Slice packages are hosted on Gemfury. To consume a private package, you need to acquire a Gemfury "Deploy Token" and configure your local environment, package manager, GitHub Actions workflows, and Dependabot. For detailed instructions, please refer to [Confluence: Consuming Private Packages](https://mypizza.atlassian.net/wiki/spaces/Web/pages/5811208206/Consuming+Private+Packages).
### ESLint
`@slicelife/eslint-config-slice` requires `eslint` version 9.6.0 or greater to be installed in the host project. You can install or upgrade ESLint by running the appropriate command for your package manager:
| Package Manager | Command |
| --------------- | -------------------------------------- |
| pnpm | `pnpm add --save-dev eslint@^9.6.0` |
| npm | `npm install --save-dev eslint@^9.6.0` |
| Yarn | `yarn add --dev eslint@^9.6.0` |
## Installation
Install `@slicelife/eslint-config-slice` by running the appropriate command for your package manager:
| Package Manager | Command |
| --------------- | ------------------------------------------------------- |
| pnpm | `pnpm add --save-dev @slicelife/eslint-config-slice` |
| npm | `npm install --save-dev @slicelife/eslint-config-slice` |
| Yarn | `yarn add --dev @slicelife/eslint-config-slice` |
## Usage
Create an `eslint.config.ts` file in the root of your project:
```ts
import { createConfig } from "@slicelife/eslint-config-slice";
export default createConfig();
```
This will assume the base configuration with sensible defaults.
## Configuration
### Default Configuration
The default configuration includes parsers and rules for a wide array of languages and technologies.
#### Language Support and Rules
- JavaScript.
- TypeScript.
- JSON.
- YAML.
- Markdown.
#### Stylistic Rules
- Sorting objects, imports, types, enums, and JSX props.
#### Miscellaneous Rules
- ESLint directive comments.
### Additional Technology-Specific Configurations
Support for additional technologies is available through the `additionalTechnologies` option when creating the ESLint configuration.
| Technology | Included Components |
| ----------------------- | ------------------------------------------------ |
| `css` | CSS language parsing and rules. |
| `jest` | Jest testing rules. |
| `next-js` | Next.js‑specific rules. |
| `playwright` | Playwright testing rules. |
| `react` | React, React hooks, and JSX accessibility rules. |
| `react-testing-library` | React Testing Library-specific rules. |
| `storybook` | Storybook-specific rules. |
| `tailwind-css` | Tailwind CSS-specific rules. |
| `tanstack-query` | TanStack Query-specific rules. |
| `typescript` | TypeScript‑specific rules. |
| `vitest` | Vitest testing rules. |
If you're building a Next.js (and therefore React) application that uses Tailwind CSS (and therefore CSS), Vitest, and Playwright, your ESLint configuration might look as follows:
```ts
export default createConfig({
additionalTechnologies: [
"css",
"next-js",
"playwright",
"react",
"tailwind-css",
"vitest",
],
});
```
This will automatically extend your project with the appropriate technology-specific configuration(s).
### Customising the Configuration
You can customise the configuration returned by `createConfig` using the `customConfig` option. This lets you extend or override any ESLint rules or settings by providing an array of standard ESLint configuration objects. Internally, the `customConfig` array is processed with `defineConfig` from `eslint/config`, so each entry supports the full ESLint configuration format - including `extends`. You can think of `customConfig` as a list of arguments you'd pass to `defineConfig`.
```ts
import { createConfig } from "@slicelife/eslint-config-slice";
export default createConfig({
customConfig: [
{
rules: {
"func-style": "off",
},
},
],
});
```
### Lintable File Extensions
The package exports a `LINTABLE_FILE_EXTENSIONS` array containing all file extensions supported by the ESLint configuration. This is useful for programmatically constructing `lint-staged` configurations or other tooling that targets lintable files.
```js
// @ts-check
import { LINTABLE_FILE_EXTENSIONS } from "@slicelife/eslint-config-slice";
/**
* @type {import("lint-staged").Configuration}
*/
const lintStagedConfig = {
[`*.{${LINTABLE_FILE_EXTENSIONS.join(",")}}`]:
"eslint --no-warn-ignored --max-warnings 0",
};
export default lintStagedConfig;
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment