@alexgorbatchev/typescript-ai-policy 1.0.2 → 1.0.4

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 CHANGED
@@ -149,6 +149,15 @@ conflicting keys.
149
149
  For Oxlint specifically, consumer configs are extension-only: if the callback tries to redefine any shared rule name,
150
150
  the factory throws with guidance to change the shared package instead of overriding that rule downstream.
151
151
 
152
+ When you run Oxlint manually, use Bun to launch the CLI:
153
+
154
+ ```bash
155
+ bun --bun oxlint .
156
+ ```
157
+
158
+ Treat `bun --bun oxlint ...` as the canonical invocation form for this package and for consumer repositories using
159
+ these TypeScript config entrypoints.
160
+
152
161
  ## Local package setup
153
162
 
154
163
  This package also uses its own shared configs at the repository root:
@@ -169,9 +178,10 @@ Repository-local development usage:
169
178
 
170
179
  - `bun run fix:semantic -- <target-directory>` — run the same semantic fixer from this repository checkout while developing the package itself.
171
180
 
172
- Today the framework applies two conservative semantic fixes:
181
+ Today the framework applies three conservative semantic fixes:
173
182
 
174
183
  - `@alexgorbatchev/interface-naming-convention` — rename repository-owned interfaces to their required `I*` form when the existing name can be normalized safely.
