Files
tiny-pattern-ts/project-specs.md
T
tmu 34e9b52ee8 ♻️ Move type-aware config to .oxlintrc.json; use source-level disable directives
Cleaner separation of concerns:
- options.typeAware: true in .oxlintrc.json activates type-aware
  rules declaratively (equivalent to --type-aware CLI flag, but
  the script command stays clean: just 'oxlint ...')
- Remove the 3 type-aware rule disables from .oxlintrc.json
- Add source-level oxlint-disable directives instead:
  - 4x typescript/no-unsafe-type-assertion (pattern.ts: keysMatch
    Object.keys() cast, candidate[] cast; structuralMatcher value
    as S cast; match.ts: handler as ... cast in nextCases)
  - 1x typescript/no-unnecessary-type-parameters (pattern.ts:
    keysMatch <S extends object>)
  - 1x file-level typescript/no-floating-promises in index.test.ts
    (expectTypeOf() is a sync type-assertion library that the
    type-aware linter misidentifies)

Disabling rules at the source (next to the line that needs the
exemption) documents intent more clearly than a global config
override, and makes the trade-off visible to anyone reading the
code. Re-enabling a rule in the future only requires removing the
inline comment, not editing a central config.
2026-09-04 23:27:58 +02:00

556 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Project Specifications for TypeScript NPM Module
- name of package: tiny-pattern-ts
## 0. References
typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starter-tiny/
## 1. Development Environment
- **TypeScript**: Use strictest practical rules. Inline in `tsconfig.json` (not
via `@tsconfig/strictest`). The rule set is: `strict`, `noImplicitAny`,
`noImplicitThis`, `alwaysStrict`, `strictNullChecks`, `strictFunctionTypes`,
`strictBindCallApply`, `strictPropertyInitialization`, `noImplicitReturns`,
`noFallthroughCasesInSwitch`, `noUncheckedIndexedAccess`, `noImplicitOverride`,
`noUnusedLocals`, `noUnusedParameters`, `forceConsistentCasingInFileNames`,
`isolatedModules`, `verbatimModuleSyntax`.
- **EditorConfig**: Use `.editorconfig` from typescript-lib-starter-tiny.
- **oxfmt**: Rust-based formatter, Prettier-compatible. Replaces Prettier.
Config in `.oxfmtrc.json` (same shape as `.prettierrc`).
- **oxlint**: Rust-based linter. Replaces ESLint. Config in `.oxlintrc.json`
with `typescript`, `unicorn`, `oxc`, `import` plugins. Categories enabled
as errors: `correctness`, `suspicious`, `restriction`. As warnings: `perf`,
`style`. `nursery` is off. Type-aware rules are enabled via
`options.typeAware: true` in `.oxlintrc.json` (no CLI flag needed);
the linter then uses `oxlint-tsgolint` for rules that require type
information.
- **oxlint-tsgolint**: TypeScript-Go-backed type-aware linter for oxlint.
Activated by `options.typeAware: true` in `.oxlintrc.json`. Native
bindings installed as `optionalDependencies` per platform (same
pattern as oxlint).
- **oxlint rules disabled by design** (in `.oxlintrc.json`):
- `eslint/no-undefined` — we use `undefined` as the no-match sentinel.
- `eslint/sort-keys` — handler/case order is semantic, not alphabetical.
- `eslint/id-length` — `T`, `R`, `U`, `V` are standard TS generics.
- `import/no-named-export` — false positive on library entry re-exports.
- Stylistic rules superseded by oxfmt (oxfmt is the canonical
formatter; these rules either conflict with its output or
duplicate features oxfmt already provides):
- `eslint/one-var` — oxfmt uses comma-joined `const`
declarations; oxlint wanted to split them.
- `import/group-exports` — oxfmt keeps separate `export`
statements as-is.
- `import/exports-last` — statement ordering is up to oxfmt.
- `eslint/sort-imports` — replaced by oxfmt's built-in
`sortImports` (enabled in `.oxfmtrc.json`).
- `import/consistent-type-specifier-style` — inline
`import { type X, Y }` is intentional for grouping.
- `unicorn/prefer-export-from` — conflicts with how the
barrel `src/index.ts` re-exports through `src/match.ts`.
- `typescript/method-signature-style` — method signatures
in `interface` are conventional TS ergonomics.
For `*.test.ts` files additionally: `no-unused-expressions` (for
`expectTypeOf(...)` calls), `no-empty-file` (we have a single import
per test file in some cases), `import/no-nodejs-modules` (we use
`node:test`/`node:assert`/`node:fs` intentionally), `eslint/no-magic-numbers`
(literals in tests are fine).
- **Import sorting**: built into oxfmt (no separate plugin).
- **cspell**: Basic spelling configuration. Words dictionary in `cspell.json`
covers tooling names (`oxlint`, `oxfmt`, `oxc`, `nodenext`,
`oxfmtrc`, `oxlintrc`, `EDITMSG`, `typescriptteam`,
`gitmoji`, `dbaeumer`, `msvc`) and a few library names (`tslib`,
`typefest`, `lefthook`).
- **Lefthook**: Pre-commit checks (run in parallel, lefthook v2 schema).
Single source of truth for the underlying commands is in
`package.json#scripts`; the lefthook config only describes _what to
run on which files_. Hooks that should run on staged files
(`oxlint`, `oxfmt`, `cspell`) set the `LEFTHOOK_FILES` env var to
the staged-files list via a `sh -c` wrapper, and the npm script
uses `${LEFTHOOK_FILES:-<default>}` to default to the full project
when invoked manually:
- `pre-commit.commands.oxlint` (glob `*.{ts,tsx,js,jsx,mjs,cjs}`):
`sh -c 'LEFTHOOK_FILES="$0" npm run check:oxlint' {staged_files}`
- `pre-commit.commands.oxfmt` (glob `*.{ts,tsx,js,jsx,mjs,cjs}`):
`sh -c 'LEFTHOOK_FILES="$0" npm run check:oxfmt' {staged_files}`
- `pre-commit.commands.cspell`:
`sh -c 'LEFTHOOK_FILES="$0" npm run check:cspell' {staged_files}`
- `pre-commit.commands.typecheck`:
`npm run check:tsc` (no file args needed)
`outdated` is intentionally NOT a pre-commit check (it can flag
upstream patch releases that aren't actionable locally); it runs in
CI and via the explicit `lefthook run outdated` command.
- **Additional Dev Dependencies**:
- lefthook
- check-outdated
- c8 (coverage for `node --test`)
- expect-type (type-level assertions in tests)
- oxfmt, oxlint (with their native bindings as `optionalDependencies`
so the correct binding is selected per platform automatically)
- typescript (TS 7)
- @types/node
- tslib (available for future runtime helper imports)
- type-fest (utility types)
## 2. Build & Test
- **Build Tool**: TypeScript 7 (`tsc`) — no bundler, no Vite. The build
is a plain `tsc -p tsconfig.build.json` invocation that emits ESM
JavaScript and `.d.ts` declarations to `dist/`. ESM only, no CJS.
- **Testing**: Node's built-in `node --test` with `--strip-types` (Node
22.6+, unflagged on Node 24/26). Test files are co-located with
source as `*.test.ts`. No Vitest.
- **TypeScript Build Output**: `dist` directory (set in
`tsconfig.build.json#outDir`; `tsconfig.json` keeps `outDir` for
editor tooling but excludes tests from emission via
`tsconfig.build.json#exclude`).
- **Import extensions**: Source uses `.ts` extensions in imports
(e.g. `from "./match.ts"`) so `node --strip-types` resolves them
at test time. TypeScript's `rewriteRelativeImportExtensions` (in
`tsconfig.build.json`) rewrites these to `.js` in the emitted
`dist/*` output, so consumers see conventional ESM imports.
- **TypeScript Compiler Options Notes**:
- `allowImportingTsExtensions: true` is set in the root
`tsconfig.json` (works because the root config has `noEmit: true`).
- `rewriteRelativeImportExtensions: true` is set in
`tsconfig.build.json` to rewrite `.ts` to `.js` on emit.
- `module: nodenext`, `moduleResolution: nodenext` for ESM-first
Node packages.
- `verbatimModuleSyntax: true` enforces explicit `import type`.
- `target: es2024`, `lib: ["es2024"]` (Node 26 supports all ES2024
features natively).
- **Additional Dev Dependencies** (for types and tests):
- `tslib` — available for runtime helper imports; not currently
used by source (kept for future use per the original spec).
- `type-fest` — utility types; not currently used by source
(kept for future use per the original spec).
- `@types/node` — types for `node:test`, `node:assert`, `node:fs`.
- **Node Version**: `>=26` (engines field). Pinned via `.node-version`
for fnm/nvm/volta/mise auto-switching and CI
(`actions/setup-node@v4` with `node-version-file: .node-version`).
## 3. Project Structure & Files
- **.gitignore**: Ignore `dist`, `node_modules`, `coverage`, and other
common files.
- **.node-version**: Single line containing the Node major version
(currently `26`). Used by version managers and CI.
- **.npmignore**: Ignore `node_modules/`, `coverage/`, `*.log`,
`*.tsbuildinfo`, `src/`, `.vscode/`, `.editorconfig`,
`.oxfmtrc.json`, `.oxlintrc.json`, `.node-version`, `cspell.json`,
`lefthook.yml`, `commit-message-template`.
(Note: `dist/` is included via `package.json#files`, not by absence
from `.npmignore`.)
- **.oxfmtrc.json**: oxfmt configuration (Prettier-shaped).
- **.oxlintrc.json**: oxlint configuration.
- **.oxlintrc.json + .oxfmtrc.json** replace the old `.eslintrc.cjs`
and `.prettierrc`.
- **LICENSE**: MIT.
- **README.md**: Scaffolded.
- **commit-message-template**: From typescript-lib-starter-tiny.
- **Target Environments**: Node 26 LTS only (no browser target; this
is a pure Node library, no DOM, no DOM lib in tsconfig).
- **No React, No CJS, ESM only**.
## 4. Automation & Quality
- **Version Automation**: Use standard `npm version` for versioning.
- **Unused Dependency Check**: Use `check-outdated` (devDep, runs in CI
and via the explicit `lefthook run outdated` command).
- **No commitlint, no conventional commits**. Commits use gitmoji
prefixes (e.g. `:sparkles:`, `:wrench:`, `:bug:`, `:fire:`,
`:white_check_mark:`, `:tada:`) for at-a-glance categorization.
## 5. Scripts
### PREFIX CONVENTION
Script names use a prefix that signals _when_ the script is intended
to run. A `<prefix>:<name>` script is implicitly aggregated by a
`<prefix>` script (if one exists) and run by the corresponding
lefthook hook or CI step. Picking the right prefix documents the
script's intended lifecycle:
- `check:*` — read-only verification. Aggregated by `npm run check`
(which runs all `check:*` scripts in order). Called from the
lefthook pre-commit hook on staged files, and from the CI build
job on the full project. Read-only; never modifies files.
- `fix:*` — mutating counterpart of a `check:*` script. There is
**no** `npm run fix` aggregator by design: fixes should be
intentional, not batched. Run individually.
- `test:*` — test scripts. `npm run test` is the canonical entry
point (`check:tsc` + unit tests); `test:unit` skips the typecheck
for fast local iteration; `test:ci` adds c8 coverage and is the
CI variant.
- `publish:*` — runs only at publish time, in the CI `publish` job
(immediately before `npm publish`). There is **no** local
`npm run publish` script — publishing is CI-only by policy (see
§7). The `publish:` prefix still documents intent: this script
validates the _publishable artifact_ (e.g., `dist/`) rather than
the source.
A new script should pick the prefix that matches its lifecycle, not
invent a new one. If no existing prefix fits, that is a signal the
script does not belong in the standard pipeline.
### SETUP
- `use:git-commit-message`: Set up commit message template (if needed).
### TEST
- `test`: Run `check:tsc` then `node --test --strip-types "src/**/*.test.ts"`.
The glob is required because Node 26 does not auto-discover test
files in a bare directory argument (`node --test src/` is
interpreted as a module path on Node 26+).
- `test:unit`: Run unit tests with `node --test --strip-types "src/**/*.test.ts"`
(no preceding typecheck).
- `test:ci`: Run tests in CI mode with c8 coverage (text + lcov + html
reporters), uploading `coverage/` as an artifact.
### BUILD
- `build`: Build the project using `tsc -p tsconfig.build.json`
(emits `dist/*.js` + `dist/*.d.ts` + sourcemaps, with `.ts`
imports rewritten to `.js`).
### CLEAN
- `clean`: Remove `dist/` via `node -e "fs.rmSync('dist', {recursive:true, force:true})"`.
(Replaces `clean:build` from the original spec — same effect,
no `rimraf` dep needed.)
### CHECK
- `check`: Run all checks in order — `check:oxlint`, `check:oxfmt`,
`check:tsc`, `check:cspell`, `check:knip`, `check:outdated`.
- `check:oxlint`: `oxlint ${LEFTHOOK_FILES:-src}` — lints `src/` by
default; when invoked from the lefthook pre-commit hook with
`LEFTHOOK_FILES` set to the staged-files list, lints only those
files. This is the single source of truth for the oxlint command
and is shared between the manual `npm run check` and the pre-commit
hook. Type-aware rules are activated declaratively via
`options.typeAware: true` in `.oxlintrc.json` (not via a CLI flag),
so the script command stays clean. oxlint only understands JS/TS-
family languages, so config files (JSON/YAML/Markdown) are
intentionally outside its scope; they are checked only by oxfmt.
Source-level `oxlint-disable` directives are used to silence
type-aware false positives in the generic type machinery: 4× for
`typescript/no-unsafe-type-assertion` and 1× for
`typescript/no-unnecessary-type-parameters` in `src/pattern.ts` /
`src/match.ts` (legitimate type machinery that needs restructuring,
documented in the source); 1× file-level for
`typescript/no-floating-promises` in `src/index.test.ts`
(`expectTypeOf(...)` is a sync type-assertion library that
oxlint-tsgolint misidentifies).
- `check:oxfmt`: `oxfmt --check ${LEFTHOOK_FILES:-.}` — formats the
whole project (`.`) by default, including JS/TS, JSON/JSONC,
YAML, Markdown, MDX and other supported file types. From lefthook
pre-commit, the `LEFTHOOK_FILES` env var scopes to the staged
files matching `*.{ts,tsx,js,jsx,mjs,cjs,json,jsonc,yaml,yml,md,mdx}`.
Built-in `sortPackageJson: true` keeps `package.json` keys
alphabetized (replaces the former `sort-package-json` tool).
Built-in import sorting (enabled via `sortImports: true`) replaces
any external import-sort plugin.
- `check:tsc`: `tsc` (noEmit is set in `tsconfig.json`).
- `check:cspell`: `cspell lint ${LEFTHOOK_FILES:-.}` — walks the
project root by default; from lefthook, only the staged files.
- `check:outdated`: `check-outdated --ignore-pre-releases --ignore-packages @oxfmt/binding-*,@oxlint/binding-*`. The oxc native bindings are declared as `optionalDependencies` so the correct one is selected per platform automatically; the `*`-platform bindings show as "not installed" on the current platform and are explicitly ignored here.
- `check:knip`: `knip --include dependencies,exports,files` — finds
unused dependencies, value exports, and source files. The
scoped `--include` list intentionally omits the `types`
category, which produces systematic false positives for
libraries whose exported types (e.g. `Matcher`, `Pattern`) are
part of the public API and not consumed internally. The
targeted scope keeps the signal high (real unused-dep
detection) without config-file boilerplate.
### FIX
- `fix:oxlint`: `oxlint --fix src`.
- `fix:oxfmt`: `oxfmt ${LEFTHOOK_FILES:-.}` — same scoping as
`check:oxfmt` (whole project by default, staged files from
lefthook). Writes changes in place.
(There is no `fix` aggregator in the scripts; run the `fix:*` scripts
individually.)
### PUBLISH
- `publish:publint`: `publint` — runs the pack-and-lint flow against
the current project (uses `npm pack` to validate the actual
publishable artifact against `package.json`'s `files`, `exports`,
`main`, etc.). Requires a fresh `build` to have populated `dist/`.
Called by the CI `publish` job immediately before `npm publish`
(see §6). Not part of `npm run check` and not run on commit:
it validates publishing correctness, not source correctness.
- `publish:attw`: `attw . --pack --profile esm-only` — validates the
emitted `.d.ts` declarations against multiple TypeScript
module-resolution scenarios. Uses `--profile esm-only` because
this package is intentionally ESM-only (no CommonJS shim); CJS
resolution scenarios are explicitly out of scope by design, not
a bug. Called by the CI `publish` job alongside `publish:publint`
and `npm publish` (see §6). Not part of `npm run check` and not
run on commit: same rationale as `publish:publint`.
### HOOKS
- Lefthook runs the relevant `check:*` scripts on staged files in
parallel for pre-commit. See `lefthook.yml`.
---
## 6. Repository & CI/CD
- **Repository**: Hosted on GitHub.
- **Build Pipeline**: GitHub Actions (`.github/workflows/ci.yml`).
- `build` job on push and pull_request to `main` and on
`workflow_dispatch`. Steps: `actions/checkout@v4`,
`actions/setup-node@v4` (with `node-version-file: .node-version`,
`cache: npm`), `npm ci`, `npm run build`, `npm run check`,
`npm run test:ci`, then upload `coverage/` as an artifact.
- `publish` job: only on `refs/tags/*`, depends on `build`. Steps:
`actions/checkout@v4`, `actions/setup-node@v4` (with
`node-version-file: .node-version` and `registry-url`),
`npm ci`, `npm run build`, `npm run publish:publint` (see
§5 for why this is `publish:` and not `check:`),
`npm run publish:attw`, and `npm publish --access public`
with `NODE_AUTH_TOKEN` from secrets.
## 7. Versioning & Publishing
- **Version Update**: Use `npm version` to bump version after merging
to main and before publishing.
- **Publishing to npm**: Only publish from CI on tagged commits.
- **Recommended Workflow**:
1. Develop and merge PRs to main
2. Run all checks via CI
3. Bump version with `npm version <patch|minor|major>`
4. Push tag to GitHub
5. CI builds and publishes to npm on tag
## 8. NPM Keywords
- pattern-matching
- pattern
- match
- algebraic-data-types
- adt
- typescript
> The library is for pattern matching (not regex), similar to F#'s
> pattern matching, for TypeScript/ESM environments.
## 9. Code Coverage
- **Tool**: c8 (V8-native coverage, no instrumentation step).
- **Configuration**: c8 has no project config; the report shape is
pinned in the `test:ci` script:
```jsonc
"test:ci": "c8 --reporter=text --reporter=lcov --reporter=html node --test --strip-types src/"
```
- **Reporters**: text summary, HTML, and lcov (matching the original
spec).
- **CI**: `coverage/` is uploaded as a workflow artifact via
`actions/upload-artifact@v4` (see `.github/workflows/ci.yml`).
- **Optional**: Coverage thresholds can be added in c8 config when the
library surface stabilizes.
## 10. Source Structure & Tree Shaking
- **Source Directory**: All source code resides in `src/` and is
exported via `src/index.ts`.
- **Configuration**:
- `sideEffects: false` in `package.json` (set).
- ESM-only exports; `package.json#exports` field maps `.` to
`{"types": "./dist/index.d.ts", "import": "./dist/index.js"}`.
- Avoid top-level side effects in modules.
- Explicit re-exports in `index.ts` for best results.
- Source structure (current):
- `src/index.ts` — public barrel.
- `src/match.ts` — `match(value).with(...).exhaustive() / .otherwise(...)` builder.
- `src/pattern.ts` — `P.literal`, `P.type`, `P.when`, `P.any`, `P.shape` constructors and the `Matcher<T>` interface.
- `src/index.test.ts` — runtime + type-level tests using `node --test` + `expect-type`.
- The human will implement the source code.
## 11. Included Templates from typescript-lib-starter-tiny
### .editorconfig
```plaintext
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
max_line_length = 80
trim_trailing_whitespace = true
quote_type = double
[*.md]
max_line_length = 0
trim_trailing_whitespace = false
[COMMIT_EDITMSG]
max_line_length = 0
```
### commit-message-template
```plaintext
# If applied, this commit will... (Max 50 char)
# Explain why this change is being made (Max 72 Char) [WHAT and WHY vs HOW]
# Provide links or keys to any relevant tickets, articles or other resources
Resolves #...
# --- COMMIT END ---
# Remember to
# Use the imperative mood in the subject line
# Capitalize the subject line
# Do not end the subject line with a period
# Separate subject from body with a blank line
# Use the body to explain what and why vs. how
# Can use multiple lines with "-" for bullet points in body
```
### README.md Structure (scaffolded)
- Project title and description
- Development
- Build: `npm run build`
- Test: `npm run test`, `npm run test:ci`
- Checks: `npm run check`, `npm run fix:oxfmt`, `npm run fix:oxlint`
- Tooling section listing TypeScript 7, node --test, c8, oxlint, oxfmt,
cspell, lefthook
- Requirements: Node.js >= 26
- VSCode integration
- Debugging
- Running tests
- oxc.oxc-vscode provides oxlint and oxfmt in-editor
- Workflows
- Version updates via `npm version`
- Publishing via GitHub Actions on tagged commits
- Contribution guidelines
- Commit signing (GPG)
- How to set up commit message template (`npm run use:git-commit-message`)
- Reference to commit-message-template
- Type-level tests use `expect-type`'s `expectTypeOf(...)` inside
`node --test` cases
## 12. Project Initialization & Commit Strategy
- Start by initializing git with a `main` branch.
- Initial commit: empty README + LICENSE.
- Create a feature branch: `feature/setup`.
- For each technology or tool added (and its configuration), create a
separate commit:
- Prepend each commit message with a matching gitmoji (e.g.
`:sparkles:` for new features, `:wrench:` for config,
`:bug:` for fixes, `:fire:` for removals,
`:white_check_mark:` for tests, `:tada:` for initial commit).
- Example commit messages used in this project:
- `:tada: Initial commit with empty README`
- `:wrench: Track .vscode/settings.json for workspace settings`
- `:construction_worker: Added GitHub Actions workflow for CI/CD`
- `:test_tube: Added Vitest configuration with coverage` (later removed)
- `:sparkles: Scaffolded src/index.ts entry point for library code`
- `:wrench: Replace Vite/Vitest with TypeScript 7 and node --test`
- `:wrench: Replace ESLint and Prettier with oxlint and oxfmt`
- `:fire: Remove Vite scaffold leftovers`
- `:sparkles: Add initial pattern-matching API`
- `:white_check_mark: Add expect-type for type-level tests`
- `:wrench: Declare Node 26 as the supported runtime`
- `:bug: Use .ts extensions in imports for node --strip-types`
- `:wrench: Remove Prettier from editor formatter config`
- Each commit should include only the relevant files and configuration
for that technology/tool. This approach ensures a clean, understandable
project history and makes it easy to review or revert specific setup
steps.
## 13. Changelog Automation
- Not currently configured. The intended workflow, when adopted, is a
changesets-driven release process: feature PRs include a changeset,
which gets consumed by a release workflow, producing a `CHANGELOG.md`
and a version bump on merge to main.
- The changelog should be updated as part of the release process.
## 14. Publishing Public
- npm publishing is configured to be public by default.
- `publishConfig: { "access": "public" }` is set in `package.json`.
- The CI/CD pipeline publishes with `--access public` on tagged commits.
## 15. VSCode Integration
- `.vscode/settings.json` uses `oxc.oxc-vscode` as the default
formatter for `[typescript]`, `[javascript]`, `[json]`, `[jsonc]`,
`[markdown]`, `[mdx]`, and `[yaml]` (oxfmt under the hood).
```json
{
"[typescript]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[javascript]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[json]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[jsonc]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[markdown]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[mdx]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[yaml]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
}
```
- `.vscode/extensions.json` recommends:
- `oxc.oxc-vscode` (oxlint + oxfmt, replaces eslint/prettier/vitest)
- `streetsidesoftware.code-spell-checker` (cspell)
- `typescriptteam.native-preview` (TypeScript 7 nightly support;
replaces the older `ms-vscode.vscode-typescript-next`)
```json
{
"recommendations": [
"oxc.oxc-vscode",
"streetsidesoftware.code-spell-checker",
"typescriptteam.native-preview"
]
}
```
- `.vscode/tasks.json` is not currently provided; common tasks
(build, test, lint, typecheck, format, spell, check:outdated) are
run via the npm scripts in `package.json` from the integrated
terminal.
- VSCode uses TypeScript 7 via the `typescriptteam.native-preview`
extension, with `oxc.oxc-vscode` for formatting and linting.