@activatus/code-standards 0.0.0-stage → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -2
- package/docs/Guides/_category_.json +4 -0
- package/docs/Guides/component-library-workflow.md +182 -0
- package/docs/Guides/migrating.md +161 -0
- package/docs/Guides/setup.md +158 -0
- package/docs/Standards/_category_.json +4 -0
- package/docs/Standards/agent-skills.md +125 -0
- package/docs/Standards/agent-specs.md +74 -0
- package/docs/Standards/direnv.mdx +249 -0
- package/docs/Standards/documentation.md +314 -0
- package/docs/Standards/editorconfig.md +74 -0
- package/docs/Standards/eslint.md +900 -0
- package/docs/Standards/human-readable-names.md +274 -0
- package/docs/Standards/lint-errors.md +171 -0
- package/docs/Standards/prettier.md +106 -0
- package/docs/Standards/readable-code.md +570 -0
- package/docs/Standards/stylelint.md +193 -0
- package/docs/Standards/styling.md +290 -0
- package/docs/Standards/tech-stack.md +158 -0
- package/docs/Structure/_category_.json +4 -0
- package/docs/Structure/api-routes.md +118 -0
- package/docs/Structure/component-organization.md +526 -0
- package/docs/Structure/folder-structure.md +366 -0
- package/docs/Structure/import-organization.md +158 -0
- package/docs/Structure/naming-conventions.md +334 -0
- package/docs/design-system-components.md +117 -0
- package/docs/intro.md +44 -0
- package/docs/ontology.md +127 -0
- package/docs/templates.mdx +396 -0
- package/docs/version-history.md +338 -0
- package/package.json +60 -3
- package/scripts/renameSymbol.mjs +188 -0
- package/static/templates/.editorconfig +25 -0
- package/static/templates/.envrc +25 -0
- package/static/templates/.nvmrc +1 -0
- package/static/templates/AGENTS.md +163 -0
- package/static/templates/decision-doc.md +19 -0
- package/static/templates/docs/ai/agent-harness.md +17 -0
- package/static/templates/docs/ai/agents.md +17 -0
- package/static/templates/docs/ai/architecture.md +17 -0
- package/static/templates/docs/ai/context.md +17 -0
- package/static/templates/docs/ai/model-routing.md +17 -0
- package/static/templates/docs/ai/models.md +17 -0
- package/static/templates/docs/ai/retrieval.md +17 -0
- package/static/templates/docs/ai/tools.md +17 -0
- package/static/templates/docs/architecture/authentication.md +17 -0
- package/static/templates/docs/architecture/backend.md +17 -0
- package/static/templates/docs/architecture/background-jobs.md +17 -0
- package/static/templates/docs/architecture/backups.md +17 -0
- package/static/templates/docs/architecture/caching.md +17 -0
- package/static/templates/docs/architecture/ci-cd.md +17 -0
- package/static/templates/docs/architecture/disaster-recovery.md +17 -0
- package/static/templates/docs/architecture/environments.md +17 -0
- package/static/templates/docs/architecture/frontend.md +17 -0
- package/static/templates/docs/architecture/hosting.md +17 -0
- package/static/templates/docs/architecture/observability.md +17 -0
- package/static/templates/docs/architecture/realtime.md +17 -0
- package/static/templates/docs/architecture/search.md +17 -0
- package/static/templates/docs/architecture/services.md +17 -0
- package/static/templates/docs/architecture/state-management.md +17 -0
- package/static/templates/docs/architecture/storage.md +17 -0
- package/static/templates/docs/architecture/system.md +17 -0
- package/static/templates/docs/business/buying-trigger.md +17 -0
- package/static/templates/docs/business/customer-value.md +17 -0
- package/static/templates/docs/business/customer.md +17 -0
- package/static/templates/docs/business/defensibility.md +17 -0
- package/static/templates/docs/business/growth.md +17 -0
- package/static/templates/docs/business/pricing.md +17 -0
- package/static/templates/docs/business/revenue-model.md +17 -0
- package/static/templates/docs/business/unit-economics.md +17 -0
- package/static/templates/docs/data/analytics.md +17 -0
- package/static/templates/docs/data/audit.md +17 -0
- package/static/templates/docs/data/database.md +17 -0
- package/static/templates/docs/data/indexing.md +17 -0
- package/static/templates/docs/data/migrations.md +17 -0
- package/static/templates/docs/data/model.md +17 -0
- package/static/templates/docs/data/retention.md +17 -0
- package/static/templates/docs/data/schema.md +17 -0
- package/static/templates/docs/deployment/approval.md +17 -0
- package/static/templates/docs/deployment/live-verification.md +17 -0
- package/static/templates/docs/deployment/process.md +17 -0
- package/static/templates/docs/deployment/rollback.md +17 -0
- package/static/templates/docs/design/design-system.md +17 -0
- package/static/templates/docs/design/errors.md +17 -0
- package/static/templates/docs/design/feedback.md +17 -0
- package/static/templates/docs/design/interactions.md +17 -0
- package/static/templates/docs/design/layout.md +17 -0
- package/static/templates/docs/design/navigation.md +17 -0
- package/static/templates/docs/design/principles.md +17 -0
- package/static/templates/docs/design/responsive.md +17 -0
- package/static/templates/docs/development/branching.md +17 -0
- package/static/templates/docs/development/code-quality.md +17 -0
- package/static/templates/docs/development/coding-standards.md +21 -0
- package/static/templates/docs/development/commits.md +17 -0
- package/static/templates/docs/development/merge-strategy.md +17 -0
- package/static/templates/docs/development/process.md +17 -0
- package/static/templates/docs/development/pull-requests.md +17 -0
- package/static/templates/docs/experience/accessibility.md +17 -0
- package/static/templates/docs/experience/aha-moment.md +17 -0
- package/static/templates/docs/experience/anti-experience.md +17 -0
- package/static/templates/docs/experience/friction.md +17 -0
- package/static/templates/docs/experience/outcomes.md +17 -0
- package/static/templates/docs/experience/principles.md +17 -0
- package/static/templates/docs/experience/retention.md +17 -0
- package/static/templates/docs/experience/sharing.md +17 -0
- package/static/templates/docs/overview/how-it-works.md +17 -0
- package/static/templates/docs/overview/what-is-it.md +17 -0
- package/static/templates/docs/overview/writing-docs.md +16 -0
- package/static/templates/docs/pitch/alternatives.md +17 -0
- package/static/templates/docs/pitch/assumptions.md +17 -0
- package/static/templates/docs/pitch/audience.md +17 -0
- package/static/templates/docs/pitch/concept.md +17 -0
- package/static/templates/docs/pitch/differentiators.md +17 -0
- package/static/templates/docs/pitch/outcomes.md +17 -0
- package/static/templates/docs/pitch/problem.md +17 -0
- package/static/templates/docs/pitch/product-market-fit.md +17 -0
- package/static/templates/docs/pitch/success-criteria.md +17 -0
- package/static/templates/docs/pitch/validation.md +17 -0
- package/static/templates/docs/pitch/value-proposition.md +17 -0
- package/static/templates/docs/pitch/vision.md +17 -0
- package/static/templates/docs/scope/core.md +17 -0
- package/static/templates/docs/scope/exclusions.md +17 -0
- package/static/templates/docs/scope/future.md +17 -0
- package/static/templates/docs/scope/launch.md +17 -0
- package/static/templates/docs/scope/post-launch.md +17 -0
- package/static/templates/docs/scope/priorities.md +17 -0
- package/static/templates/docs/scope/quality-bar.md +17 -0
- package/static/templates/docs/security/audit.md +17 -0
- package/static/templates/docs/security/data-access.md +17 -0
- package/static/templates/docs/security/logging.md +17 -0
- package/static/templates/docs/security/privacy.md +17 -0
- package/static/templates/docs/security/secrets.md +17 -0
- package/static/templates/docs/security/threat-model.md +17 -0
- package/static/templates/docs/testing/end-to-end.md +17 -0
- package/static/templates/docs/testing/integration.md +17 -0
- package/static/templates/docs/testing/regression.md +17 -0
- package/static/templates/docs/testing/security.md +17 -0
- package/static/templates/docs/testing/strategy.md +17 -0
- package/static/templates/docs/testing/test-data.md +17 -0
- package/static/templates/docs/testing/unit.md +17 -0
- package/static/templates/docs/uat/checks.md +17 -0
- package/static/templates/docs/uat/strategy.md +17 -0
- package/static/templates/docs/users/permissions.md +17 -0
- package/static/templates/docs/users/roles.md +17 -0
- package/static/templates/docs/users/user-types.md +17 -0
- package/static/templates/docs/workflows/assignment.md +17 -0
- package/static/templates/docs/workflows/bugfix.md +17 -0
- package/static/templates/docs/workflows/dependencies.md +17 -0
- package/static/templates/docs/workflows/deployment.md +17 -0
- package/static/templates/docs/workflows/development.md +17 -0
- package/static/templates/docs/workflows/planning.md +17 -0
- package/static/templates/docs/workflows/review.md +17 -0
- package/static/templates/docs/workflows/uat.md +17 -0
- package/static/templates/docsSite/docusaurus.config.mjs +144 -0
- package/static/templates/docsSite/gitignore.txt +3 -0
- package/static/templates/docsSite/package.json.txt +42 -0
- package/static/templates/docsSite/pnpm-workspace.yaml +2 -0
- package/static/templates/docsSite/sidebars.mjs +7 -0
- package/static/templates/docsSite/src/css/custom.css +7 -0
- package/static/templates/docsSite/src/pages/index.tsx +9 -0
- package/static/templates/docsSite/static/images/logo.svg +4 -0
- package/static/templates/docsSite/tsconfig.json +7 -0
- package/static/templates/eslint/base/eslint.config.mjs +104 -0
- package/static/templates/eslint/nextjs/eslint.config.mjs +181 -0
- package/static/templates/eslint/react/eslint.config.mjs +173 -0
- package/static/templates/eslint/typescript/eslint.config.mjs +154 -0
- package/static/templates/globals.css +16 -0
- package/static/templates/husky/pre-commit +1 -0
- package/static/templates/next.config.mjs +21 -0
- package/static/templates/prettier.config.mjs +26 -0
- package/static/templates/skills/codeReview.md +31 -0
- package/static/templates/skills/format.md +18 -0
- package/static/templates/skills/lint.md +21 -0
- package/static/templates/skills/lintFix.md +24 -0
- package/static/templates/skills/securityAudit.md +28 -0
- package/static/templates/skills/test.md +24 -0
- package/static/templates/skills/typescript.md +28 -0
- package/static/templates/skills/updateLayout.md +20 -0
- package/static/templates/stylelint/css/stylelint.config.mjs +40 -0
- package/static/templates/stylelint/scss/stylelint.config.mjs +13 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: -1
|
|
3
|
+
sidebar_label: "Human-Readable Names"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Human-Readable Names
|
|
7
|
+
|
|
8
|
+
**Every name we write is made of whole words a person can read out loud.** Variables, function parameters, props, types, constants, form fields, files, folders, routes, database columns, scripts and environment variables — everything. No shortened words, no single letters, no numbered names — not even for a loop counter or an event handler.
|
|
9
|
+
|
|
10
|
+
Anyone reading the code should never have to guess what a name stands for.
|
|
11
|
+
|
|
12
|
+
This applies to everyone writing code, **including AI agents**. An agent copies the examples it sees, so every example in these standards follows this rule too.
|
|
13
|
+
|
|
14
|
+
## Never / Always
|
|
15
|
+
|
|
16
|
+
| Never | Always |
|
|
17
|
+
| -------------------------- | ---------------------------------------------- |
|
|
18
|
+
| `btn`, `msg`, `val` | `button`, `message`, `value` |
|
|
19
|
+
| `(e) =>`, `(item, i) =>` | `(event) =>`, `(item, index) =>` |
|
|
20
|
+
| `acc` in a `reduce` | `runningTotal` — what it actually holds |
|
|
21
|
+
| `args`, `params` (our own) | `commandArguments`, `searchFilters` |
|
|
22
|
+
| `cwd` (our own variable) | `workingDirectory` |
|
|
23
|
+
| `deps`, `env` | `dependencies`, `environment` |
|
|
24
|
+
| `timeoutMs` | `timeoutMilliseconds` |
|
|
25
|
+
| `MAX_RETRY_ATTEMPTS` | `MAXIMUM_RETRY_ATTEMPTS` |
|
|
26
|
+
| `size: "sm" \| "lg"` | `size: "small" \| "large"` |
|
|
27
|
+
| `<T>` | `<ItemType>` |
|
|
28
|
+
| `name="email1"` | `name="emailAddress"` |
|
|
29
|
+
| `helpers.ts`, `misc.ts` | `formatCurrency.ts` — named after what it does |
|
|
30
|
+
| `helpers/`, `utilities/` | `utils/` |
|
|
31
|
+
| `useAuth`, `authStore` | `useAuthentication`, `authenticationStore` |
|
|
32
|
+
| `STRIPE_SK` | `STRIPE_SECRET_KEY` |
|
|
33
|
+
|
|
34
|
+
## Where it applies
|
|
35
|
+
|
|
36
|
+
Everything that has a name:
|
|
37
|
+
|
|
38
|
+
| Thing | ✅ Human-readable | ❌ Not |
|
|
39
|
+
| ------------------------------- | ---------------------------------------------------- | ------------------------------------------------- |
|
|
40
|
+
| **Folders** | `accountSettings/`, `invoiceTable/` | `acctSet/`, `misc/`, `stuff/`, `new/` |
|
|
41
|
+
| **Files** | `formatCurrency.ts`, `InvoiceTable.tsx` | `fmtCur.ts`, `utils2.ts`, `helpers.ts`, `temp.ts` |
|
|
42
|
+
| **Components** | `InvoiceTable`, `SubmitButton` | `Tbl`, `Comp1`, `MyComponent`, `Wrapper2` |
|
|
43
|
+
| **Component props** | `isDisabled`, `onUserSelect`, `invoiceTotal` | `dis`, `cb`, `val`, `data` |
|
|
44
|
+
| **Variables** | `userEmailAddress`, `activeUsers` | `addr`, `x`, `temp`, `data`, `res`, `obj` |
|
|
45
|
+
| **Functions** | `calculateOrderTotal`, `sendWelcomeEmail` | `calc`, `doIt`, `process`, `handle` |
|
|
46
|
+
| **Event handlers** | `handleSearchTextChange` | `onChg`, `handler`, `fn` |
|
|
47
|
+
| **Booleans** | `isLoading`, `hasPermission`, `canDelete` | `loading`, `flag`, `ok`, `chk` |
|
|
48
|
+
| **Constants** | `MAXIMUM_RETRY_ATTEMPTS`, `RETRY_DELAY_MILLISECONDS` | `MAX`, `N`, `DELAY` |
|
|
49
|
+
| **Types** | `UserType`, `InvoiceStatusType`, `ButtonProps` | `U`, `Data`, `IProps`, `Obj` |
|
|
50
|
+
| **Generic type parameters** | `<ItemType>`, `<ResponseType>` | `<T>`, `<K, V>` |
|
|
51
|
+
| **Union and enum values** | `"processing" \| "shipped" \| "cancelled"` | `"p" \| "s"`, `1 \| 2 \| 3` |
|
|
52
|
+
| **Form fields** | `name="emailAddress"`, `name="companyName"` | `name="email1"`, `name="fld3"`, `name="q"` |
|
|
53
|
+
| **CSS Module classes** | `.primaryButton`, `.invoiceRow` | `.pb`, `.btn2`, `.wrapper3` |
|
|
54
|
+
| **CSS variables** | `--color-brand-primary` | `--c1`, `--blue2` |
|
|
55
|
+
| **Database tables and columns** | `draft_ticket`, `created_at`, `invoice_total` | `dt`, `col1`, `amt`, `flag` |
|
|
56
|
+
| **API routes** | `/api/calendar-events` | `/api/ce`, `/api/data`, `/api/v2x` |
|
|
57
|
+
| **Environment variables** | `STRIPE_SECRET_KEY`, `DOCS_URL` | `SK`, `KEY2`, `URL_1` |
|
|
58
|
+
| **Test names** | `it("adds a thousands separator")` | `it("works")`, `it("test 1")` |
|
|
59
|
+
| **Doc files** | `architecture/background-jobs.md` | `arch/bg.md`, `notes2.md` |
|
|
60
|
+
| **Error messages** | `"Enter an email address"` | `"Invalid input"`, `"Error 42"` |
|
|
61
|
+
|
|
62
|
+
The words come from the [Ontology](/ontology) wherever it has a term: reusable functions are in `utils/`, never `helpers/` or `utilities/`. A project's own ontology adds its product words: in Launchpad, a Draft Ticket is `draftTicket`, never `pendingTicket`. The casing comes from [Naming Conventions](/Structure/naming-conventions).
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Form fields
|
|
67
|
+
|
|
68
|
+
Form fields are where names slip most, and where they matter most: a field's name travels through the whole system.
|
|
69
|
+
|
|
70
|
+
**One field, one name, everywhere it goes.** The same words name the input, the form's data, the API request, the type and the database column:
|
|
71
|
+
|
|
72
|
+
| Where | Name |
|
|
73
|
+
| ------------------------------- | ---------------------- |
|
|
74
|
+
| Input `name` and `id` | `emailAddress` |
|
|
75
|
+
| Form data and validation schema | `emailAddress` |
|
|
76
|
+
| API request body | `emailAddress` |
|
|
77
|
+
| Type property | `emailAddress: string` |
|
|
78
|
+
| Database column | `email_address` |
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// ✅ Good
|
|
82
|
+
<label htmlFor="emailAddress">Email address</label>
|
|
83
|
+
<input id="emailAddress" name="emailAddress" type="email" />
|
|
84
|
+
|
|
85
|
+
<label htmlFor="billingFrequency">Billing frequency</label>
|
|
86
|
+
<select id="billingFrequency" name="billingFrequency">
|
|
87
|
+
<option value="monthly">Monthly</option>
|
|
88
|
+
<option value="yearly">Yearly</option>
|
|
89
|
+
</select>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// ❌ Bad
|
|
94
|
+
<input id="f3" name="email1" placeholder="Email" />
|
|
95
|
+
|
|
96
|
+
<select name="freq">
|
|
97
|
+
<option value="1">Monthly</option>
|
|
98
|
+
<option value="2">Yearly</option>
|
|
99
|
+
</select>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- **`name` and `id` are full words**, and match each other.
|
|
103
|
+
- **Every field has a visible `<label>`** in plain English. A placeholder is never the label.
|
|
104
|
+
- **Option values are words**, not numbers: `value="monthly"`, not `value="1"`.
|
|
105
|
+
- **Buttons with only an icon get an `aria-label`** that says what they do: `aria-label="Delete invoice"`.
|
|
106
|
+
- **Validation messages name the field and say what to do**: "Enter an email address", not "Required".
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## `id` is a word
|
|
111
|
+
|
|
112
|
+
**`id` is its own word. Never write it out as `identifier`.**
|
|
113
|
+
|
|
114
|
+
| Where | Write |
|
|
115
|
+
| ---------------------- | ------------------------------- |
|
|
116
|
+
| A property or variable | `id`, `userId`, `draftTicketId` |
|
|
117
|
+
| A list of them | `userIds` |
|
|
118
|
+
| A constant | `DEFAULT_PROJECT_ID` |
|
|
119
|
+
| A database column | `user_id` |
|
|
120
|
+
|
|
121
|
+
- **Case it as a word**: `userId`, never `userID`.
|
|
122
|
+
- **`identifier` means something else.** Use it only when an outside system has a separate field with that name. Linear has both: `id` is its hidden unique code, and `identifier` is the readable `ACT-73`. Writing `identifier` for an `id` would mix the two up.
|
|
123
|
+
|
|
124
|
+
## Dictionary
|
|
125
|
+
|
|
126
|
+
Some short names are not abbreviations. They are the word people actually say, and the long form would only make the name harder to read. **These are dictionary words, and stay short:**
|
|
127
|
+
|
|
128
|
+
| Word | Means | Example names |
|
|
129
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
|
|
130
|
+
| `id` | A unique id | `userId`, `user_id`, `DEFAULT_PROJECT_ID` |
|
|
131
|
+
| `docs` | A project's documentation | `docs/`, `docsSite/`, `DOCS_URL`, `pnpm docs:dev` |
|
|
132
|
+
| `lib` | The folder for outside connections and app-wide setup. Next.js and shadcn/ui use the same name | `lib/`, `lib/integrations/` |
|
|
133
|
+
| `props` | The values a component takes | `props`, `ButtonProps` |
|
|
134
|
+
| `ref` | A React ref: a handle on an element or a value kept between renders | `inputRef`, `useRef`, `scrollContainerRef` |
|
|
135
|
+
| `repo` | A repository: in GitHub's words, it "contains all of your project's files and each file's revision history" | `repoName`, `repoUrl` |
|
|
136
|
+
| `utils` | Reusable functions that are not components | `utils/`, `utils/formatCurrency/` |
|
|
137
|
+
| `app` | The application | `app/`, `appName` |
|
|
138
|
+
| `url` | A web address | `imageUrl`, `DOCS_URL` |
|
|
139
|
+
| `api` | An application programming interface | `api/`, `apiResponse` |
|
|
140
|
+
| `uat` | User acceptance testing | `uatEnvironment` |
|
|
141
|
+
| `pdf`, `html`, `css`, `json` | File formats | `invoicePdf`, `htmlContent`, `globals.css` |
|
|
142
|
+
| `readme` | A project's README file | `readmeContent` |
|
|
143
|
+
|
|
144
|
+
**Case them as words** in camelCase and PascalCase: `imageUrl`, `apiResponse`, `docsSite` — never `imageURL` or `APIResponse`.
|
|
145
|
+
|
|
146
|
+
**This list is the only back door.** A short name not on it gets written out in full. If people say the long form out loud — "environment", "dependencies", "message" — write the long form.
|
|
147
|
+
|
|
148
|
+
### Adding a word to the dictionary
|
|
149
|
+
|
|
150
|
+
When a short name feels like a dictionary word, don't just use it — add it here first:
|
|
151
|
+
|
|
152
|
+
1. **Ask:** is this the word people actually say, so the long form would only be harder to read? `docs`, `repo`: yes. `env`, `btn`, `msg`: no.
|
|
153
|
+
2. **Add a row** to the table above, in the same pull request as the code that uses it.
|
|
154
|
+
3. **If ESLint flags the word**, add it to `replacements` in the `unicorn/name-replacements` rule of the [ESLint config](/Standards/eslint), so the linter agrees: `replacements: { docs: false, lib: false, props: false, ref: false, repo: false, utils: false }`.
|
|
155
|
+
|
|
156
|
+
The list is the record. If a word is not on it, it is not a dictionary word yet.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Third-party names
|
|
161
|
+
|
|
162
|
+
**This rule is for the names we write.** It does not cover names that come from someone else's code — a package, a framework, an outside service. Those stay exactly as they are: everyone knows them by that name, their docs use that name, and renaming them breaks the code. Use them as they are where they touch the outside, then **give the value a full-word name as soon as it comes into our code**.
|
|
163
|
+
|
|
164
|
+
| From | Examples |
|
|
165
|
+
| ------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
166
|
+
| An outside service's data | Linear's `id`, `identifier` and `pageInfo` |
|
|
167
|
+
| Node and library options | `cwd` in `execFile`, `process.env`, `stdout`, `argv` |
|
|
168
|
+
| React | `ref`, `children`, `useRef`, `useEffect` |
|
|
169
|
+
| shadcn/ui | the `cn` class-name function, and the component files it generates |
|
|
170
|
+
| Next.js | the `app/` folder, `params`, `searchParams`, `LayoutProps`, `generateMetadata` |
|
|
171
|
+
| HTML and CSS | `href`, `src`, `alt`, `tabIndex`, Tailwind classes like `text-sm` |
|
|
172
|
+
| Tools | `node_modules`, `.env.local`, Docusaurus's `src/pages`, Storybook's `args`, the `dev` and `prepare` scripts |
|
|
173
|
+
| A project's own ontology | Its decided product terms, such as Launchpad's `dev_agent` |
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
// ✅ Good — the option keeps its third-party name; our value gets a full-word name
|
|
177
|
+
const workingDirectory = process.cwd();
|
|
178
|
+
const commandArguments = ["lint", "--fix"];
|
|
179
|
+
|
|
180
|
+
execFile("pnpm", commandArguments, { cwd: workingDirectory });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
// ❌ Bad — renaming the third-party option breaks the call: execFile ignores "workingDirectory"
|
|
185
|
+
execFile("pnpm", commandArguments, { workingDirectory });
|
|
186
|
+
|
|
187
|
+
// ❌ Bad — the third-party name leaks into our own code
|
|
188
|
+
const cwd = process.cwd();
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### When a name clashes
|
|
192
|
+
|
|
193
|
+
**Keep a third-party import's own name.** When it clashes with something the file declares itself, **rename our own thing first**. A clash usually means our name is too vague: give it a more specific full-word name that says what it actually is.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
// ✅ Good — our function says what it makes, so Supabase's createClient keeps its name
|
|
197
|
+
import { createClient } from "@supabase/supabase-js";
|
|
198
|
+
|
|
199
|
+
export const createProjectDatabaseClient = () =>
|
|
200
|
+
createClient(DATABASE_URL, DATABASE_KEY);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
// ❌ Bad — the import is renamed to make room for our vague name
|
|
205
|
+
import { createClient as createSupabaseClient } from "@supabase/supabase-js";
|
|
206
|
+
|
|
207
|
+
export const createClient = () =>
|
|
208
|
+
createSupabaseClient(DATABASE_URL, DATABASE_KEY);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Rename the import only when our side can't change:**
|
|
212
|
+
|
|
213
|
+
| The clash | Why our side can't change | Rename the import to |
|
|
214
|
+
| -------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------- |
|
|
215
|
+
| **Two third-party names**, such as `Link` from `next/link` and the `Link` icon from `lucide-react` | Neither name is ours | The owner plus the original name: `Link as LucideLinkIcon` |
|
|
216
|
+
| **Our name is fixed by a framework**, such as a Next.js route's `GET` or `generateMetadata` | The framework finds it only by that exact name | The owner plus the original name |
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
// ✅ Good — two third-party names clash, so one import is renamed: owner plus original name
|
|
220
|
+
import Link from "next/link";
|
|
221
|
+
import { Link as LucideLinkIcon } from "lucide-react";
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// ❌ Bad — renamed for taste, or shortened
|
|
226
|
+
import { createClient as makeDb } from "@supabase/supabase-js";
|
|
227
|
+
import { Link as Icon } from "lucide-react";
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Never rename an import for taste, and never shorten it.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Examples in code
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
// ✅ Good
|
|
238
|
+
const userEmailAddress = user.email;
|
|
239
|
+
const isSubmitButtonDisabled = !isFormValid;
|
|
240
|
+
|
|
241
|
+
const calculateOrderTotal = (orderItems: OrderItemType[]) => {
|
|
242
|
+
return orderItems.reduce(
|
|
243
|
+
(runningTotal, orderItem) => runningTotal + orderItem.price,
|
|
244
|
+
0,
|
|
245
|
+
);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
users.map((user) => user.name);
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
// ❌ Bad
|
|
253
|
+
const addr = user.email;
|
|
254
|
+
const btnDis = !valid;
|
|
255
|
+
|
|
256
|
+
const calc = (arr: OrderItemType[]) => {
|
|
257
|
+
return arr.reduce((acc, i) => acc + i.price, 0);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
users.map((u) => u.name);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## How it is checked
|
|
266
|
+
|
|
267
|
+
| Names | Checked by |
|
|
268
|
+
| ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
269
|
+
| Variables, functions, parameters and file names | ESLint `unicorn/name-replacements` and `unicorn/filename-case` |
|
|
270
|
+
| Type names | ESLint `@typescript-eslint/naming-convention` |
|
|
271
|
+
| CSS variables and CSS Module classes | Stylelint |
|
|
272
|
+
| **Everything else** — folders, props, form fields, union values, database columns, routes, environment variables, test names | **Code review and AI agents.** The linter cannot read these, so every agent and reviewer checks them |
|
|
273
|
+
|
|
274
|
+
ESLint allows the [dictionary](#dictionary) words it would otherwise flag (`docs`, `lib`, `props`, `ref`, `repo`, `utils`), and exactly two third-party names as they are: `params` and `searchParams`. It still flags any of our own names built from an third-party name, such as `inputRef`.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 1.5
|
|
3
|
+
sidebar_label: "Fixing Lint Errors"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Fixing Lint Errors
|
|
7
|
+
|
|
8
|
+
When `pnpm lint` reports an error, find the rule name at the end of the line in the tables below. Each row says what the error means in plain English and how to fix it.
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
src/userService.ts
|
|
12
|
+
12:3 error Dependency cycle detected import-x/no-cycle
|
|
13
|
+
│ │ └── the rule name — look this up below
|
|
14
|
+
│ └── the message
|
|
15
|
+
└── line 12, column 3
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## The golden rule
|
|
19
|
+
|
|
20
|
+
**Fix the code. Never switch the rule off to make the error go away.**
|
|
21
|
+
|
|
22
|
+
A lint error means the code breaks a standard. The fix is always to change the code. Do not add `eslint-disable` comments, do not delete rules from the config, and do not move files into `vendor/` to hide them.
|
|
23
|
+
|
|
24
|
+
There are exactly two rules that may be switched off for one line, and both need a comment saying why — see [the only allowed exceptions](#the-only-allowed-exceptions).
|
|
25
|
+
|
|
26
|
+
This applies to everyone, **including AI agents**. An agent's work is not finished while `pnpm lint` reports errors.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Imports
|
|
31
|
+
|
|
32
|
+
| Rule | What you will see | What it means | How to fix it |
|
|
33
|
+
| -------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
34
|
+
| `import-x/no-cycle` | `Dependency cycle detected` | Two files import each other, directly or through other files. One will load before the other is ready | Move the part both files need into a **third file** that both import. If the cycle goes through an `index.ts`, import the file directly instead. See [Import safety](/Structure/import-organization#import-safety) |
|
|
35
|
+
| `import-x/no-unresolved` | `Unable to resolve path to module '…'` | The file being imported does not exist at that path | Check the spelling and the path. If the file was renamed or moved, update the import |
|
|
36
|
+
| `import-x/no-default-export` | `Prefer named exports` | The file uses `export default` | Change to a named export: `export const formatCurrency = …` |
|
|
37
|
+
| `@typescript-eslint/consistent-type-imports` | `All imports in the declaration are only used as types. Use import type` | A type is imported like a value | Change `import { UserType }` to `import type { UserType }` |
|
|
38
|
+
|
|
39
|
+
## Order and variables
|
|
40
|
+
|
|
41
|
+
| Rule | What you will see | What it means | How to fix it |
|
|
42
|
+
| ----------------------------------- | ------------------------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `no-use-before-define` | `'formatReport' was used before it was defined` | Something is used above the line that creates it | Move the definition **above** the code that uses it |
|
|
44
|
+
| `no-restricted-syntax` | `Use const. If this value really must be reassigned…` | A variable is declared with `let` | Change `let` to `const`. If the value really must change, see [the allowed exceptions](#the-only-allowed-exceptions) |
|
|
45
|
+
| `no-var` | `Unexpected var, use let or const instead` | A variable is declared with `var` | Change `var` to `const` |
|
|
46
|
+
| `prefer-const` | `'total' is never reassigned. Use 'const' instead` | A `let` variable never changes | Change `let` to `const` |
|
|
47
|
+
| `no-magic-numbers` | `No magic number: 3` | A bare number is used in the logic | Give it a name: `const MAXIMUM_RETRY_ATTEMPTS = 3;` |
|
|
48
|
+
| `unicorn/name-replacements` | ``The variable `btn` should be named `button` `` | A name is shorthand | Rename it to the full English word |
|
|
49
|
+
| `unicorn/no-top-level-side-effects` | `Do not use top-level side effects in exported modules` | The file runs code as it loads | Put the code inside a function and export the function |
|
|
50
|
+
|
|
51
|
+
## Functions
|
|
52
|
+
|
|
53
|
+
| Rule | What you will see | What it means | How to fix it |
|
|
54
|
+
| ----------------------- | ------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------ |
|
|
55
|
+
| `func-style` | `Expected a function expression` | The `function` keyword was used | Rewrite as an arrow function: `const name = () => { … };` |
|
|
56
|
+
| `prefer-arrow-callback` | `Unexpected function expression` | A callback uses the `function` keyword | Rewrite the callback as an arrow function |
|
|
57
|
+
| `no-restricted-syntax` | `Use an arrow function` | `const name = function () {}` was used | Rewrite as `const name = () => {}` |
|
|
58
|
+
| `no-restricted-syntax` | `Use a single object parameter when a function needs more than one value` | A function has more than one parameter | Take one object instead: `({ name, emailAddress }: CreateUserProps) =>` |
|
|
59
|
+
| `no-restricted-syntax` | `Use functions, not classes` | A `class` was used | Rewrite it as functions |
|
|
60
|
+
| `max-depth` | `Blocks are nested too deeply (4). Maximum allowed is 3` | `if`s and loops are nested more than three deep | Return early for the error cases, or move the inner part into its own function |
|
|
61
|
+
| `no-nested-ternary` | `Do not nest ternary expressions` | A `? :` sits inside another `? :` | Use an `if`, or move it into its own function |
|
|
62
|
+
|
|
63
|
+
## Files
|
|
64
|
+
|
|
65
|
+
| Rule | What you will see | What it means | How to fix it |
|
|
66
|
+
| ----------------------- | ------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
67
|
+
| `max-lines` | `File has too many lines (312). Maximum allowed is 300` | The file is over 300 lines | Split it: move functions to `utils/`, sub-components to their own folders, types to `types.ts` |
|
|
68
|
+
| `unicorn/filename-case` | `Filename is not in camel case or pascal case` | The file name is in the wrong case | Rename it: camelCase for code files, PascalCase for component files |
|
|
69
|
+
|
|
70
|
+
## Types
|
|
71
|
+
|
|
72
|
+
| Rule | What you will see | What it means | How to fix it |
|
|
73
|
+
| ----------------------------------------------- | ------------------------------------------------------------------ | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
74
|
+
| `@typescript-eslint/no-explicit-any` | `Unexpected any. Specify a different type` | `any` was used | Use the real type, or `unknown` and then check it |
|
|
75
|
+
| `@typescript-eslint/no-non-null-assertion` | `Forbidden non-null assertion` | `value!` was used | Check that the value exists first: `if (!value) { return; }` |
|
|
76
|
+
| `@typescript-eslint/consistent-type-assertions` | `Do not use any type assertions` | `value as SomeType` was used | Validate the value instead. If a cast is truly needed, see [the allowed exceptions](#the-only-allowed-exceptions) |
|
|
77
|
+
| `@typescript-eslint/naming-convention` | ``Interface name `User` must match the RegExp: /(Props\|Type)$/u`` | A type name does not end in `Props` or `Type` | Rename it: `UserType`, `ButtonProps` |
|
|
78
|
+
|
|
79
|
+
## React
|
|
80
|
+
|
|
81
|
+
| Rule | What you will see | What it means | How to fix it |
|
|
82
|
+
| ------------------------------------- | ------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
83
|
+
| `react/jsx-no-bind` | `JSX props should not use arrow functions` | A function is written inside the JSX, or passed without `useCallback` | Declare it above with `useCallback` and pass it by name |
|
|
84
|
+
| `react/no-multi-comp` | `Declare only one React component per file` | The file has more than one component | Move the second component to its own folder |
|
|
85
|
+
| `react/function-component-definition` | An error naming this rule | A component is written with the `function` keyword | Rewrite it as an arrow function. `pnpm lint:fix` does this automatically |
|
|
86
|
+
|
|
87
|
+
## Comments
|
|
88
|
+
|
|
89
|
+
| Rule | What you will see | What it means | How to fix it |
|
|
90
|
+
| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------- |
|
|
91
|
+
| `@eslint-community/eslint-comments/require-description` | `Unexpected undescribed directive comment. Include descriptions to explain why the comment is necessary` | An `eslint-disable` comment has no reason | Add the reason after `--`. Better: remove the comment and fix the code |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## The only allowed exceptions
|
|
96
|
+
|
|
97
|
+
Two rules may be switched off for a single line. Both need the reason written after `--`, and ESLint rejects the comment without one.
|
|
98
|
+
|
|
99
|
+
### A variable that must be reassigned
|
|
100
|
+
|
|
101
|
+
Always use `const`. When a value genuinely has to change — a running total, a retry counter — use `let` and say why:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
// eslint-disable-next-line no-restricted-syntax -- the running total is built up across the loop
|
|
105
|
+
let runningTotal = 0;
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Before reaching for `let`, check whether `const` with `map`, `filter` or `reduce` does the job. It usually does.
|
|
109
|
+
|
|
110
|
+
### A type cast that cannot be avoided
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- the API returns untyped JSON; validated by parseUser below
|
|
114
|
+
const user = response.data as UserType;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Nothing else is switched off. If another rule seems impossible to satisfy, the code needs restructuring — ask before disabling it.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Errors that stop ESLint running
|
|
122
|
+
|
|
123
|
+
| What you will see | What it means | How to fix it |
|
|
124
|
+
| ------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------- |
|
|
125
|
+
| `typescript-eslint does not support TS 7.0` | The linter cannot run against TypeScript 7 yet | See the [TypeScript 7 note](/Standards/eslint#installation) on the ESLint page |
|
|
126
|
+
| `contextOrFilename.getFilename is not a function` | The React plugin cannot detect the React version | Set `settings.react.version` to the project's React version instead of `"detect"` |
|
|
127
|
+
| `Cannot find module '…'` | A lint package is not installed | Run the install commands on the [ESLint page](/Standards/eslint#installation) |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## How errors are caught
|
|
132
|
+
|
|
133
|
+
Lint runs in three places, so a broken standard is caught before it reaches `main`:
|
|
134
|
+
|
|
135
|
+
| Where | When | What happens on an error |
|
|
136
|
+
| --------------------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------ |
|
|
137
|
+
| **AI agents** | Before finishing any task | The agent fixes the error. Its work is not done until `pnpm lint` passes |
|
|
138
|
+
| **Git hook** ([`.husky/pre-commit`](#lint-before-every-commit)) | On every commit | The commit is refused until the errors are fixed |
|
|
139
|
+
| **CI** | On every change pushed | The change cannot be merged |
|
|
140
|
+
|
|
141
|
+
## Lint before every commit
|
|
142
|
+
|
|
143
|
+
A [Husky](https://typicode.github.io/husky/) git hook runs `pnpm lint` before every commit, so code that breaks the standards cannot be committed.
|
|
144
|
+
|
|
145
|
+
1. Install Husky:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pnpm add -D husky
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
2. Add the `prepare` script to `package.json`, so the hook is installed whenever someone runs `pnpm install`:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"scripts": {
|
|
156
|
+
"prepare": "husky"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
3. Copy the hook file [`.husky/pre-commit`](/templates#husky-pre-commit):
|
|
162
|
+
|
|
163
|
+
```text title=".husky/pre-commit"
|
|
164
|
+
pnpm lint
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
4. Run `pnpm install` once to switch it on.
|
|
168
|
+
|
|
169
|
+
**Add the hook last**, once the project already passes `pnpm lint`. Otherwise every commit is refused until the whole codebase is fixed.
|
|
170
|
+
|
|
171
|
+
How the project names commits and branches, and who may commit, is up to the project. See [Project workflow](/Standards/agent-specs#project-workflow).
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
sidebar_label: "Prettier"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Prettier Configuration
|
|
7
|
+
|
|
8
|
+
A single shared Prettier config keeps formatting consistent and eliminates "style" discussion in code review.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm add -D prettier prettier-plugin-tailwindcss
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Create `prettier.config.mjs` in your project root:
|
|
19
|
+
|
|
20
|
+
```js title="prettier.config.mjs"
|
|
21
|
+
/** @type {import("prettier").Config & import("prettier-plugin-tailwindcss").PluginOptions} */
|
|
22
|
+
const config = {
|
|
23
|
+
// Short lines, so code reads well on a phone.
|
|
24
|
+
printWidth: 70,
|
|
25
|
+
// Tabs indent. A tab counts as 2 characters towards the line width.
|
|
26
|
+
useTabs: true,
|
|
27
|
+
tabWidth: 2,
|
|
28
|
+
singleQuote: false,
|
|
29
|
+
trailingComma: "all",
|
|
30
|
+
bracketSpacing: true,
|
|
31
|
+
bracketSameLine: false,
|
|
32
|
+
arrowParens: "always",
|
|
33
|
+
semi: true,
|
|
34
|
+
endOfLine: "lf",
|
|
35
|
+
plugins: ["prettier-plugin-tailwindcss"],
|
|
36
|
+
tailwindStylesheet: "./app/globals.css",
|
|
37
|
+
overrides: [
|
|
38
|
+
{
|
|
39
|
+
// YAML does not allow tabs, and Markdown reads a tab as four spaces.
|
|
40
|
+
files: ["*.md", "*.mdx", "*.yml", "*.yaml"],
|
|
41
|
+
options: { useTabs: false },
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default config;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Add scripts to `package.json`:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"scripts": {
|
|
54
|
+
"format": "prettier --write .",
|
|
55
|
+
"format:check": "prettier --check ."
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Tailwind class sorting
|
|
61
|
+
|
|
62
|
+
The config includes [`prettier-plugin-tailwindcss`](https://github.com/tailwindlabs/prettier-plugin-tailwindcss), the official Tailwind plugin. It sorts the classes in every `className` into one standard order when the file is formatted, so the same classes are always written the same way:
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// Before formatting
|
|
66
|
+
<button className="text-white px-8 hover:bg-brand-primary-hover rounded-md bg-brand-primary py-4 flex">
|
|
67
|
+
|
|
68
|
+
// After formatting
|
|
69
|
+
<button className="flex rounded-md bg-brand-primary px-8 py-4 text-white hover:bg-brand-primary-hover">
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
- **It is part of the standard**, not a project extra. Every project that uses Tailwind has it.
|
|
73
|
+
- **`tailwindStylesheet` points at the project's global stylesheet**, so the plugin knows the project's own colours. Change the path if `globals.css` lives somewhere else.
|
|
74
|
+
- **A project with no Tailwind** (a plain Node.js service) removes the `plugins` and `tailwindStylesheet` lines.
|
|
75
|
+
|
|
76
|
+
## Settings reference
|
|
77
|
+
|
|
78
|
+
| Option | Value | Rationale |
|
|
79
|
+
| ----------------- | ---------- | ----------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `printWidth` | `70` | Short lines read well on a phone, where a lot of code is reviewed |
|
|
81
|
+
| `tabWidth` | `2` | How many characters a tab counts as towards the line width; matches `.editorconfig` |
|
|
82
|
+
| `useTabs` | `true` | Tabs indent code: each person sees the width they like, and files are smaller. Markdown and YAML use spaces |
|
|
83
|
+
| `singleQuote` | `false` | Double quotes are standard in JSX and HTML; one style for all |
|
|
84
|
+
| `trailingComma` | `"all"` | Cleaner diffs when adding items to the end of a list |
|
|
85
|
+
| `bracketSpacing` | `true` | `{ foo }` is more readable than `{foo}` |
|
|
86
|
+
| `bracketSameLine` | `false` | Closing `>` on its own line for JSX |
|
|
87
|
+
| `arrowParens` | `"always"` | `(x) => x` is consistent whether 0, 1, or many params |
|
|
88
|
+
| `semi` | `true` | Explicit semicolons avoid ASI footguns |
|
|
89
|
+
| `endOfLine` | `"lf"` | Unix line endings everywhere; matches `.editorconfig` |
|
|
90
|
+
|
|
91
|
+
## Editor integration
|
|
92
|
+
|
|
93
|
+
### VS Code
|
|
94
|
+
|
|
95
|
+
Install the [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) extension, then add to your workspace settings:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
100
|
+
"editor.formatOnSave": true
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### JetBrains IDEs
|
|
105
|
+
|
|
106
|
+
Go to **Settings → Languages & Frameworks → JavaScript → Prettier** and enable "Run on save".
|