184
+ - `@alexgorbatchev/no-i-prefixed-type-aliases` — rename repository-owned type aliases to drop the interface-style `I*` prefix when the diagnostic resolves to a concrete type alias name safely.
175
185
  - `@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.
176
186
 
177
187
  The command and backend shape remain intentionally generic so more rule-backed semantic operations can be added later.
@@ -191,39 +201,40 @@ The command and backend shape remain intentionally generic so more rule-backed s
191
201
 
192
202
  For rule-by-rule rationale plus good/bad examples, see [`src/oxlint/README.md`](./src/oxlint/README.md).
193
203
 
194
- | Rule | Policy encoded |
195
- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
196
- | `@alexgorbatchev/testid-naming-convention` | Non-story React test ids must be scoped to the owning component as `ComponentName` or `ComponentName--thing`. |
197
- | `@alexgorbatchev/no-react-create-element` | Regular application code must use JSX instead of `createElement`. |
198
- | `@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`. |
199
- | `@alexgorbatchev/component-file-contract` | Component ownership files may export exactly one main runtime component plus unrestricted type-only API. |
200
- | `@alexgorbatchev/component-file-naming-convention` | Component filenames must match their exported PascalCase component name in either PascalCase or kebab-case form; non-component `.tsx` modules should be renamed to `.ts`. |
201
- | `@alexgorbatchev/component-story-file-convention` | Every component ownership file must have a matching `basename.stories.tsx` file somewhere under a sibling `stories/` directory. |
202
- | `@alexgorbatchev/story-file-location-convention` | Storybook files must live under sibling `stories/` directories and must still match a sibling component basename. |
203
- | `@alexgorbatchev/story-meta-type-annotation` | The default Storybook meta must use a typed `const meta: Meta<typeof ComponentName>` binding instead of object assertions. |
204
- | `@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. |
205
- | `@alexgorbatchev/hook-file-contract` | Hook ownership files may export exactly one main runtime hook and it must use `export function useThing() {}` form. |
206
- | `@alexgorbatchev/hook-file-naming-convention` | Hook filenames must match their exported hook name as either `useFoo.ts[x]` or `use-foo.ts[x]`. |
207
- | `@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. |
208
- | `@alexgorbatchev/no-non-running-tests` | Ban skip/todo/gated test modifiers that still leave non-running test code after the Jest rules run. |
209
- | `@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. |
210
- | `@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. |
211
- | `@alexgorbatchev/no-module-mocking` | Ban whole-module mocking APIs and push tests toward dependency injection plus explicit stubs. |
212
- | `@alexgorbatchev/no-test-file-exports` | Treat `*.test.ts(x)` files as execution units, not shared modules. |
213
- | `@alexgorbatchev/no-imports-from-tests-directory` | Files outside `__tests__/` must not import, require, or re-export modules from any `__tests__/` directory. |
214
- | `@alexgorbatchev/interface-naming-convention` | Repository-owned interfaces must use `I` followed by PascalCase; ambient external contract interfaces such as `Window` stay exempt. |
215
- | `@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. |
216
- | `@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. |
217
- | `@alexgorbatchev/index-file-contract` | `index.ts` must stay a pure barrel: no local definitions, no side effects, only re-exports, and never `index.tsx`. |
218
- | `@alexgorbatchev/no-type-imports-from-constants` | Types must not be imported from `constants` modules, including inline `import("./constants")` type queries. |
219
- | `@alexgorbatchev/no-type-exports-from-constants` | `constants.ts` files may export runtime values only; exported types must move to `types.ts`. |
220
- | `@alexgorbatchev/no-value-exports-from-types` | `types.ts` files may export type-only API only; runtime values and value re-exports must move elsewhere. |
221
- | `@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. |
222
- | `@alexgorbatchev/fixture-file-contract` | `__tests__/fixtures.ts(x)` and `stories/fixtures.ts(x)` may export only direct named `const` fixtures and named factory functions. |
223
- | `@alexgorbatchev/fixture-export-naming-convention` | Fixture entrypoint exports must use `fixture_<lowerCamelCase>` and `factory_<lowerCamelCase>`. |
224
- | `@alexgorbatchev/fixture-export-type-contract` | Fixture entrypoint exports must declare explicit imported concrete types and must not use `any` or `unknown`. |
225
- | `@alexgorbatchev/no-fixture-exports-outside-fixture-entrypoint` | `fixture_*` and `factory_*` exports may exist only in nested `fixtures.ts(x)` entrypoints under `__tests__/` or `stories/`. |
226
- | `@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. |
227
- | `@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. |
228
- | `@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. |
229
- | `@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/`. |
204
+ | Rule | Policy encoded |
205
+ | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
206
+ | `@alexgorbatchev/testid-naming-convention` | Non-story React test ids must be scoped to the owning component as `ComponentName` or `ComponentName--thing`. |
207
+ | `@alexgorbatchev/no-react-create-element` | Regular application code must use JSX instead of `createElement`. |
208
+ | `@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`. |
209
+ | `@alexgorbatchev/component-file-contract` | Component ownership files may export exactly one main runtime component plus unrestricted type-only API. |
210
+ | `@alexgorbatchev/component-file-naming-convention` | Component filenames must match their exported PascalCase component name in either PascalCase or kebab-case form; non-component `.tsx` modules should be renamed to `.ts`. |
211
+ | `@alexgorbatchev/component-story-file-convention` | Every component ownership file must have a matching `basename.stories.tsx` file somewhere under a sibling `stories/` directory. |
212
+ | `@alexgorbatchev/story-file-location-convention` | Storybook files must live under sibling `stories/` directories and must still match a sibling component basename. |
213
+ | `@alexgorbatchev/story-meta-type-annotation` | The default Storybook meta must use a typed `const meta: Meta<typeof ComponentName>` binding instead of object assertions. |
214
+ | `@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. |
215
+ | `@alexgorbatchev/hook-file-contract` | Hook ownership files may export exactly one main runtime hook and it must use `export function useThing() {}` form. |
216
+ | `@alexgorbatchev/hook-file-naming-convention` | Hook filenames must match their exported hook name as either `useFoo.ts[x]` or `use-foo.ts[x]`. |
217
+ | `@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. |
218
+ | `@alexgorbatchev/no-non-running-tests` | Ban skip/todo/gated test modifiers that still leave non-running test code after the Jest rules run. |
219
+ | `@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. |
220
+ | `@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. |
221
+ | `@alexgorbatchev/no-module-mocking` | Ban whole-module mocking APIs and push tests toward dependency injection plus explicit stubs. |
222
+ | `@alexgorbatchev/no-test-file-exports` | Treat `*.test.ts(x)` files as execution units, not shared modules. |
223
+ | `@alexgorbatchev/no-imports-from-tests-directory` | Files outside `__tests__/` must not import, require, or re-export modules from any `__tests__/` directory. |
224
+ | `@alexgorbatchev/interface-naming-convention` | Repository-owned interfaces must use `I` followed by PascalCase; ambient external contract interfaces such as `Window` stay exempt. |
225
+ | `@alexgorbatchev/no-i-prefixed-type-aliases` | Type aliases must not use the interface-style `I[A-Z]` prefix; this applies to `type` aliases only and does not change the separate interface naming contract. |
226
+ | `@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. |
227
+ | `@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; if indentation is significant, normalize the string explicitly with `@alexgorbatchev/dedent-string`. |
228
+ | `@alexgorbatchev/index-file-contract` | `index.ts` must stay a pure barrel: no local definitions, no side effects, only re-exports, and never `index.tsx`. |
229
+ | `@alexgorbatchev/no-type-imports-from-constants` | Types must not be imported from `constants` modules, including inline `import("./constants")` type queries. |
230
+ | `@alexgorbatchev/no-type-exports-from-constants` | `constants.ts` files may export runtime values only; exported types must move to `types.ts`. |
231
+ | `@alexgorbatchev/no-value-exports-from-types` | `types.ts` files may export type-only API only; runtime values and value re-exports must move elsewhere. |
232
+ | `@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. |
233
+ | `@alexgorbatchev/fixture-file-contract` | `__tests__/fixtures.ts(x)` and `stories/fixtures.ts(x)` may export only direct named `const` fixtures and named factory functions. |
234
+ | `@alexgorbatchev/fixture-export-naming-convention` | Fixture entrypoint exports must use `fixture_<lowerCamelCase>` and `factory_<lowerCamelCase>`. |
235
+ | `@alexgorbatchev/fixture-export-type-contract` | Fixture entrypoint exports must declare explicit imported concrete types and must not use `any` or `unknown`. |
236
+ | `@alexgorbatchev/no-fixture-exports-outside-fixture-entrypoint` | `fixture_*` and `factory_*` exports may exist only in nested `fixtures.ts(x)` entrypoints under `__tests__/` or `stories/`. |
237
+ | `@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. |
238
+ | `@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. |
239
+ | `@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. |
240
+ | `@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexgorbatchev/typescript-ai-policy",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Shared TypeScript AI policy configs and Oxlint rules",
5
5
  "homepage": "https://github.com/alexgorbatchev/typescript-ai-policy#readme",
