Last active
December 1, 2025 11:55
-
-
Save louis-young/e03363821e2142eb86c5c1663831cc7f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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", | |
| }, | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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", | |
| }, | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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", | |
| }, | |
| }, | |
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!-- 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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