@alexgorbatchev/typescript-ai-policy 1.0.3 → 1.0.5
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 +12 -0
- package/package.json +1 -1
- package/src/oxlint/rules/component-directory-file-convention.ts +8 -2
- package/src/oxlint/rules/component-file-contract.ts +2 -1
- package/src/oxlint/rules/component-file-location-convention.ts +2 -2
- package/src/oxlint/rules/component-file-naming-convention.ts +2 -5
- package/src/oxlint/rules/component-story-file-convention.ts +4 -6
- package/src/oxlint/rules/helpers.ts +5 -0
- package/src/oxlint/rules/hook-file-contract.ts +14 -3
- package/src/oxlint/rules/hook-file-naming-convention.ts +54 -15
- package/src/oxlint/rules/hook-test-file-convention.ts +9 -7
- package/src/oxlint/rules/hooks-directory-file-convention.ts +2 -1
- package/src/oxlint/rules/index-file-contract.ts +1 -5
- package/src/oxlint/rules/single-fixture-entrypoint.ts +2 -2
- package/src/oxlint/rules/stories-directory-file-convention.ts +2 -2
- package/src/oxlint/rules/story-export-contract.ts +9 -3
- package/src/oxlint/rules/story-file-location-convention.ts +2 -6
- package/src/oxlint/rules/story-meta-type-annotation.ts +2 -2
- package/src/oxlint/rules/test-file-location-convention.ts +2 -8
- package/src/oxlint/rules/tests-directory-file-convention.ts +2 -2
package/README.md
CHANGED
|
@@ -10,6 +10,9 @@ as direct repair instructions so an agent can make the required change instead o
|
|
|
10
10
|
|
|
11
11
|
The shared Oxlint policy is implemented in TypeScript-authored custom rule modules under `src/oxlint/`.
|
|
12
12
|
|
|
13
|
+
File-level diagnostics are anchored to the first top-level syntax node when possible so editors highlight a concrete
|
|
14
|
+
repair location instead of painting the entire file.
|
|
15
|
+
|
|
13
16
|
Upstream rules stay enabled as baseline correctness guardrails around that stricter policy layer.
|
|
14
17
|
|
|
15
18
|
These rules are designed to work as a **full policy set**, not as a grab bag of independent preferences. Disabling one
|
|
@@ -149,6 +152,15 @@ conflicting keys.
|
|
|
149
152
|
For Oxlint specifically, consumer configs are extension-only: if the callback tries to redefine any shared rule name,
|
|
150
153
|
the factory throws with guidance to change the shared package instead of overriding that rule downstream.
|
|
151
154
|
|
|
155
|
+
When you run Oxlint manually, use Bun to launch the CLI:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
bun --bun oxlint .
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Treat `bun --bun oxlint ...` as the canonical invocation form for this package and for consumer repositories using
|
|
162
|
+
these TypeScript config entrypoints.
|
|
163
|
+
|
|
152
164
|
## Local package setup
|
|
153
165
|
|
|
154
166
|
This package also uses its own shared configs at the repository root:
|
package/package.json
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { RuleModule } from "./types.ts";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
getBaseName,
|
|
4
|
+
getExtension,
|
|
5
|
+
isExemptSupportBasename,
|
|
6
|
+
readPathFromFirstMatchingDirectory,
|
|
7
|
+
readProgramReportNode,
|
|
8
|
+
} from "./helpers.ts";
|
|
3
9
|
|
|
4
10
|
const COMPONENT_DIRECTORY_NAMES = new Set(["components", "templates", "layouts"]);
|
|
5
11
|
const COMPONENT_ALLOWED_SUPPORT_FILES = new Set(["constants.ts", "index.ts", "types.ts"]);
|
|
@@ -50,7 +56,7 @@ const componentDirectoryFileConventionRule: RuleModule = {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
context.report({
|
|
53
|
-
node,
|
|
59
|
+
node: readProgramReportNode(node),
|
|
54
60
|
messageId: "invalidComponentDirectoryFile",
|
|
55
61
|
data: {
|
|
56
62
|
directoryName: componentDirectoryMatch.directoryName,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isInTestsDirectory,
|
|
16
16
|
isTypeDeclaration,
|
|
17
17
|
readDeclarationIdentifierNames,
|
|
18
|
+
readProgramReportNode,
|
|
18
19
|
readMultipartComponentRootName,
|
|
19
20
|
readPatternIdentifierNames,
|
|
20
21
|
unwrapExpression,
|
|
@@ -286,7 +287,7 @@ const componentFileContractRule: RuleModule = {
|
|
|
286
287
|
const runtimeExportEntries = readRuntimeExportEntries(node);
|
|
287
288
|
if (runtimeExportEntries.length === 0) {
|
|
288
289
|
context.report({
|
|
289
|
-
node,
|
|
290
|
+
node: readProgramReportNode(node),
|
|
290
291
|
messageId: "missingMainComponentExport",
|
|
291
292
|
});
|
|
292
293
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RuleModule } from "./types.ts";
|
|
2
|
-
import { getExtension, hasPathSegment, isInTestsDirectory } from "./helpers.ts";
|
|
2
|
+
import { getExtension, hasPathSegment, isInTestsDirectory, readProgramReportNode } from "./helpers.ts";
|
|
3
3
|
|
|
4
4
|
const COMPONENT_DIRECTORY_NAMES = new Set(["components", "templates", "layouts"]);
|
|
5
5
|
|
|
@@ -32,7 +32,7 @@ const componentFileLocationConventionRule: RuleModule = {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
context.report({
|
|
35
|
-
node,
|
|
35
|
+
node: readProgramReportNode(node),
|
|
36
36
|
messageId: "unexpectedComponentFileLocation",
|
|
37
37
|
});
|
|
38
38
|
},
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
isPascalCase,
|
|
16
16
|
readDeclarationIdentifierNames,
|
|
17
17
|
readMultipartComponentRootName,
|
|
18
|
+
readProgramReportNode,
|
|
18
19
|
} from "./helpers.ts";
|
|
19
20
|
|
|
20
21
|
function isTypeOnlyExportSpecifier(
|
|
@@ -187,10 +188,6 @@ function readExpectedComponentNameFromFilename(filename: string): string | null
|
|
|
187
188
|
.join("");
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
function readInvalidFilenameReportNode(program: AstProgram): TSESTree.Node {
|
|
191
|
-
return program.body[0] ?? program;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
191
|
const componentFileNamingConventionRule: RuleModule = {
|
|
195
192
|
meta: {
|
|
196
193
|
type: "problem" as const,
|
|
@@ -228,7 +225,7 @@ const componentFileNamingConventionRule: RuleModule = {
|
|
|
228
225
|
const expectedComponentName = readExpectedComponentNameFromFilename(context.filename);
|
|
229
226
|
if (!expectedComponentName) {
|
|
230
227
|
context.report({
|
|
231
|
-
node:
|
|
228
|
+
node: readProgramReportNode(node),
|
|
232
229
|
messageId: "invalidComponentFileName",
|
|
233
230
|
});
|
|
234
231
|
return;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { AstProgram, AstProgramStatement, RuleModule } from "./types.ts";
|
|
1
|
+
import type { AstNode, AstProgram, RuleModule } from "./types.ts";
|
|
3
2
|
import { dirname, join } from "node:path";
|
|
4
3
|
import {
|
|
5
4
|
findDescendantFilePath,
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
isInStoriesDirectory,
|
|
9
8
|
isInTestsDirectory,
|
|
10
9
|
readAbbreviatedSiblingDirectoryPath,
|
|
10
|
+
readProgramReportNode,
|
|
11
11
|
} from "./helpers.ts";
|
|
12
12
|
|
|
13
13
|
function readRequiredStoriesDirectoryPath(filename: string): string {
|
|
@@ -20,9 +20,7 @@ function readRequiredStoryFileName(filename: string): string {
|
|
|
20
20
|
return `${sourceBaseName}.stories.tsx`;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
function readReportNode(program: AstProgram): ReportNode {
|
|
23
|
+
function readReportNode(program: AstProgram): AstNode {
|
|
26
24
|
for (const statement of program.body) {
|
|
27
25
|
if (statement.type === "ExportNamedDeclaration") {
|
|
28
26
|
if (statement.exportKind === "type") {
|
|
@@ -60,7 +58,7 @@ function readReportNode(program: AstProgram): ReportNode {
|
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
return program
|
|
61
|
+
return readProgramReportNode(program);
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
const componentStoryFileConventionRule: RuleModule = {
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
AstDestructuringPattern,
|
|
8
8
|
AstFunctionLike,
|
|
9
9
|
AstNode,
|
|
10
|
+
AstProgram,
|
|
10
11
|
AstTypeDeclaration,
|
|
11
12
|
} from "./types.ts";
|
|
12
13
|
|
|
@@ -84,6 +85,10 @@ export function readAbbreviatedSiblingDirectoryPath(filename: string, siblingDir
|
|
|
84
85
|
return readAbbreviatedPath(`${dirname(filename)}/${siblingDirectoryName}`);
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
export function readProgramReportNode(program: AstProgram): AstNode {
|
|
89
|
+
return program.body[0] ?? program;
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
export function readPathFromFirstMatchingDirectory(
|
|
88
93
|
filename: string,
|
|
89
94
|
expectedDirectoryNames: DirectoryNames,
|
|
@@ -7,7 +7,14 @@ import type {
|
|
|
7
7
|
AstProgramStatement,
|
|
8
8
|
RuleModule,
|
|
9
9
|
} from "./types.ts";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
isExemptSupportBasename,
|
|
12
|
+
isInStoriesDirectory,
|
|
13
|
+
isInTestsDirectory,
|
|
14
|
+
isTypeDeclaration,
|
|
15
|
+
readPatternIdentifierNames,
|
|
16
|
+
readProgramReportNode,
|
|
17
|
+
} from "./helpers.ts";
|
|
11
18
|
|
|
12
19
|
type HookRuntimeExportEntry = {
|
|
13
20
|
kind:
|
|
@@ -138,7 +145,11 @@ const hookFileContractRule: RuleModule = {
|
|
|
138
145
|
},
|
|
139
146
|
},
|
|
140
147
|
create(context) {
|
|
141
|
-
if (
|
|
148
|
+
if (
|
|
149
|
+
isExemptSupportBasename(context.filename) ||
|
|
150
|
+
isInStoriesDirectory(context.filename) ||
|
|
151
|
+
isInTestsDirectory(context.filename)
|
|
152
|
+
) {
|
|
142
153
|
return {};
|
|
143
154
|
}
|
|
144
155
|
|
|
@@ -147,7 +158,7 @@ const hookFileContractRule: RuleModule = {
|
|
|
147
158
|
const runtimeExportEntries = readRuntimeExportEntries(node);
|
|
148
159
|
if (runtimeExportEntries.length === 0) {
|
|
149
160
|
context.report({
|
|
150
|
-
node,
|
|
161
|
+
node: readProgramReportNode(node),
|
|
151
162
|
messageId: "missingMainHookExport",
|
|
152
163
|
});
|
|
153
164
|
return;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AstExportNamedDeclaration,
|
|
3
3
|
AstExportSpecifier,
|
|
4
|
+
AstNode,
|
|
4
5
|
AstProgram,
|
|
5
6
|
AstProgramStatement,
|
|
6
7
|
RuleModule,
|
|
7
8
|
} from "./types.ts";
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getFilenameWithoutExtension,
|
|
11
|
+
isExemptSupportBasename,
|
|
12
|
+
isInStoriesDirectory,
|
|
13
|
+
isInTestsDirectory,
|
|
14
|
+
readDeclarationIdentifierNames,
|
|
15
|
+
readProgramReportNode,
|
|
16
|
+
} from "./helpers.ts";
|
|
9
17
|
|
|
10
18
|
function isTypeOnlyExportSpecifier(
|
|
11
19
|
specifier: AstExportSpecifier,
|
|
@@ -22,18 +30,23 @@ function readExportedSpecifierName(specifier: AstExportSpecifier): string {
|
|
|
22
30
|
return String(specifier.exported.value);
|
|
23
31
|
}
|
|
24
32
|
|
|
25
|
-
|
|
33
|
+
type HookRuntimeExportEntry = {
|
|
34
|
+
name: string;
|
|
35
|
+
reportNode: AstNode;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
function readFirstRuntimeExportEntry(program: AstProgram): HookRuntimeExportEntry | null {
|
|
26
39
|
for (const statement of program.body) {
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
return
|
|
40
|
+
const exportEntry = readStatementRuntimeExportEntry(statement);
|
|
41
|
+
if (exportEntry !== null) {
|
|
42
|
+
return exportEntry;
|
|
30
43
|
}
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
return null;
|
|
34
47
|
}
|
|
35
48
|
|
|
36
|
-
function
|
|
49
|
+
function readStatementRuntimeExportEntry(statement: AstProgramStatement): HookRuntimeExportEntry | null {
|
|
37
50
|
if (statement.type !== "ExportNamedDeclaration") {
|
|
38
51
|
return null;
|
|
39
52
|
}
|
|
@@ -50,10 +63,28 @@ function readStatementRuntimeExportName(statement: AstProgramStatement): string
|
|
|
50
63
|
|
|
51
64
|
if (statement.declaration.type === "VariableDeclaration") {
|
|
52
65
|
const firstDeclarator = statement.declaration.declarations[0];
|
|
53
|
-
|
|
66
|
+
if (!firstDeclarator || firstDeclarator.id.type !== "Identifier") {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
name: firstDeclarator.id.name,
|
|
72
|
+
reportNode: firstDeclarator.id,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const declarationName = readDeclarationIdentifierNames(statement.declaration)[0];
|
|
77
|
+
if (!declarationName) {
|
|
78
|
+
return null;
|
|
54
79
|
}
|
|
55
80
|
|
|
56
|
-
|
|
81
|
+
const reportNode =
|
|
82
|
+
"id" in statement.declaration && statement.declaration.id ? statement.declaration.id : statement.declaration;
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
name: declarationName,
|
|
86
|
+
reportNode,
|
|
87
|
+
};
|
|
57
88
|
}
|
|
58
89
|
|
|
59
90
|
const runtimeSpecifier = statement.specifiers.find((specifier) => !isTypeOnlyExportSpecifier(specifier, statement));
|
|
@@ -61,7 +92,10 @@ function readStatementRuntimeExportName(statement: AstProgramStatement): string
|
|
|
61
92
|
return null;
|
|
62
93
|
}
|
|
63
94
|
|
|
64
|
-
return
|
|
95
|
+
return {
|
|
96
|
+
name: readExportedSpecifierName(runtimeSpecifier),
|
|
97
|
+
reportNode: runtimeSpecifier.exported.type === "Identifier" ? runtimeSpecifier.exported : runtimeSpecifier,
|
|
98
|
+
};
|
|
65
99
|
}
|
|
66
100
|
|
|
67
101
|
function readExpectedHookNameFromFilename(filename: string): string | null {
|
|
@@ -101,21 +135,26 @@ const hookFileNamingConventionRule: RuleModule = {
|
|
|
101
135
|
},
|
|
102
136
|
},
|
|
103
137
|
create(context) {
|
|
104
|
-
if (
|
|
138
|
+
if (
|
|
139
|
+
isExemptSupportBasename(context.filename) ||
|
|
140
|
+
isInStoriesDirectory(context.filename) ||
|
|
141
|
+
isInTestsDirectory(context.filename)
|
|
142
|
+
) {
|
|
105
143
|
return {};
|
|
106
144
|
}
|
|
107
145
|
|
|
108
146
|
return {
|
|
109
147
|
Program(node) {
|
|
110
|
-
const
|
|
111
|
-
if (!
|
|
148
|
+
const exportedHookEntry = readFirstRuntimeExportEntry(node);
|
|
149
|
+
if (!exportedHookEntry) {
|
|
112
150
|
return;
|
|
113
151
|
}
|
|
114
152
|
|
|
153
|
+
const { name: exportedHookName, reportNode } = exportedHookEntry;
|
|
115
154
|
const expectedHookName = readExpectedHookNameFromFilename(context.filename);
|
|
116
155
|
if (!expectedHookName) {
|
|
117
156
|
context.report({
|
|
118
|
-
node,
|
|
157
|
+
node: readProgramReportNode(node),
|
|
119
158
|
messageId: "invalidHookFileName",
|
|
120
159
|
});
|
|
121
160
|
return;
|
|
@@ -123,7 +162,7 @@ const hookFileNamingConventionRule: RuleModule = {
|
|
|
123
162
|
|
|
124
163
|
if (!/^use[A-Z][A-Za-z0-9]*$/u.test(exportedHookName)) {
|
|
125
164
|
context.report({
|
|
126
|
-
node,
|
|
165
|
+
node: reportNode,
|
|
127
166
|
messageId: "invalidHookExportName",
|
|
128
167
|
});
|
|
129
168
|
}
|
|
@@ -135,7 +174,7 @@ const hookFileNamingConventionRule: RuleModule = {
|
|
|
135
174
|
const extension = context.filename.endsWith(".tsx") ? ".tsx" : ".ts";
|
|
136
175
|
|
|
137
176
|
context.report({
|
|
138
|
-
node,
|
|
177
|
+
node: reportNode,
|
|
139
178
|
messageId: "mismatchedHookFileName",
|
|
140
179
|
data: {
|
|
141
180
|
exportedName: exportedHookName,
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { AstProgram, RuleModule } from "./types.ts";
|
|
2
|
-
import type { TSESTree } from "@typescript-eslint/types";
|
|
3
2
|
import { dirname, join } from "node:path";
|
|
4
3
|
import {
|
|
5
4
|
findDescendantFilePath,
|
|
6
5
|
getBaseName,
|
|
7
6
|
getFilenameWithoutExtension,
|
|
8
7
|
isExemptSupportBasename,
|
|
8
|
+
isInStoriesDirectory,
|
|
9
|
+
isInTestsDirectory,
|
|
9
10
|
readAbbreviatedSiblingDirectoryPath,
|
|
11
|
+
readProgramReportNode,
|
|
10
12
|
} from "./helpers.ts";
|
|
11
13
|
|
|
12
14
|
function readRequiredTestsDirectoryPath(filename: string): string {
|
|
@@ -20,10 +22,6 @@ function readRequiredHookTestFileName(filename: string): string {
|
|
|
20
22
|
return `${sourceBaseName}${testExtension}`;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
function readReportNode(program: AstProgram): TSESTree.Node {
|
|
24
|
-
return program.body[0] ?? program;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
25
|
const hookTestFileConventionRule: RuleModule = {
|
|
28
26
|
meta: {
|
|
29
27
|
type: "problem" as const,
|
|
@@ -38,7 +36,11 @@ const hookTestFileConventionRule: RuleModule = {
|
|
|
38
36
|
},
|
|
39
37
|
},
|
|
40
38
|
create(context) {
|
|
41
|
-
if (
|
|
39
|
+
if (
|
|
40
|
+
isExemptSupportBasename(context.filename) ||
|
|
41
|
+
isInStoriesDirectory(context.filename) ||
|
|
42
|
+
isInTestsDirectory(context.filename)
|
|
43
|
+
) {
|
|
42
44
|
return {};
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -52,7 +54,7 @@ const hookTestFileConventionRule: RuleModule = {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
context.report({
|
|
55
|
-
node:
|
|
57
|
+
node: readProgramReportNode(node),
|
|
56
58
|
messageId: "missingHookTestFile",
|
|
57
59
|
data: {
|
|
58
60
|
requiredTestFileName,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getFilenameWithoutExtension,
|
|
5
5
|
isStrictAreaAllowedSupportFile,
|
|
6
6
|
readPathFromDirectory,
|
|
7
|
+
readProgramReportNode,
|
|
7
8
|
} from "./helpers.ts";
|
|
8
9
|
|
|
9
10
|
function isAllowedHookOwnershipBasename(filename: string): boolean {
|
|
@@ -61,7 +62,7 @@ const hooksDirectoryFileConventionRule: RuleModule = {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
context.report({
|
|
64
|
-
node,
|
|
65
|
+
node: readProgramReportNode(node),
|
|
65
66
|
messageId: "invalidHooksDirectoryFile",
|
|
66
67
|
data: {
|
|
67
68
|
relativePath: relativePath || ".",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TSESTree } from "@typescript-eslint/types";
|
|
2
2
|
import type { AstProgramStatement, RuleModule } from "./types.ts";
|
|
3
|
-
import { getBaseName } from "./helpers.ts";
|
|
3
|
+
import { getBaseName, readProgramReportNode } from "./helpers.ts";
|
|
4
4
|
|
|
5
5
|
type IndexMessageId = "unexpectedIndexExport" | "unexpectedIndexStatement";
|
|
6
6
|
|
|
@@ -86,10 +86,6 @@ function readExportDefaultReportNode(node: TSESTree.ExportDefaultDeclaration): T
|
|
|
86
86
|
return node;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
function readProgramReportNode(node: TSESTree.Program): TSESTree.Node {
|
|
90
|
-
return node.body[0] ?? node;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
89
|
function readIndexViolationReportNode(statement: AstProgramStatement): TSESTree.Node {
|
|
94
90
|
if (statement.type === "ExportNamedDeclaration") {
|
|
95
91
|
if (statement.declaration) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RuleModule } from "./types.ts";
|
|
2
2
|
import { existsSync, statSync } from "node:fs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { readPathFromStoriesDirectory, readPathFromTestsDirectory } from "./helpers.ts";
|
|
4
|
+
import { readPathFromStoriesDirectory, readPathFromTestsDirectory, readProgramReportNode } from "./helpers.ts";
|
|
5
5
|
|
|
6
6
|
const FIXTURE_ENTRYPOINT_CANDIDATE_KEYS = ["fixtures.ts", "fixtures.tsx", "fixtures/"];
|
|
7
7
|
|
|
@@ -127,7 +127,7 @@ const singleFixtureEntrypointRule: RuleModule = {
|
|
|
127
127
|
return {
|
|
128
128
|
Program(node) {
|
|
129
129
|
context.report({
|
|
130
|
-
node,
|
|
130
|
+
node: readProgramReportNode(node),
|
|
131
131
|
messageId: "conflictingFixtureEntrypoints",
|
|
132
132
|
data: {
|
|
133
133
|
directoryLabel,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RuleModule } from "./types.ts";
|
|
2
|
-
import { isInStoriesDirectory, readPathFromStoriesDirectory } from "./helpers.ts";
|
|
2
|
+
import { isInStoriesDirectory, readPathFromStoriesDirectory, readProgramReportNode } from "./helpers.ts";
|
|
3
3
|
|
|
4
4
|
const ALLOWED_ROOT_STORY_FILES_PATTERN = /^[^/]+\.stories\.tsx$/u;
|
|
5
5
|
const ALLOWED_SUPPORT_FILES = new Set(["fixtures.ts", "fixtures.tsx", "helpers.ts", "helpers.tsx"]);
|
|
@@ -41,7 +41,7 @@ const storiesDirectoryFileConventionRule: RuleModule = {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
context.report({
|
|
44
|
-
node,
|
|
44
|
+
node: readProgramReportNode(node),
|
|
45
45
|
messageId: "invalidStoriesDirectoryFile",
|
|
46
46
|
data: {
|
|
47
47
|
relativePath,
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { TSESTree } from "@typescript-eslint/types";
|
|
2
2
|
import type { AstProgram, RuleModule } from "./types.ts";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getStorySourceBaseName,
|
|
5
|
+
isPascalCase,
|
|
6
|
+
readProgramReportNode,
|
|
7
|
+
unwrapExpression,
|
|
8
|
+
unwrapTypeScriptExpression,
|
|
9
|
+
} from "./helpers.ts";
|
|
4
10
|
|
|
5
11
|
type StoryExportRecord = {
|
|
6
12
|
exportedName: string;
|
|
@@ -245,7 +251,7 @@ const storyExportContractRule: RuleModule = {
|
|
|
245
251
|
|
|
246
252
|
if (storyEntries.length === 0) {
|
|
247
253
|
context.report({
|
|
248
|
-
node,
|
|
254
|
+
node: readProgramReportNode(node),
|
|
249
255
|
messageId: "missingStoryExport",
|
|
250
256
|
});
|
|
251
257
|
return;
|
|
@@ -285,7 +291,7 @@ const storyExportContractRule: RuleModule = {
|
|
|
285
291
|
const exportedStoryEntries = storyEntries.filter((storyEntry) => storyEntry.exports.length > 0);
|
|
286
292
|
if (exportedStoryEntries.length === 0) {
|
|
287
293
|
context.report({
|
|
288
|
-
node,
|
|
294
|
+
node: readProgramReportNode(node),
|
|
289
295
|
messageId: "missingStoryExport",
|
|
290
296
|
});
|
|
291
297
|
return;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { TSESTree } from "@typescript-eslint/types";
|
|
2
1
|
import type { AstProgram, RuleModule } from "./types.ts";
|
|
3
2
|
import { existsSync } from "node:fs";
|
|
4
3
|
import { join } from "node:path";
|
|
@@ -6,6 +5,7 @@ import {
|
|
|
6
5
|
getStorySourceBaseName,
|
|
7
6
|
readAbbreviatedPath,
|
|
8
7
|
readPathFromStoriesDirectory,
|
|
8
|
+
readProgramReportNode,
|
|
9
9
|
readRootPathBeforeDirectory,
|
|
10
10
|
} from "./helpers.ts";
|
|
11
11
|
|
|
@@ -23,10 +23,6 @@ function readRequiredSiblingComponentFilePath(filename: string): string | null {
|
|
|
23
23
|
return join(siblingDirectoryPath, `${storySourceBaseName}.tsx`);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
function readReportNode(program: AstProgram): TSESTree.Node {
|
|
27
|
-
return program.body[0] ?? program;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
26
|
const storyFileLocationConventionRule: RuleModule = {
|
|
31
27
|
meta: {
|
|
32
28
|
type: "problem" as const,
|
|
@@ -45,7 +41,7 @@ const storyFileLocationConventionRule: RuleModule = {
|
|
|
45
41
|
create(context) {
|
|
46
42
|
return {
|
|
47
43
|
Program(node: AstProgram) {
|
|
48
|
-
const reportNode =
|
|
44
|
+
const reportNode = readProgramReportNode(node);
|
|
49
45
|
const relativeStoryPath = readPathFromStoriesDirectory(context.filename);
|
|
50
46
|
if (relativeStoryPath === null) {
|
|
51
47
|
context.report({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TSESTree } from "@typescript-eslint/types";
|
|
2
2
|
import type { AstProgram, RuleModule } from "./types.ts";
|
|
3
|
-
import { unwrapExpression } from "./helpers.ts";
|
|
3
|
+
import { readProgramReportNode, unwrapExpression } from "./helpers.ts";
|
|
4
4
|
|
|
5
5
|
type MetaBinding = {
|
|
6
6
|
declaration: TSESTree.VariableDeclaration;
|
|
@@ -79,7 +79,7 @@ const storyMetaTypeAnnotationRule: RuleModule = {
|
|
|
79
79
|
const defaultExportDeclaration = readDefaultExportDeclaration(node);
|
|
80
80
|
if (!defaultExportDeclaration || defaultExportDeclaration.declaration.type !== "Identifier") {
|
|
81
81
|
context.report({
|
|
82
|
-
node: defaultExportDeclaration ?? node,
|
|
82
|
+
node: defaultExportDeclaration ?? readProgramReportNode(node),
|
|
83
83
|
messageId: "invalidMetaBinding",
|
|
84
84
|
});
|
|
85
85
|
return;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import type { AstExpression,
|
|
2
|
-
import { getBaseName, isInTestsDirectory } from "./helpers.ts";
|
|
1
|
+
import type { AstExpression, RuleModule } from "./types.ts";
|
|
2
|
+
import { getBaseName, isInTestsDirectory, readProgramReportNode } from "./helpers.ts";
|
|
3
3
|
|
|
4
4
|
const TEST_FRAMEWORK_MODULES = new Set(["@jest/globals", "bun:test", "node:test", "vitest"]);
|
|
5
5
|
const TEST_IMPORT_NAMES = new Set(["describe", "it", "test"]);
|
|
6
6
|
const REQUIRED_TEST_FILE_NAME_PATTERN = /\.test\.tsx?$/u;
|
|
7
7
|
const SPEC_TEST_FILE_NAME_PATTERN = /\.spec\.tsx?$/u;
|
|
8
8
|
|
|
9
|
-
type ProgramReportNode = AstProgram | AstProgramStatement;
|
|
10
|
-
|
|
11
9
|
function readCallTargetName(node: AstExpression): string | null {
|
|
12
10
|
if (node.type === "Identifier") {
|
|
13
11
|
return node.name;
|
|
@@ -25,10 +23,6 @@ function readCallTargetName(node: AstExpression): string | null {
|
|
|
25
23
|
return null;
|
|
26
24
|
}
|
|
27
25
|
|
|
28
|
-
function readProgramReportNode(node: AstProgram): ProgramReportNode {
|
|
29
|
-
return node.body[0] ?? node;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
26
|
const testFileLocationConventionRule: RuleModule = {
|
|
33
27
|
meta: {
|
|
34
28
|
type: "problem" as const,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RuleModule } from "./types.ts";
|
|
2
|
-
import { isInTestsDirectory, readPathFromTestsDirectory } from "./helpers.ts";
|
|
2
|
+
import { isInTestsDirectory, readPathFromTestsDirectory, readProgramReportNode } from "./helpers.ts";
|
|
3
3
|
|
|
4
4
|
const ALLOWED_ROOT_TEST_FILES_PATTERN = /^[^/]+\.test\.tsx?$/u;
|
|
5
5
|
const ALLOWED_SUPPORT_FILES = new Set(["fixtures.ts", "fixtures.tsx", "helpers.ts", "helpers.tsx"]);
|
|
@@ -41,7 +41,7 @@ const testsDirectoryFileConventionRule: RuleModule = {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
context.report({
|
|
44
|
-
node,
|
|
44
|
+
node: readProgramReportNode(node),
|
|
45
45
|
messageId: "invalidTestsDirectoryFile",
|
|
46
46
|
data: {
|
|
47
47
|
relativePath,
|