6
6
  "repository": {
@@ -47,6 +47,7 @@ const DEFAULT_OXLINT_CONFIG = defineConfig({
47
47
  files: ["**/*.{ts,tsx,mts,cts}"],
48
48
  rules: {
49
49
  "@alexgorbatchev/interface-naming-convention": "error",
50
+ "@alexgorbatchev/no-i-prefixed-type-aliases": "error",
50
51
  "@alexgorbatchev/no-inline-type-expressions": "error",
51
52
  "@alexgorbatchev/require-template-indent": "error",
52
53
  },
@@ -13,6 +13,7 @@ import noTypeImportsFromConstantsRule from "./rules/no-type-imports-from-constan
13
13
  import noTypeExportsFromConstantsRule from "./rules/no-type-exports-from-constants.ts";
14
14
  import noValueExportsFromTypesRule from "./rules/no-value-exports-from-types.ts";
15
15
  import interfaceNamingConventionRule from "./rules/interface-naming-convention.ts";
16
+ import noIPrefixedTypeAliasesRule from "./rules/no-i-prefixed-type-aliases.ts";
16
17
  import noInlineTypeExpressionsRule from "./rules/no-inline-type-expressions.ts";
17
18
  import componentFileLocationConventionRule from "./rules/component-file-location-convention.ts";
18
19
  import componentDirectoryFileConventionRule from "./rules/component-directory-file-convention.ts";
@@ -59,6 +60,7 @@ const plugin = {
59
60
  "no-type-exports-from-constants": noTypeExportsFromConstantsRule,
60
61
  "no-value-exports-from-types": noValueExportsFromTypesRule,
61
62
  "interface-naming-convention": interfaceNamingConventionRule,
63
+ "no-i-prefixed-type-aliases": noIPrefixedTypeAliasesRule,
62
64
  "no-inline-type-expressions": noInlineTypeExpressionsRule,
63
65
  "component-file-location-convention": componentFileLocationConventionRule,
64
66
  "component-directory-file-convention": componentDirectoryFileConventionRule,
@@ -0,0 +1,45 @@
1
+ import type { RuleModule } from "./types.ts";
2
+
3
+ function readSuggestedTypeAliasName(typeAliasName: string): string {
4
+ return typeAliasName.slice(1);
5
+ }
6
+
7
+ function hasBlockedTypeAliasPrefix(typeAliasName: string): boolean {
8
+ return /^I[A-Z]/u.test(typeAliasName);
9
+ }
10
+
11
+ const noIPrefixedTypeAliasesRule: RuleModule = {
12
+ meta: {
13
+ type: "problem" as const,
14
+ docs: {
15
+ description:
16
+ 'Disallow repository-owned type alias names that start with an "I" followed by another capital letter',
17
+ },
18
+ schema: [],
19
+ messages: {
20
+ unexpectedTypeAliasName:
21
+ 'Rename type alias "{{ name }}" to remove the "I" prefix, for example "{{ suggestedName }}". Repository-owned type aliases must not use the interface-style "I[A-Z]" prefix.',
22
+ },
23
+ },
24
+ create(context) {
25
+ return {
26
+ TSTypeAliasDeclaration(node) {
27
+ const typeAliasName = node.id.name;
28
+ if (!hasBlockedTypeAliasPrefix(typeAliasName)) {
29
+ return;
30
+ }
31
+
32
+ context.report({
33
+ node: node.id,
34
+ messageId: "unexpectedTypeAliasName",
35
+ data: {
36
+ name: typeAliasName,
37
+ suggestedName: readSuggestedTypeAliasName(typeAliasName),
38
+ },
39
+ });
40
+ },
41
+ };
42
+ },
43
+ };
44
+
45
+ export default noIPrefixedTypeAliasesRule;
@@ -1,9 +1,13 @@
1
1
  import type { TSESTree } from "@typescript-eslint/types";
2
- import type { RuleModule } from "./types.ts";
2
+ import type { RuleFixer, RuleModule } from "./types.ts";
3
3
 
4
- function readIndentSize(line: string): number {
4
+ function readIndent(line: string): string {
5
5
  const indentMatch = line.match(/^[ \t]*/u);
6
- return indentMatch ? indentMatch[0].length : 0;
6
+ return indentMatch ? indentMatch[0] : "";
7
+ }
8
+
9
+ function readIndentSize(line: string): number {
10
+ return readIndent(line).length;
7
11
  }
8
12
 
9
13
  function readMinimumContentIndent(content: string): number {
@@ -21,8 +25,8 @@ function readMinimumContentIndent(content: string): number {
21
25
  return Number.isFinite(minimumIndent) ? minimumIndent : 0;
22
26
  }
23
27
 
24
- function readTemplateContent(node: TSESTree.TemplateLiteral): string {
25
- return node.quasis.map((quasi) => quasi.value.raw).join("${...}");
28
+ function readTemplateContent(sourceText: string, node: TSESTree.TemplateLiteral): string {
29
+ return sourceText.slice(node.range[0] + 1, node.range[1] - 1);
26
30
  }
27
31
 
28
32
  function startsWithNewline(templateContent: string): boolean {
@@ -33,6 +37,32 @@ function hasNonEmptyContent(templateContent: string): boolean {
33
37
  return templateContent.replace(/^\n/u, "").trim().length > 0;
34
38
  }
35
39
 
40
+ function readFixedTemplateContent(templateContent: string, indentPrefix: string): string {
41
+ const fixedContentLines = templateContent
42
+ .replace(/^\n/u, "")
43
+ .split("\n")
44
+ .map((contentLine) => {
45
+ if (contentLine.trim().length === 0) {
46
+ return contentLine;
47
+ }
48
+
49
+ return `${indentPrefix}${contentLine}`;
50
+ });
51
+
52
+ return `\n${fixedContentLines.join("\n")}`;
53
+ }
54
+
55
+ function readTemplateIndentFix(
56
+ fixer: RuleFixer,
57
+ node: TSESTree.TemplateLiteral,
58
+ templateContent: string,
59
+ indentPrefix: string,
60
+ ) {
61
+ const fixedTemplateContent = readFixedTemplateContent(templateContent, indentPrefix);
62
+
63
+ return fixer.replaceTextRange([node.range[0] + 1, node.range[1] - 1], fixedTemplateContent);
64
+ }
65
+
36
66
  const requireTemplateIndentRule: RuleModule = {
37
67
  meta: {
38
68
  type: "problem" as const,
@@ -40,17 +70,19 @@ const requireTemplateIndentRule: RuleModule = {
40
70
  description: "Require multiline template literals to keep their content indented with the surrounding code",
41
71
  },
42
72
  schema: [],
73
+ fixable: "code" as const,
43
74
  messages: {
44
75
  badIndent:
45
- "Indent this multiline template literal to match the surrounding code. If leading whitespace is part of the intended value, normalize the string explicitly instead of relying on under-indented source text.",
76
+ 'Indent this multiline template literal to match the surrounding code. If indentation is significant, normalize the string explicitly with "@alexgorbatchev/dedent-string" instead of relying on under-indented source text.',
46
77
  },
47
78
  },
48
79
  create(context) {
49
- const sourceLines = context.sourceCode.lines;
80
+ const sourceCode = context.getSourceCode?.() ?? context.sourceCode;
81
+ const sourceText = sourceCode.getText();
50
82
 
51
83
  return {
52
84
  TemplateLiteral(node) {
53
- const templateContent = readTemplateContent(node);
85
+ const templateContent = readTemplateContent(sourceText, node);
54
86
  if (!startsWithNewline(templateContent) || !hasNonEmptyContent(templateContent)) {
55
87
  return;
56
88
  }
@@ -60,7 +92,7 @@ const requireTemplateIndentRule: RuleModule = {
60
92
  return;
61
93
  }
62
94
 
63
- const sourceLine = sourceLines[startLine - 1];
95
+ const sourceLine = sourceCode.getLines()[startLine - 1];
64
96
  if (!sourceLine) {
65
97
  return;
66
98
  }
@@ -71,9 +103,14 @@ const requireTemplateIndentRule: RuleModule = {
71
103
  return;
72
104
  }
73
105
 
106
+ const indentPrefix = readIndent(sourceLine).slice(0, lineIndent - contentIndent);
107
+
74
108
  context.report({
75
109
  node,
76
110
  messageId: "badIndent",
111
+ fix(fixer) {
112
+ return readTemplateIndentFix(fixer, node, templateContent, indentPrefix);
113
+ },
77
114
  });
78
115
  },
79
116
  };
@@ -9,7 +9,7 @@ const testIdNamingConventionRule: RuleModule = {
9
9
  },
10
10
  messages: {
11
11
  invalidTestId:
12
- 'Rename {{ attributeName }} to "{{ componentName }}" on the component root, or to "{{ componentName }}--thing" on child elements. Received "{{ candidate }}".',
12
+ 'Rename {{ attributeName }} "{{ candidate }}" to "{{ componentName }}" on the component root, or to "{{ componentName }}--thing" on child elements.',
13
13
  },
14
14
  schema: [],
15
15
  fixable: "code" as const,
@@ -1,10 +1,10 @@
1
1
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import { applyTextEdits, readUpdatedContent } from "./applyTextEdits.ts";
4
- import type { IFileMove, ITextEdit } from "./types.ts";
4
+ import type { FileMove, TextEdit } from "./types.ts";
5
5
 
6
- function readTextEditsByFilePath(textEdits: readonly ITextEdit[]): Map<string, readonly ITextEdit[]> {
7
- const textEditsByFilePath = new Map<string, ITextEdit[]>();
6
+ function readTextEditsByFilePath(textEdits: readonly TextEdit[]): Map<string, readonly TextEdit[]> {
7
+ const textEditsByFilePath = new Map<string, TextEdit[]>();
8
8
 
9
9
  for (const textEdit of textEdits) {
10
10
  const existingTextEdits = textEditsByFilePath.get(textEdit.filePath);
@@ -19,7 +19,7 @@ function readTextEditsByFilePath(textEdits: readonly ITextEdit[]): Map<string, r
19
19
  return new Map(textEditsByFilePath);
20
20
  }
21
21
 
22
- function assertMoveFileOperationsAreSafe(fileMoves: readonly IFileMove[]): void {
22
+ function assertMoveFileOperationsAreSafe(fileMoves: readonly FileMove[]): void {
23
23
  const sourceFilePathSet = new Set<string>();
24
24
  const destinationFilePathSet = new Set<string>();
25
25
 
@@ -41,7 +41,7 @@ function assertMoveFileOperationsAreSafe(fileMoves: readonly IFileMove[]): void
41
41
  }
42
42
  }
43
43
 
44
- function applyMovedFile(fileMove: IFileMove, textEdits: readonly ITextEdit[]): void {
44
+ function applyMovedFile(fileMove: FileMove, textEdits: readonly TextEdit[]): void {
45
45
  if (!existsSync(fileMove.sourceFilePath)) {
46
46
  throw new Error(`Cannot move a missing file: ${fileMove.sourceFilePath}`);
47
47
  }
@@ -58,7 +58,7 @@ function applyMovedFile(fileMove: IFileMove, textEdits: readonly ITextEdit[]): v
58
58
  rmSync(fileMove.sourceFilePath);
59
59
  }
60
60
 
61
- export function applyFileChanges(textEdits: readonly ITextEdit[], fileMoves: readonly IFileMove[]): readonly string[] {
61
+ export function applyFileChanges(textEdits: readonly TextEdit[], fileMoves: readonly FileMove[]): readonly string[] {
62
62
  assertMoveFileOperationsAreSafe(fileMoves);
63
63
 
64
64
  const changedFilePathSet = new Set<string>();
@@ -2,19 +2,20 @@ import { dirname, isAbsolute, relative, resolve } from "node:path";
2
2
  import { applyFileChanges } from "./applyFileChanges.ts";
3
3
  import { createTsgoLspSemanticFixBackend } from "./backends/tsgo-lsp/createTsgoLspSemanticFixBackend.ts";
4
4
  import { createInterfaceNamingConventionSemanticFixProvider } from "./providers/createInterfaceNamingConventionSemanticFixProvider.ts";
5
+ import { createNoIPrefixedTypeAliasesSemanticFixProvider } from "./providers/createNoIPrefixedTypeAliasesSemanticFixProvider.ts";
5
6
  import { createTestFileLocationConventionSemanticFixProvider } from "./providers/createTestFileLocationConventionSemanticFixProvider.ts";
6
7
  import { runOxlintJson } from "./runOxlintJson.ts";
7
8
  import type {
8
- IApplySemanticFixesOptions,
9
- IApplySemanticFixesProgressEvent,
10
- IApplySemanticFixesResult,
11
- IOxlintDiagnostic,
12
- ISemanticFixOperation,
13
- ISemanticFixPlan,
14
- ISkippedDiagnostic,
9
+ ApplySemanticFixesOptions,
10
+ ApplySemanticFixesProgressEvent,
11
+ ApplySemanticFixesResult,
12
+ OxlintDiagnostic,
13
+ SemanticFixOperation,
14
+ SemanticFixPlan,
15
+ SkippedDiagnostic,
15
16
  } from "./types.ts";
16
17
 
17
- function readAbsoluteDiagnosticFilePath(targetDirectoryPath: string, diagnostic: IOxlintDiagnostic): string {
18
+ function readAbsoluteDiagnosticFilePath(targetDirectoryPath: string, diagnostic: OxlintDiagnostic): string {
18
19
  if (isAbsolute(diagnostic.filename)) {
19
20
  return diagnostic.filename;
20
21
  }
@@ -24,9 +25,9 @@ function readAbsoluteDiagnosticFilePath(targetDirectoryPath: string, diagnostic:
24
25
 
25
26
  function readSkippedDiagnostic(
26
27
  targetDirectoryPath: string,
27
- diagnostic: IOxlintDiagnostic,
28
+ diagnostic: OxlintDiagnostic,
28
29
  reason: string,
29
- ): ISkippedDiagnostic {
30
+ ): SkippedDiagnostic {
30
31
  return {
31
32
  filePath: readAbsoluteDiagnosticFilePath(targetDirectoryPath, diagnostic),
32
33
  reason,
@@ -34,14 +35,14 @@ function readSkippedDiagnostic(
34
35
  };
35
36
  }
36
37
 
37
- function readPlanSignature(plan: ISemanticFixPlan): string {
38
+ function readPlanSignature(plan: SemanticFixPlan): string {
38
39
  return JSON.stringify({
39
40
  fileMoves: plan.fileMoves,
40
41
  textEdits: plan.textEdits,
41
42
  });
42
43
  }
43
44
 
44
- function readChangedFilePaths(plans: readonly ISemanticFixPlan[]): readonly string[] {
45
+ function readChangedFilePaths(plans: readonly SemanticFixPlan[]): readonly string[] {
45
46
  const changedFilePathSet = new Set<string>();
46
47
  const movedFilePathMap = new Map<string, string>();
47
48
 
@@ -61,7 +62,7 @@ function readChangedFilePaths(plans: readonly ISemanticFixPlan[]): readonly stri
61
62
  return [...changedFilePathSet].sort((left, right) => left.localeCompare(right));
62
63
  }
63
64
 
64
- function readOperationDescription(operation: ISemanticFixOperation): string {
65
+ function readOperationDescription(operation: SemanticFixOperation): string {
65
66
  switch (operation.kind) {
66
67
  case "rename-symbol": {
67
68
  return `Rename ${operation.symbolName} to ${operation.newName}`;
@@ -72,7 +73,7 @@ function readOperationDescription(operation: ISemanticFixOperation): string {
72
73
  }
73
74
  }
74
75
 
75
- function readOperationEmptyPlanReason(operation: ISemanticFixOperation): string {
76
+ function readOperationEmptyPlanReason(operation: SemanticFixOperation): string {
76
77
  switch (operation.kind) {
77
78
  case "rename-symbol": {
78
79
  return `No text edits were produced for ${operation.symbolName}.`;
@@ -83,8 +84,8 @@ function readOperationEmptyPlanReason(operation: ISemanticFixOperation): string
83
84
  }
84
85
  }
85
86
 
86
- function readUniqueOperations(operations: readonly ISemanticFixOperation[]): readonly ISemanticFixOperation[] {
87
- const operationMap = new Map<string, ISemanticFixOperation>();
87
+ function readUniqueOperations(operations: readonly SemanticFixOperation[]): readonly SemanticFixOperation[] {
88
+ const operationMap = new Map<string, SemanticFixOperation>();
88
89
 
89
90
  for (const operation of operations) {
90
91
  operationMap.set(operation.id, operation);
@@ -93,8 +94,8 @@ function readUniqueOperations(operations: readonly ISemanticFixOperation[]): rea
93
94
  return [...operationMap.values()];
94
95
  }
95
96
 
96
- function readUniquePlans(plans: readonly ISemanticFixPlan[]): readonly ISemanticFixPlan[] {
97
- const planMap = new Map<string, ISemanticFixPlan>();
97
+ function readUniquePlans(plans: readonly SemanticFixPlan[]): readonly SemanticFixPlan[] {
98
+ const planMap = new Map<string, SemanticFixPlan>();
98
99
 
99
100
  for (const plan of plans) {
100
101
  planMap.set(readPlanSignature(plan), plan);
@@ -103,14 +104,16 @@ function readUniquePlans(plans: readonly ISemanticFixPlan[]): readonly ISemantic
103
104
  return [...planMap.values()];
104
105
  }
105
106
 
106
- export async function applySemanticFixes(options: IApplySemanticFixesOptions): Promise<IApplySemanticFixesResult> {
107
- const reportProgress = (event: IApplySemanticFixesProgressEvent): void => {
107
+ export async function applySemanticFixes(options: ApplySemanticFixesOptions): Promise<ApplySemanticFixesResult> {
108
+ const reportProgress = (event: ApplySemanticFixesProgressEvent): void => {
108
109
  options.onProgress?.(event);
109
110
  };
110
111
  const semanticFixProviders = new Map(
111
- [createInterfaceNamingConventionSemanticFixProvider(), createTestFileLocationConventionSemanticFixProvider()].map(
112
- (semanticFixProvider) => [semanticFixProvider.ruleCode, semanticFixProvider],
113
- ),
112
+ [
113
+ createInterfaceNamingConventionSemanticFixProvider(),
114
+ createNoIPrefixedTypeAliasesSemanticFixProvider(),
115
+ createTestFileLocationConventionSemanticFixProvider(),
116
+ ].map((semanticFixProvider) => [semanticFixProvider.ruleCode, semanticFixProvider]),
114
117
  );
115
118
  const semanticFixBackend = createTsgoLspSemanticFixBackend({
116
119
  tsgoExecutablePath: options.tsgoExecutablePath,
@@ -133,8 +136,8 @@ export async function applySemanticFixes(options: IApplySemanticFixesOptions): P
133
136
  kind: "collected-diagnostics",
134
137
  });
135
138
 
136
- const skippedDiagnostics: ISkippedDiagnostic[] = [];
137
- const operations: ISemanticFixOperation[] = [];
139
+ const skippedDiagnostics: SkippedDiagnostic[] = [];
140
+ const operations: SemanticFixOperation[] = [];
138
141
 
139
142
  for (const diagnostic of diagnostics) {
140
143
  const semanticFixProviderForDiagnostic = semanticFixProviders.get(diagnostic.code);
@@ -160,7 +163,7 @@ export async function applySemanticFixes(options: IApplySemanticFixesOptions): P
160
163
  }
161
164
 
162
165
  const uniqueOperations = readUniqueOperations(operations);
163
- const plans: ISemanticFixPlan[] = [];
166
+ const plans: SemanticFixPlan[] = [];
164
167
 
165
168
  reportProgress({
166
169
  kind: "planning-start",