@alexgorbatchev/typescript-ai-policy 0.1.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 +223 -0
- package/package.json +60 -0
- package/src/oxfmt/createOxfmtConfig.ts +26 -0
- package/src/oxlint/assertNoRuleCollisions.ts +40 -0
- package/src/oxlint/createOxlintConfig.ts +161 -0
- package/src/oxlint/oxlint.config.ts +3 -0
- package/src/oxlint/plugin.ts +90 -0
- package/src/oxlint/rules/component-directory-file-convention.ts +65 -0
- package/src/oxlint/rules/component-file-contract.ts +328 -0
- package/src/oxlint/rules/component-file-location-convention.ts +43 -0
- package/src/oxlint/rules/component-file-naming-convention.ts +260 -0
- package/src/oxlint/rules/component-story-file-convention.ts +108 -0
- package/src/oxlint/rules/fixture-export-naming-convention.ts +72 -0
- package/src/oxlint/rules/fixture-export-type-contract.ts +264 -0
- package/src/oxlint/rules/fixture-file-contract.ts +91 -0
- package/src/oxlint/rules/fixture-import-path-convention.ts +125 -0
- package/src/oxlint/rules/helpers.ts +544 -0
- package/src/oxlint/rules/hook-export-location-convention.ts +169 -0
- package/src/oxlint/rules/hook-file-contract.ts +179 -0
- package/src/oxlint/rules/hook-file-naming-convention.ts +151 -0
- package/src/oxlint/rules/hook-test-file-convention.ts +60 -0
- package/src/oxlint/rules/hooks-directory-file-convention.ts +75 -0
- package/src/oxlint/rules/index-file-contract.ts +177 -0
- package/src/oxlint/rules/interface-naming-convention.ts +72 -0
- package/src/oxlint/rules/no-conditional-logic-in-tests.ts +53 -0
- package/src/oxlint/rules/no-fixture-exports-outside-fixture-entrypoint.ts +68 -0
- package/src/oxlint/rules/no-imports-from-tests-directory.ts +114 -0
- package/src/oxlint/rules/no-inline-fixture-bindings-in-tests.ts +54 -0
- package/src/oxlint/rules/no-inline-type-expressions.ts +169 -0
- package/src/oxlint/rules/no-local-type-declarations-in-fixture-files.ts +55 -0
- package/src/oxlint/rules/no-module-mocking.ts +85 -0
- package/src/oxlint/rules/no-non-running-tests.ts +72 -0
- package/src/oxlint/rules/no-react-create-element.ts +59 -0
- package/src/oxlint/rules/no-test-file-exports.ts +52 -0
- package/src/oxlint/rules/no-throw-in-tests.ts +40 -0
- package/src/oxlint/rules/no-type-exports-from-constants.ts +97 -0
- package/src/oxlint/rules/no-type-imports-from-constants.ts +73 -0
- package/src/oxlint/rules/no-value-exports-from-types.ts +115 -0
- package/src/oxlint/rules/require-component-root-testid.ts +547 -0
- package/src/oxlint/rules/require-template-indent.ts +83 -0
- package/src/oxlint/rules/single-fixture-entrypoint.ts +142 -0
- package/src/oxlint/rules/stories-directory-file-convention.ts +55 -0
- package/src/oxlint/rules/story-export-contract.ts +343 -0
- package/src/oxlint/rules/story-file-location-convention.ts +64 -0
- package/src/oxlint/rules/story-meta-type-annotation.ts +129 -0
- package/src/oxlint/rules/test-file-location-convention.ts +115 -0
- package/src/oxlint/rules/testid-naming-convention.ts +63 -0
- package/src/oxlint/rules/tests-directory-file-convention.ts +55 -0
- package/src/oxlint/rules/types.ts +45 -0
- package/src/semantic-fixes/applyFileChanges.ts +81 -0
- package/src/semantic-fixes/applySemanticFixes.ts +239 -0
- package/src/semantic-fixes/applyTextEdits.ts +164 -0
- package/src/semantic-fixes/backends/tsgo-lsp/TsgoLspClient.ts +439 -0
- package/src/semantic-fixes/backends/tsgo-lsp/createTsgoLspSemanticFixBackend.ts +251 -0
- package/src/semantic-fixes/providers/createInterfaceNamingConventionSemanticFixProvider.ts +132 -0
- package/src/semantic-fixes/providers/createTestFileLocationConventionSemanticFixProvider.ts +52 -0
- package/src/semantic-fixes/readMovedFileTextEdits.ts +150 -0
- package/src/semantic-fixes/readSemanticFixRuntimePaths.ts +38 -0
- package/src/semantic-fixes/runApplySemanticFixes.ts +120 -0
- package/src/semantic-fixes/runOxlintJson.ts +139 -0
- package/src/semantic-fixes/types.ts +163 -0
- package/src/shared/mergeConfig.ts +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# @alexgorbatchev/typescript-ai-policy
|
|
2
|
+
|
|
3
|
+
This package exists to codify TypeScript coding standards through lint rules as aggressively as practical.
|
|
4
|
+
|
|
5
|
+
The goal is to replace as much prose-based LLM guidance as possible with strict, machine-enforced checks. The custom
|
|
6
|
+
`@alexgorbatchev/*` rules turn repository conventions into deterministic policies, and their error messages are written
|
|
7
|
+
as direct repair instructions so an agent can make the required change instead of interpreting vague prose.
|
|
8
|
+
|
|
9
|
+
**The benefit is twofold: guaranteed enforcement and reduced number of context instructions.**
|
|
10
|
+
|
|
11
|
+
The shared Oxlint policy is implemented in TypeScript-authored custom rule modules under `src/oxlint/`.
|
|
12
|
+
|
|
13
|
+
Upstream rules stay enabled as baseline correctness guardrails around that stricter policy layer.
|
|
14
|
+
|
|
15
|
+
These rules are designed to work as a **full policy set**, not as a grab bag of independent preferences. Disabling one
|
|
16
|
+
rule can weaken contracts enforced by other rules, because many of the rules deliberately compose into a larger workflow
|
|
17
|
+
for React structure, test layout, fixture ownership, and import discipline. Treat rule removal as a policy change, not
|
|
18
|
+
as a local lint tweak.
|
|
19
|
+
|
|
20
|
+
The fixture rules in particular form a **coupled contract** across:
|
|
21
|
+
|
|
22
|
+
- location
|
|
23
|
+
- allowed contents
|
|
24
|
+
- export shape
|
|
25
|
+
- naming
|
|
26
|
+
- typing
|
|
27
|
+
- import path
|
|
28
|
+
- single entrypoint
|
|
29
|
+
|
|
30
|
+
If one of those fixture rules is disabled, the rest of the fixture contract becomes less reliable as agent guidance.
|
|
31
|
+
|
|
32
|
+
## Stated policy goals
|
|
33
|
+
|
|
34
|
+
The policy pack exists to enforce consistency in generated and modified code by making the expected repository shape
|
|
35
|
+
machine-checkable.
|
|
36
|
+
|
|
37
|
+
At a high level, the enforced goals are:
|
|
38
|
+
|
|
39
|
+
- one runtime React component per ownership file
|
|
40
|
+
- one runtime hook per ownership file
|
|
41
|
+
- every component ownership file must have a matching Storybook file under a sibling `stories/` directory
|
|
42
|
+
- every exported story must be typed and include a `play` function
|
|
43
|
+
- every hook ownership file must have a matching test file under a sibling `__tests__/` directory
|
|
44
|
+
- non-story React components must expose deterministic test ids: the root id equals the component name, and child ids use `ComponentName--thing`
|
|
45
|
+
- tests and test fixtures must stay under `__tests__/`, story files must stay under `stories/`, and fixture exports must stay inside those role directories
|
|
46
|
+
- test-only code and fixture-only code must stay isolated from runtime modules
|
|
47
|
+
|
|
48
|
+
## Folder and filename contract
|
|
49
|
+
|
|
50
|
+
The shared policy now enforces **role directories**, not a single canonical feature tree.
|
|
51
|
+
|
|
52
|
+
That means component and hook ownership files may live in whatever parent folders a consumer repository chooses, but
|
|
53
|
+
some file roles still have hard placement requirements:
|
|
54
|
+
|
|
55
|
+
- Story files must live somewhere under a sibling `stories/` directory.
|
|
56
|
+
- Test files must live somewhere under a sibling `__tests__/` directory.
|
|
57
|
+
- Fixture entrypoints and fixture directories must live under `stories/` or `__tests__/`.
|
|
58
|
+
- Component filenames must still match their exported PascalCase component name in either PascalCase or kebab-case
|
|
59
|
+
form:
|
|
60
|
+
- `export function AccountPanel()` → `AccountPanel.tsx` or `account-panel.tsx`
|
|
61
|
+
- Hook filenames must still match their exported hook name in either camelCase or kebab-case `use*` form:
|
|
62
|
+
- `export function useAccountSettings()` → `useAccountSettings.ts` / `useAccountSettings.tsx` or
|
|
63
|
+
`use-account-settings.ts` / `use-account-settings.tsx`
|
|
64
|
+
- Every component ownership file must still have a matching `basename.stories.tsx` file under a sibling `stories/`
|
|
65
|
+
tree.
|
|
66
|
+
- Every hook ownership file must still have a matching `basename.test.ts[x]` file under a sibling `__tests__/` tree.
|
|
67
|
+
- Story files must still map back to a sibling component basename, default-export a typed `meta` binding, and export
|
|
68
|
+
typed stories whose objects include a `play` function.
|
|
69
|
+
- `index.ts` is reserved for pure barrel re-exports only. `index.tsx` is invalid.
|
|
70
|
+
- `constants.ts` is reserved for runtime values only.
|
|
71
|
+
- `types.ts` is reserved for type-only exports only.
|
|
72
|
+
|
|
73
|
+
A valid consumer layout can therefore look like any of these:
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
feature/
|
|
77
|
+
├── AccountPanel.tsx
|
|
78
|
+
├── stories/
|
|
79
|
+
│ ├── AccountPanel.stories.tsx
|
|
80
|
+
│ └── catalog/
|
|
81
|
+
│ └── AccountPanelAlt.stories.tsx
|
|
82
|
+
├── useAccount.ts
|
|
83
|
+
└── __tests__/
|
|
84
|
+
├── useAccount.test.ts
|
|
85
|
+
└── integration/
|
|
86
|
+
└── AccountPanel.test.tsx
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
feature/
|
|
91
|
+
├── ui/
|
|
92
|
+
│ ├── account-panel.tsx
|
|
93
|
+
│ └── stories/
|
|
94
|
+
│ └── catalog/
|
|
95
|
+
│ └── account-panel.stories.tsx
|
|
96
|
+
└── data/
|
|
97
|
+
├── use-account.ts
|
|
98
|
+
└── __tests__/
|
|
99
|
+
└── hooks/
|
|
100
|
+
└── use-account.test.ts
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
For the rule-by-rule rationale and examples behind this contract, see [`src/oxlint/README.md`](./src/oxlint/README.md).
|
|
104
|
+
|
|
105
|
+
## Install
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
bun add -d @alexgorbatchev/typescript-ai-policy oxfmt oxlint
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
If you want the semantic-fix CLI, install its tsgo backend too:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
bun add -d @typescript/native-preview
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Use
|
|
118
|
+
|
|
119
|
+
`oxfmt.config.ts`
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import createOxfmtConfig from "@alexgorbatchev/typescript-ai-policy/oxfmt-config";
|
|
123
|
+
|
|
124
|
+
export default createOxfmtConfig(() => ({
|
|
125
|
+
ignorePatterns: ["vendor/**"],
|
|
126
|
+
}));
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`oxlint.config.ts`
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import createOxlintConfig from "@alexgorbatchev/typescript-ai-policy/oxlint-config";
|
|
133
|
+
|
|
134
|
+
export default createOxlintConfig(() => ({
|
|
135
|
+
ignorePatterns: ["coverage"],
|
|
136
|
+
rules: {
|
|
137
|
+
"no-var": "error",
|
|
138
|
+
},
|
|
139
|
+
}));
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Both config entrypoints now export factory functions. The callback is optional, it must return a valid Oxfmt/Oxlint
|
|
143
|
+
config object, and that object is deep-merged **before** the shared defaults so the shared policy still wins on any
|
|
144
|
+
conflicting keys.
|
|
145
|
+
|
|
146
|
+
For Oxlint specifically, consumer configs are extension-only: if the callback tries to redefine any shared rule name,
|
|
147
|
+
the factory throws with guidance to change the shared package instead of overriding that rule downstream.
|
|
148
|
+
|
|
149
|
+
## Local package setup
|
|
150
|
+
|
|
151
|
+
This package also uses its own shared configs at the repository root:
|
|
152
|
+
|
|
153
|
+
- `oxfmt.config.ts`
|
|
154
|
+
- `oxlint.config.ts`
|
|
155
|
+
|
|
156
|
+
## Local semantic-fix tooling
|
|
157
|
+
|
|
158
|
+
The repository now includes a local semantic-fix command under `src/semantic-fixes/` that uses `tsgo --lsp --stdio` as its first real backend.
|
|
159
|
+
|
|
160
|
+
Useful commands:
|
|
161
|
+
|
|
162
|
+
- `bun run fix:semantic -- <target-directory>` — run Oxlint with this repository's policy config, collect supported diagnostics, and apply semantic fixes to the target directory.
|
|
163
|
+
- `bun run fix:semantic -- <target-directory> --dry-run` — print the planned semantic-fix scope without mutating files.
|
|
164
|
+
- `typescript-ai-policy-fix-semantic <target-directory>` — run the same semantic fixer through the package-installed bin after installing this package and `@typescript/native-preview`.
|
|
165
|
+
|
|
166
|
+
Today the framework applies two conservative semantic fixes:
|
|
167
|
+
|
|
168
|
+
- `@alexgorbatchev/interface-naming-convention` — rename repository-owned interfaces to their required `I*` form when the existing name can be normalized safely.
|
|
169
|
+
- `@alexgorbatchev/test-file-location-convention` — move misplaced `.test.ts` / `.test.tsx` files into a sibling `__tests__/` directory as `__tests__/basename.test.ts[x]` and rewrite the moved file's relative imports.
|
|
170
|
+
|
|
171
|
+
The command and backend shape remain intentionally generic so more rule-backed semantic operations can be added later.
|
|
172
|
+
|
|
173
|
+
## Enabled Oxlint error rules
|
|
174
|
+
|
|
175
|
+
### Built-in guardrails
|
|
176
|
+
|
|
177
|
+
| Rule | Policy encoded |
|
|
178
|
+
| ---------------------------- | ------------------------------------------------------------------------- |
|
|
179
|
+
| `eqeqeq` | Require `===` and `!==` so agents do not rely on coercion-based equality. |
|
|
180
|
+
| `typescript/no-explicit-any` | Ban explicit `any` so type contracts stay checkable and reviewable. |
|
|
181
|
+
| `jest/no-disabled-tests` | Block skipped or disabled tests in committed `*.test.ts(x)` files. |
|
|
182
|
+
| `jest/no-focused-tests` | Block focused tests so CI and local runs exercise the full suite. |
|
|
183
|
+
|
|
184
|
+
### Custom agentic workflow rules
|
|
185
|
+
|
|
186
|
+
For rule-by-rule rationale plus good/bad examples, see [`src/oxlint/README.md`](./src/oxlint/README.md).
|
|
187
|
+
|
|
188
|
+
| Rule | Policy encoded |
|
|
189
|
+
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
190
|
+
| `@alexgorbatchev/testid-naming-convention` | Non-story React test ids must be scoped to the owning component as `ComponentName` or `ComponentName--thing`. |
|
|
191
|
+
| `@alexgorbatchev/no-react-create-element` | Regular application code must use JSX instead of `createElement`. |
|
|
192
|
+
| `@alexgorbatchev/require-component-root-testid` | Non-story exported React components must render a DOM root with `data-testid`/`testId` exactly equal to the component name, and child ids must use `ComponentName--thing`. |
|
|
193
|
+
| `@alexgorbatchev/component-file-contract` | Component ownership files may export exactly one main runtime component plus unrestricted type-only API. |
|
|
194
|
+
| `@alexgorbatchev/component-file-naming-convention` | Component filenames must match their exported PascalCase component name in either PascalCase or kebab-case form. |
|
|
195
|
+
| `@alexgorbatchev/component-story-file-convention` | Every component ownership file must have a matching `basename.stories.tsx` file somewhere under a sibling `stories/` directory. |
|
|
196
|
+
| `@alexgorbatchev/story-file-location-convention` | Storybook files must live under sibling `stories/` directories and must still match a sibling component basename. |
|
|
197
|
+
| `@alexgorbatchev/story-meta-type-annotation` | The default Storybook meta must use a typed `const meta: Meta<typeof ComponentName>` binding instead of object assertions. |
|
|
198
|
+
| `@alexgorbatchev/story-export-contract` | Story exports must use typed `Story` bindings, every story must define `play`, and single-story vs multi-story export shapes are enforced. |
|
|
199
|
+
| `@alexgorbatchev/hook-file-contract` | Hook ownership files may export exactly one main runtime hook and it must use `export function useThing() {}` form. |
|
|
200
|
+
| `@alexgorbatchev/hook-file-naming-convention` | Hook filenames must match their exported hook name as either `useFoo.ts[x]` or `use-foo.ts[x]`. |
|
|
201
|
+
| `@alexgorbatchev/hook-test-file-convention` | Every hook ownership file must have a matching `__tests__/basename.test.ts[x]` file somewhere under a sibling `__tests__/` directory, with the same source extension family. |
|
|
202
|
+
| `@alexgorbatchev/no-non-running-tests` | Ban skip/todo/gated test modifiers that still leave non-running test code after the Jest rules run. |
|
|
203
|
+
| `@alexgorbatchev/no-conditional-logic-in-tests` | Ban `if`, `switch`, and ternary control flow in committed `*.test.ts(x)` files so assertions execute deterministically and test paths stay explicit. |
|
|
204
|
+
| `@alexgorbatchev/no-throw-in-tests` | Ban `throw new Error(...)` in committed `*.test.ts(x)` files so failures use explicit `assert(...)` / `assert.fail(...)` calls instead of ad-hoc exception paths. |
|
|
205
|
+
| `@alexgorbatchev/no-module-mocking` | Ban whole-module mocking APIs and push tests toward dependency injection plus explicit stubs. |
|
|
206
|
+
| `@alexgorbatchev/no-test-file-exports` | Treat `*.test.ts(x)` files as execution units, not shared modules. |
|
|
207
|
+
| `@alexgorbatchev/no-imports-from-tests-directory` | Files outside `__tests__/` must not import, require, or re-export modules from any `__tests__/` directory. |
|
|
208
|
+
| `@alexgorbatchev/interface-naming-convention` | Repository-owned interfaces must use `I` followed by PascalCase; ambient external contract interfaces such as `Window` stay exempt. |
|
|
209
|
+
| `@alexgorbatchev/no-inline-type-expressions` | Explicit type usage must rely on named declarations or inference; do not define object, tuple, function, broad union, intersection, mapped, or conditional types inline at use sites. Narrow `T \| null \| undefined` wrappers stay allowed. |
|
|
210
|
+
| `@alexgorbatchev/require-template-indent` | Multiline template literals that begin on their own line must keep their content indented with the surrounding code so embedded text stays reviewable and intentional. |
|
|
211
|
+
| `@alexgorbatchev/index-file-contract` | `index.ts` must stay a pure barrel: no local definitions, no side effects, only re-exports, and never `index.tsx`. |
|
|
212
|
+
| `@alexgorbatchev/no-type-imports-from-constants` | Types must not be imported from `constants` modules, including inline `import("./constants")` type queries. |
|
|
213
|
+
| `@alexgorbatchev/no-type-exports-from-constants` | `constants.ts` files may export runtime values only; exported types must move to `types.ts`. |
|
|
214
|
+
| `@alexgorbatchev/no-value-exports-from-types` | `types.ts` files may export type-only API only; runtime values and value re-exports must move elsewhere. |
|
|
215
|
+
| `@alexgorbatchev/test-file-location-convention` | Repository-owned `.test.ts` / `.test.tsx` files must live under `__tests__/` directories. `.spec.ts[x]` files are ignored by this rule. The semantic fixer moves misplaced `.test.ts[x]` files into a sibling `__tests__/` directory and rewrites their relative imports. |
|
|
216
|
+
| `@alexgorbatchev/fixture-file-contract` | `__tests__/fixtures.ts(x)` and `stories/fixtures.ts(x)` may export only direct named `const` fixtures and named factory functions. |
|
|
217
|
+
| `@alexgorbatchev/fixture-export-naming-convention` | Fixture entrypoint exports must use `fixture_<lowerCamelCase>` and `factory_<lowerCamelCase>`. |
|
|
218
|
+
| `@alexgorbatchev/fixture-export-type-contract` | Fixture entrypoint exports must declare explicit imported concrete types and must not use `any` or `unknown`. |
|
|
219
|
+
| `@alexgorbatchev/no-fixture-exports-outside-fixture-entrypoint` | `fixture_*` and `factory_*` exports may exist only in nested `fixtures.ts(x)` entrypoints under `__tests__/` or `stories/`. |
|
|
220
|
+
| `@alexgorbatchev/no-inline-fixture-bindings-in-tests` | Test and story files must import `fixture_*` and `factory_*` bindings from a relative `fixtures` module inside the same `__tests__/` or `stories/` tree instead of declaring them inline. |
|
|
221
|
+
| `@alexgorbatchev/fixture-import-path-convention` | Fixture-like imports inside test and story files must be named imports from a relative `fixtures` module inside the same `__tests__/` or `stories/` tree, with no aliasing. |
|
|
222
|
+
| `@alexgorbatchev/no-local-type-declarations-in-fixture-files` | Fixture files and `fixtures/` contents under `__tests__/` or `stories/` must import shared types instead of declaring local types, interfaces, or enums. |
|
|
223
|
+
| `@alexgorbatchev/single-fixture-entrypoint` | Each fixture-support directory under `__tests__/` or `stories/` must choose exactly one fixture entrypoint shape: `fixtures.ts`, `fixtures.tsx`, or `fixtures/`. |
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alexgorbatchev/typescript-ai-policy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared TypeScript AI policy configs and Oxlint rules",
|
|
5
|
+
"homepage": "https://github.com/alexgorbatchev/typescript-ai-policy#readme",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/alexgorbatchev/typescript-ai-policy.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/alexgorbatchev/typescript-ai-policy/issues"
|
|
12
|
+
},
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"files": [
|
|
18
|
+
"src/oxfmt/createOxfmtConfig.ts",
|
|
19
|
+
"src/oxlint/assertNoRuleCollisions.ts",
|
|
20
|
+
"src/oxlint/createOxlintConfig.ts",
|
|
21
|
+
"src/oxlint/oxlint.config.ts",
|
|
22
|
+
"src/oxlint/plugin.ts",
|
|
23
|
+
"src/oxlint/rules/*.ts",
|
|
24
|
+
"src/semantic-fixes/*.ts",
|
|
25
|
+
"src/semantic-fixes/backends/tsgo-lsp/*.ts",
|
|
26
|
+
"src/semantic-fixes/providers/*.ts",
|
|
27
|
+
"src/shared/mergeConfig.ts"
|
|
28
|
+
],
|
|
29
|
+
"bin": {
|
|
30
|
+
"typescript-ai-policy-fix-semantic": "./src/semantic-fixes/runApplySemanticFixes.ts"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
"./oxfmt-config": "./src/oxfmt/createOxfmtConfig.ts",
|
|
34
|
+
"./oxlint-config": "./src/oxlint/createOxlintConfig.ts",
|
|
35
|
+
"./oxlint-plugin": "./src/oxlint/plugin.ts"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@typescript/native-preview": "^7.0.0-dev.20260330.1",
|
|
39
|
+
"oxfmt": "^0.42.0",
|
|
40
|
+
"oxlint": "^1.57.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"@typescript/native-preview": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"check": "./scripts/check.sh",
|
|
49
|
+
"fix:semantic": "bun src/semantic-fixes/runApplySemanticFixes.ts",
|
|
50
|
+
"lint:target": "./scripts/lint-target.sh"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/bun": "latest",
|
|
54
|
+
"@typescript-eslint/rule-tester": "^8.58.0",
|
|
55
|
+
"@typescript/native-preview": "^7.0.0-dev.20260330.1",
|
|
56
|
+
"oxfmt": "^0.42.0",
|
|
57
|
+
"oxlint": "^1.57.0",
|
|
58
|
+
"typescript": "^6"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineConfig, type OxfmtConfig } from "oxfmt";
|
|
2
|
+
import { mergeConfig } from "../shared/mergeConfig.ts";
|
|
3
|
+
|
|
4
|
+
export type OxfmtConfigCallback = () => OxfmtConfig;
|
|
5
|
+
|
|
6
|
+
const DEFAULT_OXFMT_CONFIG = defineConfig({
|
|
7
|
+
printWidth: 120,
|
|
8
|
+
tabWidth: 2,
|
|
9
|
+
useTabs: false,
|
|
10
|
+
semi: true,
|
|
11
|
+
singleQuote: false,
|
|
12
|
+
quoteProps: "as-needed",
|
|
13
|
+
jsxSingleQuote: false,
|
|
14
|
+
trailingComma: "all",
|
|
15
|
+
bracketSpacing: true,
|
|
16
|
+
bracketSameLine: false,
|
|
17
|
+
arrowParens: "always",
|
|
18
|
+
endOfLine: "lf",
|
|
19
|
+
sortPackageJson: false,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export default function createOxfmtConfig(callback?: OxfmtConfigCallback): OxfmtConfig {
|
|
23
|
+
const userConfig = callback?.() ?? defineConfig({});
|
|
24
|
+
|
|
25
|
+
return defineConfig(mergeConfig(userConfig, DEFAULT_OXFMT_CONFIG));
|
|
26
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { OxlintConfig } from "oxlint";
|
|
2
|
+
|
|
3
|
+
function readRuleNames(config: OxlintConfig): Set<string> {
|
|
4
|
+
const ruleNames = new Set<string>();
|
|
5
|
+
|
|
6
|
+
if (config.rules) {
|
|
7
|
+
for (const ruleName of Object.keys(config.rules)) {
|
|
8
|
+
ruleNames.add(ruleName);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (config.overrides) {
|
|
13
|
+
for (const overrideConfig of config.overrides) {
|
|
14
|
+
if (!overrideConfig.rules) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (const ruleName of Object.keys(overrideConfig.rules)) {
|
|
19
|
+
ruleNames.add(ruleName);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return ruleNames;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function assertNoRuleCollisions(userConfig: OxlintConfig, defaultConfig: OxlintConfig): void {
|
|
28
|
+
const defaultRuleNames = readRuleNames(defaultConfig);
|
|
29
|
+
const conflictingRuleNames = [...readRuleNames(userConfig)].filter((ruleName) => defaultRuleNames.has(ruleName));
|
|
30
|
+
|
|
31
|
+
if (conflictingRuleNames.length === 0) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
conflictingRuleNames.sort();
|
|
36
|
+
|
|
37
|
+
throw new Error(
|
|
38
|
+
`User oxlint config must extend the shared policy instead of redefining existing rules. Remove these rule entries: ${conflictingRuleNames.join(", ")}. If you need to change a shared rule, update @alexgorbatchev/typescript-ai-policy itself instead of overriding it in a consumer config.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { defineConfig, type OxlintConfig } from "oxlint";
|
|
2
|
+
import { mergeConfig } from "../shared/mergeConfig.ts";
|
|
3
|
+
import { assertNoRuleCollisions } from "./assertNoRuleCollisions.ts";
|
|
4
|
+
|
|
5
|
+
export type OxlintConfigCallback = () => OxlintConfig;
|
|
6
|
+
|
|
7
|
+
//
|
|
8
|
+
// The rules must be optimized for performance:
|
|
9
|
+
// - global rules are reserved for true ingress/leak policies that must inspect
|
|
10
|
+
// arbitrary files
|
|
11
|
+
// - filename-addressable roles such as tests, stories, hooks, index barrels,
|
|
12
|
+
// constants files, and types files belong in narrow overrides when the path
|
|
13
|
+
// shape can identify them deterministically
|
|
14
|
+
//
|
|
15
|
+
const DEFAULT_OXLINT_CONFIG = defineConfig({
|
|
16
|
+
ignorePatterns: [
|
|
17
|
+
".cache",
|
|
18
|
+
".venv",
|
|
19
|
+
"**/.astro",
|
|
20
|
+
"**/.react-email",
|
|
21
|
+
"**/dist",
|
|
22
|
+
"**/node_modules",
|
|
23
|
+
"**/*.generated.ts",
|
|
24
|
+
"**/*.gen.ts",
|
|
25
|
+
"**/routeTree.gen.ts",
|
|
26
|
+
"**/.vitepress/cache",
|
|
27
|
+
"**/.vitepress/dist",
|
|
28
|
+
],
|
|
29
|
+
plugins: ["unicorn", "typescript", "oxc", "react", "jsx-a11y", "jest"],
|
|
30
|
+
jsPlugins: [
|
|
31
|
+
{
|
|
32
|
+
name: "@alexgorbatchev",
|
|
33
|
+
specifier: "@alexgorbatchev/typescript-ai-policy/oxlint-plugin",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
rules: {
|
|
37
|
+
eqeqeq: "error",
|
|
38
|
+
"@alexgorbatchev/no-react-create-element": "error",
|
|
39
|
+
"@alexgorbatchev/no-imports-from-tests-directory": "error",
|
|
40
|
+
"@alexgorbatchev/no-type-imports-from-constants": "error",
|
|
41
|
+
"@alexgorbatchev/test-file-location-convention": "error",
|
|
42
|
+
"@alexgorbatchev/no-fixture-exports-outside-fixture-entrypoint": "error",
|
|
43
|
+
"typescript/no-explicit-any": "error",
|
|
44
|
+
},
|
|
45
|
+
overrides: [
|
|
46
|
+
{
|
|
47
|
+
files: ["**/*.{ts,tsx,mts,cts}"],
|
|
48
|
+
rules: {
|
|
49
|
+
"@alexgorbatchev/interface-naming-convention": "error",
|
|
50
|
+
"@alexgorbatchev/no-inline-type-expressions": "error",
|
|
51
|
+
"@alexgorbatchev/require-template-indent": "error",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
files: ["**/*.tsx"],
|
|
56
|
+
rules: {
|
|
57
|
+
"@alexgorbatchev/testid-naming-convention": "error",
|
|
58
|
+
"@alexgorbatchev/require-component-root-testid": "error",
|
|
59
|
+
"@alexgorbatchev/component-file-contract": "error",
|
|
60
|
+
"@alexgorbatchev/component-file-naming-convention": "error",
|
|
61
|
+
"@alexgorbatchev/component-story-file-convention": "error",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
files: ["**/*.stories.tsx"],
|
|
66
|
+
rules: {
|
|
67
|
+
"@alexgorbatchev/testid-naming-convention": "off",
|
|
68
|
+
"@alexgorbatchev/require-component-root-testid": "off",
|
|
69
|
+
"@alexgorbatchev/story-file-location-convention": "error",
|
|
70
|
+
"@alexgorbatchev/story-meta-type-annotation": "error",
|
|
71
|
+
"@alexgorbatchev/story-export-contract": "error",
|
|
72
|
+
"@alexgorbatchev/no-inline-fixture-bindings-in-tests": "error",
|
|
73
|
+
"@alexgorbatchev/fixture-import-path-convention": "error",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
files: ["**/use[A-Z]*.ts", "**/use[A-Z]*.tsx", "**/use-*.ts", "**/use-*.tsx"],
|
|
78
|
+
rules: {
|
|
79
|
+
"@alexgorbatchev/hook-file-contract": "error",
|
|
80
|
+
"@alexgorbatchev/hook-file-naming-convention": "error",
|
|
81
|
+
"@alexgorbatchev/hook-test-file-convention": "error",
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
files: ["**/index.{ts,tsx}"],
|
|
86
|
+
rules: {
|
|
87
|
+
"@alexgorbatchev/index-file-contract": "error",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
files: ["**/constants.{ts,tsx,mts,cts}", "**/constants.d.{ts,tsx,mts,cts}"],
|
|
92
|
+
rules: {
|
|
93
|
+
"@alexgorbatchev/no-type-exports-from-constants": "error",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
files: ["**/types.{ts,tsx,mts,cts}", "**/types.d.{ts,tsx,mts,cts}"],
|
|
98
|
+
rules: {
|
|
99
|
+
"@alexgorbatchev/no-value-exports-from-types": "error",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
files: ["**/__tests__/**"],
|
|
104
|
+
rules: {
|
|
105
|
+
"@alexgorbatchev/no-module-mocking": "error",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
files: ["**/__tests__/*.test.{ts,tsx}", "**/__tests__/**/*.test.{ts,tsx}"],
|
|
110
|
+
rules: {
|
|
111
|
+
"@alexgorbatchev/testid-naming-convention": "off",
|
|
112
|
+
"@alexgorbatchev/require-component-root-testid": "off",
|
|
113
|
+
"@alexgorbatchev/no-non-running-tests": "error",
|
|
114
|
+
"@alexgorbatchev/no-conditional-logic-in-tests": "error",
|
|
115
|
+
"@alexgorbatchev/no-throw-in-tests": "error",
|
|
116
|
+
"@alexgorbatchev/no-test-file-exports": "error",
|
|
117
|
+
"@alexgorbatchev/no-inline-fixture-bindings-in-tests": "error",
|
|
118
|
+
"@alexgorbatchev/fixture-import-path-convention": "error",
|
|
119
|
+
"jest/no-disabled-tests": "error",
|
|
120
|
+
"jest/no-focused-tests": "error",
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
files: [
|
|
125
|
+
"**/__tests__/fixtures.{ts,tsx}",
|
|
126
|
+
"**/__tests__/**/fixtures.{ts,tsx}",
|
|
127
|
+
"**/stories/fixtures.{ts,tsx}",
|
|
128
|
+
"**/stories/**/fixtures.{ts,tsx}",
|
|
129
|
+
],
|
|
130
|
+
rules: {
|
|
131
|
+
"@alexgorbatchev/fixture-file-contract": "error",
|
|
132
|
+
"@alexgorbatchev/fixture-export-naming-convention": "error",
|
|
133
|
+
"@alexgorbatchev/fixture-export-type-contract": "error",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
files: [
|
|
138
|
+
"**/__tests__/fixtures.{ts,tsx}",
|
|
139
|
+
"**/__tests__/fixtures/**/*.{ts,tsx}",
|
|
140
|
+
"**/__tests__/**/fixtures.{ts,tsx}",
|
|
141
|
+
"**/__tests__/**/fixtures/**/*.{ts,tsx}",
|
|
142
|
+
"**/stories/fixtures.{ts,tsx}",
|
|
143
|
+
"**/stories/fixtures/**/*.{ts,tsx}",
|
|
144
|
+
"**/stories/**/fixtures.{ts,tsx}",
|
|
145
|
+
"**/stories/**/fixtures/**/*.{ts,tsx}",
|
|
146
|
+
],
|
|
147
|
+
rules: {
|
|
148
|
+
"@alexgorbatchev/no-local-type-declarations-in-fixture-files": "error",
|
|
149
|
+
"@alexgorbatchev/single-fixture-entrypoint": "error",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
export default function createOxlintConfig(callback?: OxlintConfigCallback): OxlintConfig {
|
|
156
|
+
const userConfig = callback?.() ?? defineConfig({});
|
|
157
|
+
|
|
158
|
+
assertNoRuleCollisions(userConfig, DEFAULT_OXLINT_CONFIG);
|
|
159
|
+
|
|
160
|
+
return defineConfig(mergeConfig(userConfig, DEFAULT_OXLINT_CONFIG));
|
|
161
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import testIdNamingConventionRule from "./rules/testid-naming-convention.ts";
|
|
2
|
+
import noReactCreateElementRule from "./rules/no-react-create-element.ts";
|
|
3
|
+
import requireComponentRootTestIdRule from "./rules/require-component-root-testid.ts";
|
|
4
|
+
import noNonRunningTestsRule from "./rules/no-non-running-tests.ts";
|
|
5
|
+
import noConditionalLogicInTestsRule from "./rules/no-conditional-logic-in-tests.ts";
|
|
6
|
+
import noThrowInTestsRule from "./rules/no-throw-in-tests.ts";
|
|
7
|
+
import requireTemplateIndentRule from "./rules/require-template-indent.ts";
|
|
8
|
+
import noModuleMockingRule from "./rules/no-module-mocking.ts";
|
|
9
|
+
import noTestFileExportsRule from "./rules/no-test-file-exports.ts";
|
|
10
|
+
import noImportsFromTestsDirectoryRule from "./rules/no-imports-from-tests-directory.ts";
|
|
11
|
+
import indexFileContractRule from "./rules/index-file-contract.ts";
|
|
12
|
+
import noTypeImportsFromConstantsRule from "./rules/no-type-imports-from-constants.ts";
|
|
13
|
+
import noTypeExportsFromConstantsRule from "./rules/no-type-exports-from-constants.ts";
|
|
14
|
+
import noValueExportsFromTypesRule from "./rules/no-value-exports-from-types.ts";
|
|
15
|
+
import interfaceNamingConventionRule from "./rules/interface-naming-convention.ts";
|
|
16
|
+
import noInlineTypeExpressionsRule from "./rules/no-inline-type-expressions.ts";
|
|
17
|
+
import componentFileLocationConventionRule from "./rules/component-file-location-convention.ts";
|
|
18
|
+
import componentDirectoryFileConventionRule from "./rules/component-directory-file-convention.ts";
|
|
19
|
+
import componentFileContractRule from "./rules/component-file-contract.ts";
|
|
20
|
+
import componentFileNamingConventionRule from "./rules/component-file-naming-convention.ts";
|
|
21
|
+
import componentStoryFileConventionRule from "./rules/component-story-file-convention.ts";
|
|
22
|
+
import storiesDirectoryFileConventionRule from "./rules/stories-directory-file-convention.ts";
|
|
23
|
+
import storyFileLocationConventionRule from "./rules/story-file-location-convention.ts";
|
|
24
|
+
import storyMetaTypeAnnotationRule from "./rules/story-meta-type-annotation.ts";
|
|
25
|
+
import storyExportContractRule from "./rules/story-export-contract.ts";
|
|
26
|
+
import hookExportLocationConventionRule from "./rules/hook-export-location-convention.ts";
|
|
27
|
+
import hooksDirectoryFileConventionRule from "./rules/hooks-directory-file-convention.ts";
|
|
28
|
+
import hookFileContractRule from "./rules/hook-file-contract.ts";
|
|
29
|
+
import hookFileNamingConventionRule from "./rules/hook-file-naming-convention.ts";
|
|
30
|
+
import hookTestFileConventionRule from "./rules/hook-test-file-convention.ts";
|
|
31
|
+
import testFileLocationConventionRule from "./rules/test-file-location-convention.ts";
|
|
32
|
+
import testsDirectoryFileConventionRule from "./rules/tests-directory-file-convention.ts";
|
|
33
|
+
import fixtureFileContractRule from "./rules/fixture-file-contract.ts";
|
|
34
|
+
import fixtureExportNamingConventionRule from "./rules/fixture-export-naming-convention.ts";
|
|
35
|
+
import fixtureExportTypeContractRule from "./rules/fixture-export-type-contract.ts";
|
|
36
|
+
import noFixtureExportsOutsideFixtureEntrypointRule from "./rules/no-fixture-exports-outside-fixture-entrypoint.ts";
|
|
37
|
+
import noInlineFixtureBindingsInTestsRule from "./rules/no-inline-fixture-bindings-in-tests.ts";
|
|
38
|
+
import fixtureImportPathConventionRule from "./rules/fixture-import-path-convention.ts";
|
|
39
|
+
import noLocalTypeDeclarationsInFixtureFilesRule from "./rules/no-local-type-declarations-in-fixture-files.ts";
|
|
40
|
+
import singleFixtureEntrypointRule from "./rules/single-fixture-entrypoint.ts";
|
|
41
|
+
|
|
42
|
+
const plugin = {
|
|
43
|
+
meta: {
|
|
44
|
+
name: "@alexgorbatchev",
|
|
45
|
+
},
|
|
46
|
+
rules: {
|
|
47
|
+
"testid-naming-convention": testIdNamingConventionRule,
|
|
48
|
+
"no-react-create-element": noReactCreateElementRule,
|
|
49
|
+
"require-component-root-testid": requireComponentRootTestIdRule,
|
|
50
|
+
"no-non-running-tests": noNonRunningTestsRule,
|
|
51
|
+
"no-conditional-logic-in-tests": noConditionalLogicInTestsRule,
|
|
52
|
+
"no-throw-in-tests": noThrowInTestsRule,
|
|
53
|
+
"require-template-indent": requireTemplateIndentRule,
|
|
54
|
+
"no-module-mocking": noModuleMockingRule,
|
|
55
|
+
"no-test-file-exports": noTestFileExportsRule,
|
|
56
|
+
"no-imports-from-tests-directory": noImportsFromTestsDirectoryRule,
|
|
57
|
+
"index-file-contract": indexFileContractRule,
|
|
58
|
+
"no-type-imports-from-constants": noTypeImportsFromConstantsRule,
|
|
59
|
+
"no-type-exports-from-constants": noTypeExportsFromConstantsRule,
|
|
60
|
+
"no-value-exports-from-types": noValueExportsFromTypesRule,
|
|
61
|
+
"interface-naming-convention": interfaceNamingConventionRule,
|
|
62
|
+
"no-inline-type-expressions": noInlineTypeExpressionsRule,
|
|
63
|
+
"component-file-location-convention": componentFileLocationConventionRule,
|
|
64
|
+
"component-directory-file-convention": componentDirectoryFileConventionRule,
|
|
65
|
+
"component-file-contract": componentFileContractRule,
|
|
66
|
+
"component-file-naming-convention": componentFileNamingConventionRule,
|
|
67
|
+
"component-story-file-convention": componentStoryFileConventionRule,
|
|
68
|
+
"stories-directory-file-convention": storiesDirectoryFileConventionRule,
|
|
69
|
+
"story-file-location-convention": storyFileLocationConventionRule,
|
|
70
|
+
"story-meta-type-annotation": storyMetaTypeAnnotationRule,
|
|
71
|
+
"story-export-contract": storyExportContractRule,
|
|
72
|
+
"hook-export-location-convention": hookExportLocationConventionRule,
|
|
73
|
+
"hooks-directory-file-convention": hooksDirectoryFileConventionRule,
|
|
74
|
+
"hook-file-contract": hookFileContractRule,
|
|
75
|
+
"hook-file-naming-convention": hookFileNamingConventionRule,
|
|
76
|
+
"hook-test-file-convention": hookTestFileConventionRule,
|
|
77
|
+
"test-file-location-convention": testFileLocationConventionRule,
|
|
78
|
+
"tests-directory-file-convention": testsDirectoryFileConventionRule,
|
|
79
|
+
"fixture-file-contract": fixtureFileContractRule,
|
|
80
|
+
"fixture-export-naming-convention": fixtureExportNamingConventionRule,
|
|
81
|
+
"fixture-export-type-contract": fixtureExportTypeContractRule,
|
|
82
|
+
"no-fixture-exports-outside-fixture-entrypoint": noFixtureExportsOutsideFixtureEntrypointRule,
|
|
83
|
+
"no-inline-fixture-bindings-in-tests": noInlineFixtureBindingsInTestsRule,
|
|
84
|
+
"fixture-import-path-convention": fixtureImportPathConventionRule,
|
|
85
|
+
"no-local-type-declarations-in-fixture-files": noLocalTypeDeclarationsInFixtureFilesRule,
|
|
86
|
+
"single-fixture-entrypoint": singleFixtureEntrypointRule,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export default plugin;
|