@yasainet/eslint 0.0.74 → 0.0.76
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 +4 -104
- package/package.json +12 -1
- package/src/cli/test-audit.mjs +97 -0
- package/src/common/CLAUDE.md +19 -0
- package/src/common/_internal/constants.mjs +1 -1
- package/src/common/_internal/import-patterns.mjs +2 -2
- package/src/common/_internal/selectors.mjs +4 -2
- package/src/common/base/typescript.mjs +13 -7
- package/src/common/boundaries/entry-point.mjs +1 -1
- package/src/common/cross-cutting/ban-alias.mjs +1 -1
- package/src/common/cross-cutting/features-ts-only.mjs +1 -1
- package/src/common/cross-cutting/no-colocated-test.mjs +18 -0
- package/src/common/index.mjs +8 -4
- package/src/common/layers/entries.mjs +15 -9
- package/src/common/layers/queries.mjs +18 -14
- package/src/common/layers/schemas.mjs +6 -1
- package/src/common/layers/services.mjs +14 -8
- package/src/common/layers/{lib.mjs → top-level/lib.mjs} +3 -3
- package/src/common/layers/{top-level-utils.mjs → top-level/utils.mjs} +1 -1
- package/src/common/layers/utils.mjs +5 -1
- package/src/common/local-plugins/entry-single-service-call.mjs +3 -1
- package/src/common/local-plugins/entry-template.mjs +18 -16
- package/src/common/local-plugins/feature-name.mjs +1 -6
- package/src/common/local-plugins/form-state-naming.mjs +1 -1
- package/src/common/local-plugins/form-state-shape.mjs +8 -8
- package/src/common/local-plugins/import-path-style.mjs +2 -2
- package/src/common/local-plugins/index.mjs +2 -0
- package/src/common/local-plugins/layout-main-structural-only.mjs +1 -1
- package/src/common/local-plugins/namespace-import-name.mjs +1 -1
- package/src/common/local-plugins/no-any-return.mjs +1 -1
- package/src/common/local-plugins/no-colocated-test.mjs +26 -0
- package/src/common/local-plugins/queries-export.mjs +1 -1
- package/src/common/local-plugins/queries-namespace-import.mjs +1 -1
- package/src/common/local-plugins/schema-naming.mjs +2 -2
- package/src/common/local-plugins/supabase-columns-satisfies.mjs +1 -1
- package/src/common/local-plugins/supabase-select-typed-columns.mjs +5 -5
- package/src/deno/CLAUDE.md +10 -0
- package/src/deno/boundaries/entry-point.mjs +3 -3
- package/src/deno/boundaries/lib.mjs +1 -1
- package/src/deno/boundaries/utils.mjs +2 -2
- package/src/deno/local-plugins/flat-entry-point.mjs +1 -1
- package/src/next/CLAUDE.md +14 -0
- package/src/next/boundaries/components.mjs +2 -2
- package/src/next/boundaries/hooks.mjs +2 -2
- package/src/next/boundaries/page.mjs +2 -2
- package/src/next/boundaries/route.mjs +2 -2
- package/src/next/boundaries/sitemap.mjs +2 -2
- package/src/next/directives.mjs +4 -4
- package/src/next/layers/components.mjs +1 -1
- package/src/next/layers/hooks.mjs +1 -1
- package/src/next/tailwindcss.mjs +2 -2
- package/src/node/CLAUDE.md +7 -0
package/README.md
CHANGED
|
@@ -9,113 +9,13 @@ Shared ESLint configuration for Next.js, Node.js and Deno.
|
|
|
9
9
|
- `@yasainet/eslint/node` — Common rules for CLI scripts
|
|
10
10
|
- Feature Root: `scripts/features/`
|
|
11
11
|
- Entry Points: `scripts/commands/*.ts`
|
|
12
|
-
- `@yasainet/eslint/deno` — Common rules for Edge Functions
|
|
13
|
-
- Feature Root: `supabase/functions/
|
|
14
|
-
|
|
15
|
-
## Architecture
|
|
16
|
-
|
|
17
|
-
`feature-based architecture` を ESLint で機械的に enforce する設定集。詳細な命名規約・directory 構造・各 layer の責務は [CLAUDE.md](./CLAUDE.md) を参照。consuming project の Claude Code は CLAUDE.md を読んで自動的に規約に従う。
|
|
12
|
+
- `@yasainet/eslint/deno` — Common rules for Supabase Edge Functions
|
|
13
|
+
- Feature Root: `supabase/functions/_features/`
|
|
18
14
|
|
|
19
15
|
## Setup
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```sh
|
|
24
|
-
npm install -D @yasainet/eslint
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
```js
|
|
28
|
-
// eslint.config.mjs
|
|
29
|
-
import { eslintConfig as nextEslintConfig } from "@yasainet/eslint/next";
|
|
30
|
-
import { eslintConfig as nodeEslintConfig } from "@yasainet/eslint/node";
|
|
31
|
-
import { eslintConfig as denoEslintConfig } from "@yasainet/eslint/deno";
|
|
32
|
-
import { defineConfig, globalIgnores } from "eslint/config";
|
|
33
|
-
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
34
|
-
import nextTs from "eslint-config-next/typescript";
|
|
35
|
-
|
|
36
|
-
export default defineConfig([
|
|
37
|
-
...nextVitals,
|
|
38
|
-
...nextTs,
|
|
39
|
-
// Override default ignores of eslint-config-next.
|
|
40
|
-
globalIgnores([
|
|
41
|
-
// Default ignores of eslint-config-next:
|
|
42
|
-
".next/**",
|
|
43
|
-
".vercel/**",
|
|
44
|
-
"out/**",
|
|
45
|
-
"build/**",
|
|
46
|
-
"next-env.d.ts",
|
|
47
|
-
]),
|
|
48
|
-
...nextEslintConfig,
|
|
49
|
-
...nodeEslintConfig,
|
|
50
|
-
...denoEslintConfig,
|
|
51
|
-
]);
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### Next.js
|
|
55
|
-
|
|
56
|
-
```sh
|
|
57
|
-
npm install -D @yasainet/eslint
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
```js
|
|
61
|
-
// eslint.config.mjs
|
|
62
|
-
import { eslintConfig } from "@yasainet/eslint/next";
|
|
63
|
-
import { defineConfig, globalIgnores } from "eslint/config";
|
|
64
|
-
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
65
|
-
import nextTs from "eslint-config-next/typescript";
|
|
66
|
-
|
|
67
|
-
export default defineConfig([
|
|
68
|
-
...nextVitals,
|
|
69
|
-
...nextTs,
|
|
70
|
-
// Override default ignores of eslint-config-next.
|
|
71
|
-
globalIgnores([
|
|
72
|
-
// Default ignores of eslint-config-next:
|
|
73
|
-
".next/**",
|
|
74
|
-
".vercel/**",
|
|
75
|
-
"out/**",
|
|
76
|
-
"build/**",
|
|
77
|
-
"next-env.d.ts",
|
|
78
|
-
]),
|
|
79
|
-
...eslintConfig,
|
|
80
|
-
]);
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Node.js
|
|
84
|
-
|
|
85
|
-
```sh
|
|
86
|
-
npm install -D @yasainet/eslint eslint typescript-eslint
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
```js
|
|
90
|
-
// eslint.config.mjs
|
|
91
|
-
import { eslintConfig } from "@yasainet/eslint/node";
|
|
92
|
-
|
|
93
|
-
export default [...eslintConfig];
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Deno
|
|
97
|
-
|
|
98
|
-
```sh
|
|
99
|
-
npm install -D @yasainet/eslint eslint typescript-eslint
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
```js
|
|
103
|
-
// eslint.config.mjs
|
|
104
|
-
import { eslintConfig } from "@yasainet/eslint/deno";
|
|
105
|
-
|
|
106
|
-
export default [...eslintConfig];
|
|
107
|
-
```
|
|
17
|
+
利用 entry 別の install / config template は [`docs/setup.md`](./docs/setup.md) を参照。
|
|
108
18
|
|
|
109
19
|
## Release
|
|
110
20
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
1. `main` にコミット & push
|
|
114
|
-
2. tag を作成 & push:
|
|
115
|
-
|
|
116
|
-
```sh
|
|
117
|
-
git tag v1.0.0
|
|
118
|
-
git push --tags
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
3. GitHub Actions が npm に publish する
|
|
21
|
+
Git tag (`vX.Y.Z`) を push すると GitHub Actions が npm に publish する。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yasainet/eslint",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.76",
|
|
4
4
|
"description": "ESLint",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
"default": "./src/deno/index.mjs"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
|
+
"bin": {
|
|
21
|
+
"test-audit": "./src/cli/test-audit.mjs"
|
|
22
|
+
},
|
|
20
23
|
"files": [
|
|
21
24
|
"src"
|
|
22
25
|
],
|
|
@@ -32,6 +35,10 @@
|
|
|
32
35
|
"publishConfig": {
|
|
33
36
|
"access": "public"
|
|
34
37
|
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"docs": "node scripts/generate-rules-catalog.mjs",
|
|
40
|
+
"check": "node scripts/check-layer-selectors.mjs"
|
|
41
|
+
},
|
|
35
42
|
"dependencies": {
|
|
36
43
|
"@stylistic/eslint-plugin": "^5.9.0",
|
|
37
44
|
"eslint-plugin-better-tailwindcss": "^4.1.1",
|
|
@@ -42,5 +49,9 @@
|
|
|
42
49
|
"peerDependencies": {
|
|
43
50
|
"eslint": "^9",
|
|
44
51
|
"typescript-eslint": "^8"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"eslint": "^9",
|
|
55
|
+
"typescript-eslint": "^8"
|
|
45
56
|
}
|
|
46
57
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
// pure layer (schemas / utils) の unit test presence を機械チェックする:
|
|
6
|
+
//
|
|
7
|
+
// - 各 source に 兄弟 *.test.ts が存在するか、`@unit-exempt:` marker を持つことを要求
|
|
8
|
+
// - schemas は定義上 pure。utils は impure 混在のため marker で opt-out できる
|
|
9
|
+
// - ESLint の per-file モデルと噛み合わない「存在強制」を全ツリー一括監査で担う
|
|
10
|
+
|
|
11
|
+
const REQUIRE_DIRS = new Set(["schemas", "utils"]);
|
|
12
|
+
const EXEMPT_RE = /@unit-exempt:/;
|
|
13
|
+
|
|
14
|
+
function getFlag(argv, name) {
|
|
15
|
+
const i = argv.indexOf(name);
|
|
16
|
+
return i !== -1 && argv[i + 1] ? argv[i + 1] : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function* walk(dir) {
|
|
20
|
+
if (!fs.existsSync(dir)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
+
const full = path.join(dir, entry.name);
|
|
25
|
+
if (entry.isDirectory()) {
|
|
26
|
+
yield* walk(full);
|
|
27
|
+
} else if (entry.isFile()) {
|
|
28
|
+
yield full;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isTarget(file) {
|
|
34
|
+
if (!file.endsWith(".ts")) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (file.endsWith(".test.ts") || file.endsWith(".d.ts")) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
return REQUIRE_DIRS.has(path.basename(path.dirname(file)));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isSatisfied(file) {
|
|
44
|
+
const testFile = file.replace(/\.ts$/, ".test.ts");
|
|
45
|
+
if (fs.existsSync(testFile)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return EXEMPT_RE.test(fs.readFileSync(file, "utf8"));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function main() {
|
|
52
|
+
const argv = process.argv.slice(2);
|
|
53
|
+
|
|
54
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
55
|
+
console.log(
|
|
56
|
+
"test-audit — pure layer (schemas/utils) の unit test presence を検査\n\n" +
|
|
57
|
+
"Usage: test-audit [--feature-root <path>]\n\n" +
|
|
58
|
+
" --feature-root <path> feature root (default: src/features)\n\n" +
|
|
59
|
+
"各 schemas/*.ts と utils/*.ts に 兄弟 *.test.ts または\n" +
|
|
60
|
+
"`// @unit-exempt: <理由>` marker を要求する。",
|
|
61
|
+
);
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const projectRoot = process.cwd();
|
|
66
|
+
const featureRoot = getFlag(argv, "--feature-root") ?? "src/features";
|
|
67
|
+
const roots = [path.join(projectRoot, featureRoot)];
|
|
68
|
+
|
|
69
|
+
const violations = [];
|
|
70
|
+
for (const root of roots) {
|
|
71
|
+
for (const file of walk(root)) {
|
|
72
|
+
if (isTarget(file) && !isSatisfied(file)) {
|
|
73
|
+
violations.push(path.relative(projectRoot, file));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (violations.length > 0) {
|
|
79
|
+
console.error(
|
|
80
|
+
`✗ test-audit: ${violations.length} 件の pure layer に unit test も @unit-exempt marker もありません:\n`,
|
|
81
|
+
);
|
|
82
|
+
for (const v of violations.sort()) {
|
|
83
|
+
console.error(` ${v}`);
|
|
84
|
+
}
|
|
85
|
+
console.error(
|
|
86
|
+
"\n対応: 兄弟 *.test.ts を追加するか、impure な場合は\n" +
|
|
87
|
+
"`// @unit-exempt: <理由>` を記載する。",
|
|
88
|
+
);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(
|
|
93
|
+
"✓ test-audit: schemas / utils はすべて unit test または @unit-exempt が揃っています。",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# src/common/CLAUDE.md
|
|
2
|
+
|
|
3
|
+
全 entry で共有する rule の置き場所判断 (target 基準):
|
|
4
|
+
|
|
5
|
+
- `_internal/` — private 実装詳細 (constants / plugins / selectors / import-patterns)。consumer から見えない
|
|
6
|
+
- `base/` — 全ファイル対象の generic rule (TypeScript syntactic / type-aware)
|
|
7
|
+
- `boundaries/<surface>.mjs` — 外界 → features の入口で enforce する契約
|
|
8
|
+
- `cross-cutting/` — 複数 layer に跨る規約
|
|
9
|
+
- 例 (naming): feature-name / namespace-import / form-state
|
|
10
|
+
- 例 (rule): logger / jsdoc / no-any-return / supabase-columns-satisfies
|
|
11
|
+
- `layers/<layer>.mjs` — features 内部の階層単位
|
|
12
|
+
- 種類: queries / services / entries / utils / constants / schemas / types
|
|
13
|
+
- 1 layer の全制約 (naming + syntax + imports + local rules) を 1 file に集約
|
|
14
|
+
- `layers/top-level/<layer>.mjs` — features と同階層 (top-level) のディレクトリ単位
|
|
15
|
+
- 種類: lib / utils
|
|
16
|
+
- `local-plugins/` — ESLint local plugin の実装本体
|
|
17
|
+
- `index.mjs` — common entry。上記 file を合成して export
|
|
18
|
+
|
|
19
|
+
新規 rule の glob が単一 layer に閉じるなら `layers/`、跨ぐなら `cross-cutting/`、外界の caller surface なら `boundaries/` に置く。
|
|
@@ -20,7 +20,7 @@ function findProjectRoot() {
|
|
|
20
20
|
|
|
21
21
|
const PROJECT_ROOT = findProjectRoot();
|
|
22
22
|
|
|
23
|
-
const EXCLUDE_LIST = ["
|
|
23
|
+
const EXCLUDE_LIST = ["type.ts", "proxy.ts"];
|
|
24
24
|
|
|
25
25
|
export function generatePrefixLibMapping(featureRoot) {
|
|
26
26
|
const libRoot = featureRoot.replace(/features$/, "lib");
|
|
@@ -2,7 +2,7 @@ export const LIB_BOUNDARY_PATTERNS = [
|
|
|
2
2
|
{
|
|
3
3
|
group: ["@/lib/*", "@/lib/**"],
|
|
4
4
|
message:
|
|
5
|
-
"lib/*
|
|
5
|
+
"lib/* は queries からのみ import 可。他層は queries 経由で使う。",
|
|
6
6
|
},
|
|
7
7
|
];
|
|
8
8
|
|
|
@@ -11,6 +11,6 @@ export const MAPPING_PATTERNS = [
|
|
|
11
11
|
group: ["@/utils/mapping.util"],
|
|
12
12
|
importNames: ["mapSnakeToCamel", "mapCamelToSnake"],
|
|
13
13
|
message:
|
|
14
|
-
"
|
|
14
|
+
"mapping 関数は services のみ許可。snake/camel 変換は service 境界で行う。",
|
|
15
15
|
},
|
|
16
16
|
];
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export const loggerSelector = "CallExpression[callee.object.name='logger']";
|
|
2
2
|
|
|
3
3
|
export const loggerMessage =
|
|
4
|
-
"logger
|
|
4
|
+
"logger は entries 以外で禁止。ログ出力は entries に集約する。";
|
|
5
5
|
|
|
6
6
|
export const aliasDynamicImportSelector =
|
|
7
7
|
"ImportExpression[source.type='Literal'][source.value=/^@\\//]";
|
|
8
8
|
|
|
9
9
|
export const aliasDynamicImportMessage =
|
|
10
|
-
"
|
|
10
|
+
"features layers で `@/` パスの動的 import は禁止 (prefix-lib / lateral 制約を迂回する):\n" +
|
|
11
|
+
"- 内部依存は queries/<prefix>.ts か services/<prefix>.ts を作る\n" +
|
|
12
|
+
"- 外部 npm は cold-start 最適化の遅延 import なら可";
|
|
@@ -8,7 +8,10 @@ import { simpleImportSortPlugin, stylistic } from "../_internal/plugins.mjs";
|
|
|
8
8
|
const findProjectRoot = (start) => {
|
|
9
9
|
let dir = start;
|
|
10
10
|
while (dir !== dirname(dir)) {
|
|
11
|
-
if (
|
|
11
|
+
if (
|
|
12
|
+
!dir.split(sep).includes("node_modules") &&
|
|
13
|
+
existsSync(join(dir, "tsconfig.json"))
|
|
14
|
+
) {
|
|
12
15
|
return dir;
|
|
13
16
|
}
|
|
14
17
|
dir = dirname(dir);
|
|
@@ -26,6 +29,7 @@ const sharedRulesConfig = {
|
|
|
26
29
|
},
|
|
27
30
|
rules: {
|
|
28
31
|
"no-console": "warn",
|
|
32
|
+
|
|
29
33
|
"no-irregular-whitespace": [
|
|
30
34
|
"warn",
|
|
31
35
|
{
|
|
@@ -35,19 +39,21 @@ const sharedRulesConfig = {
|
|
|
35
39
|
skipTemplates: false,
|
|
36
40
|
},
|
|
37
41
|
],
|
|
38
|
-
"simple-import-sort/imports": "warn",
|
|
39
|
-
"simple-import-sort/exports": "warn",
|
|
40
|
-
"@stylistic/quotes": ["warn", "double", { avoidEscape: true }],
|
|
41
42
|
"no-unreachable": "error",
|
|
42
|
-
"no-unreachable-loop": "error",
|
|
43
|
-
"no-useless-return": "error",
|
|
44
43
|
"no-constant-condition": "error",
|
|
45
44
|
"no-constant-binary-expression": "error",
|
|
46
45
|
"no-dupe-else-if": "error",
|
|
47
46
|
"no-self-assign": "error",
|
|
48
|
-
"no-self-compare": "error",
|
|
49
47
|
"no-useless-catch": "error",
|
|
50
48
|
"no-fallthrough": "error",
|
|
49
|
+
|
|
50
|
+
"no-unreachable-loop": "error",
|
|
51
|
+
"no-useless-return": "error",
|
|
52
|
+
"no-self-compare": "error",
|
|
53
|
+
|
|
54
|
+
"simple-import-sort/imports": "warn",
|
|
55
|
+
"simple-import-sort/exports": "warn",
|
|
56
|
+
"@stylistic/quotes": ["warn", "double", { avoidEscape: true }],
|
|
51
57
|
},
|
|
52
58
|
};
|
|
53
59
|
|
|
@@ -10,7 +10,7 @@ export function createEntryPointConfigs(entryPointFiles, entryPointIgnores = [])
|
|
|
10
10
|
{
|
|
11
11
|
selector: "ImportDeclaration:has(ImportNamespaceSpecifier)",
|
|
12
12
|
message:
|
|
13
|
-
"
|
|
13
|
+
"entry point は `import * as` 禁止。named import で依存を明示する。",
|
|
14
14
|
},
|
|
15
15
|
],
|
|
16
16
|
},
|
|
@@ -11,7 +11,7 @@ export function createFeaturesTsOnlyConfigs({ featureRoot }) {
|
|
|
11
11
|
{
|
|
12
12
|
selector: "Program",
|
|
13
13
|
message:
|
|
14
|
-
"features/
|
|
14
|
+
"features/ は .ts のみ。component は src/components/ に置く。",
|
|
15
15
|
},
|
|
16
16
|
],
|
|
17
17
|
},
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { localPlugin } from "../local-plugins/index.mjs";
|
|
2
|
+
|
|
3
|
+
export function createNoColocatedTestConfigs({ featureRoot }) {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
name: "test/no-colocated-test",
|
|
7
|
+
files: [
|
|
8
|
+
`${featureRoot}/**/services/**/*.test.{ts,tsx}`,
|
|
9
|
+
`${featureRoot}/**/queries/**/*.test.{ts,tsx}`,
|
|
10
|
+
`${featureRoot}/**/entries/**/*.test.{ts,tsx}`,
|
|
11
|
+
],
|
|
12
|
+
plugins: { local: localPlugin },
|
|
13
|
+
rules: {
|
|
14
|
+
"local/no-colocated-test": "error",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
}
|
package/src/common/index.mjs
CHANGED
|
@@ -9,14 +9,15 @@ import { createJsdocConfigs } from "./cross-cutting/jsdoc.mjs";
|
|
|
9
9
|
import { createLoggerConfigs } from "./cross-cutting/logger.mjs";
|
|
10
10
|
import { createNamespaceImportConfigs } from "./cross-cutting/namespace-import.mjs";
|
|
11
11
|
import { createNoAnyReturnConfigs } from "./cross-cutting/no-any-return.mjs";
|
|
12
|
+
import { createNoColocatedTestConfigs } from "./cross-cutting/no-colocated-test.mjs";
|
|
12
13
|
import { createSupabaseColumnsSatisfiesConfigs } from "./cross-cutting/supabase-columns-satisfies.mjs";
|
|
13
14
|
import { createConstantsConfigs } from "./layers/constants.mjs";
|
|
14
15
|
import { createEntriesConfigs } from "./layers/entries.mjs";
|
|
15
|
-
import {
|
|
16
|
+
import { createTopLevelLibConfigs } from "./layers/top-level/lib.mjs";
|
|
16
17
|
import { createQueriesConfigs } from "./layers/queries.mjs";
|
|
17
18
|
import { createSchemasConfigs } from "./layers/schemas.mjs";
|
|
18
19
|
import { createServicesConfigs } from "./layers/services.mjs";
|
|
19
|
-
import { createTopLevelUtilsConfigs } from "./layers/top-level
|
|
20
|
+
import { createTopLevelUtilsConfigs } from "./layers/top-level/utils.mjs";
|
|
20
21
|
import { createTypesConfigs } from "./layers/types.mjs";
|
|
21
22
|
import { createUtilsConfigs } from "./layers/utils.mjs";
|
|
22
23
|
|
|
@@ -30,11 +31,14 @@ export function createCommonConfigs(
|
|
|
30
31
|
...createTypescriptConfigs({ typeAware, ...(rulesFiles && { files: rulesFiles }) }),
|
|
31
32
|
...createFeatureNameConfigs(ctx),
|
|
32
33
|
...createNamespaceImportConfigs(ctx),
|
|
34
|
+
// logger は features/**\/*.ts 全体に no-restricted-syntax を設定するため、
|
|
35
|
+
// 同 rule を持つ services/queries より前に置く (flat config は後勝ちで完全置換)。
|
|
36
|
+
...createLoggerConfigs(ctx),
|
|
33
37
|
...createServicesConfigs(ctx),
|
|
34
38
|
...createQueriesConfigs(ctx),
|
|
35
39
|
...createFormStateConfigs(ctx),
|
|
36
40
|
...createSupabaseColumnsSatisfiesConfigs(ctx),
|
|
37
|
-
...
|
|
41
|
+
...createTopLevelLibConfigs(ctx),
|
|
38
42
|
...createTopLevelUtilsConfigs(ctx),
|
|
39
43
|
...createTypesConfigs(ctx),
|
|
40
44
|
...createSchemasConfigs(ctx),
|
|
@@ -42,7 +46,7 @@ export function createCommonConfigs(
|
|
|
42
46
|
...createConstantsConfigs(ctx),
|
|
43
47
|
...createEntriesConfigs(ctx),
|
|
44
48
|
...createFeaturesTsOnlyConfigs(ctx),
|
|
45
|
-
...
|
|
49
|
+
...createNoColocatedTestConfigs(ctx),
|
|
46
50
|
...createNoAnyReturnConfigs(ctx),
|
|
47
51
|
...createFeatureDefaultImportsConfigs(ctx),
|
|
48
52
|
...createJsdocConfigs(ctx),
|
|
@@ -13,19 +13,19 @@ import { localPlugin } from "../local-plugins/index.mjs";
|
|
|
13
13
|
const LAYER_PATTERNS = [
|
|
14
14
|
{
|
|
15
15
|
group: ["**/queries/*", "**/queries"],
|
|
16
|
-
message:
|
|
16
|
+
message:
|
|
17
|
+
"entries は queries を import 不可。queries は service 経由で使う。",
|
|
17
18
|
},
|
|
18
19
|
{
|
|
19
20
|
group: ["**/hooks/*", "**/hooks"],
|
|
20
|
-
message: "entries
|
|
21
|
+
message: "entries は hooks を import 不可。依存は単方向に保つ。",
|
|
21
22
|
},
|
|
22
23
|
];
|
|
23
24
|
|
|
24
25
|
const LATERAL_PATTERNS = [
|
|
25
26
|
{
|
|
26
27
|
group: ["@/features/*/entries/*", "@/features/*/entries"],
|
|
27
|
-
message:
|
|
28
|
-
"entries cannot import other feature's entries (lateral violation)",
|
|
28
|
+
message: "他 feature の entries は import 不可。feature を跨ぐ依存は禁止。",
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
group: [
|
|
@@ -35,7 +35,9 @@ const LATERAL_PATTERNS = [
|
|
|
35
35
|
"!@/features/shared/services",
|
|
36
36
|
],
|
|
37
37
|
message:
|
|
38
|
-
"
|
|
38
|
+
"他 feature の services は import 不可:\n" +
|
|
39
|
+
"- 同一 feature の service を 1:1 で使うか、orchestration を service 層へ移す\n" +
|
|
40
|
+
"- `shared/services/*` は横断的な副作用 (通知等) のため例外",
|
|
39
41
|
},
|
|
40
42
|
];
|
|
41
43
|
|
|
@@ -44,21 +46,21 @@ const CARDINALITY_PATTERNS = {
|
|
|
44
46
|
{
|
|
45
47
|
group: ["**/services/client", "**/services/admin"],
|
|
46
48
|
message:
|
|
47
|
-
"server entry
|
|
49
|
+
"server entry は server service のみ import 可。context を跨ぐ呼び出しは禁止。",
|
|
48
50
|
},
|
|
49
51
|
],
|
|
50
52
|
client: [
|
|
51
53
|
{
|
|
52
54
|
group: ["**/services/server", "**/services/admin"],
|
|
53
55
|
message:
|
|
54
|
-
"client entry
|
|
56
|
+
"client entry は client service のみ import 可。context を跨ぐ呼び出しは禁止。",
|
|
55
57
|
},
|
|
56
58
|
],
|
|
57
59
|
admin: [
|
|
58
60
|
{
|
|
59
61
|
group: ["**/services/server", "**/services/client"],
|
|
60
62
|
message:
|
|
61
|
-
"admin entry
|
|
63
|
+
"admin entry は admin service のみ import 可。context を跨ぐ呼び出しは禁止。",
|
|
62
64
|
},
|
|
63
65
|
],
|
|
64
66
|
};
|
|
@@ -75,7 +77,10 @@ export function createEntriesConfigs({ featureRoot, prefixLibMapping }) {
|
|
|
75
77
|
{
|
|
76
78
|
name: "naming/entries",
|
|
77
79
|
files: featuresGlob(featureRoot, "**/entries/*.ts"),
|
|
78
|
-
ignores:
|
|
80
|
+
ignores: [
|
|
81
|
+
...featuresGlob(featureRoot, "shared/entries/*.ts"),
|
|
82
|
+
"**/*.test.ts",
|
|
83
|
+
],
|
|
79
84
|
plugins: { "check-file": checkFile },
|
|
80
85
|
rules: {
|
|
81
86
|
"check-file/filename-naming-convention": [
|
|
@@ -87,6 +92,7 @@ export function createEntriesConfigs({ featureRoot, prefixLibMapping }) {
|
|
|
87
92
|
{
|
|
88
93
|
name: "naming/entries-shared",
|
|
89
94
|
files: featuresGlob(featureRoot, "shared/entries/*.ts"),
|
|
95
|
+
ignores: ["**/*.test.ts"],
|
|
90
96
|
plugins: { "check-file": checkFile },
|
|
91
97
|
rules: {
|
|
92
98
|
"check-file/filename-naming-convention": [
|
|
@@ -12,15 +12,15 @@ import { localPlugin } from "../local-plugins/index.mjs";
|
|
|
12
12
|
const LAYER_PATTERNS = [
|
|
13
13
|
{
|
|
14
14
|
group: ["**/services/*", "**/services"],
|
|
15
|
-
message: "queries
|
|
15
|
+
message: "queries は services を import 不可。ロジックは services へ。",
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
group: ["**/entries/*", "**/entries"],
|
|
19
|
-
message: "queries
|
|
19
|
+
message: "queries は entries を import 不可。依存は単方向に保つ。",
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
group: ["**/hooks/*", "**/hooks"],
|
|
23
|
-
message: "queries
|
|
23
|
+
message: "queries は hooks を import 不可。依存は単方向に保つ。",
|
|
24
24
|
},
|
|
25
25
|
];
|
|
26
26
|
|
|
@@ -28,7 +28,7 @@ const LATERAL_PATTERNS = [
|
|
|
28
28
|
{
|
|
29
29
|
group: ["@/features/*/queries/*", "@/features/*/queries"],
|
|
30
30
|
message:
|
|
31
|
-
"queries
|
|
31
|
+
"他 feature の queries は import 不可。feature を跨ぐ依存は禁止。",
|
|
32
32
|
},
|
|
33
33
|
];
|
|
34
34
|
|
|
@@ -39,7 +39,7 @@ function prefixLibPatterns(prefix, mapping) {
|
|
|
39
39
|
.filter((p) => p !== prefix)
|
|
40
40
|
.map((p) => ({
|
|
41
41
|
group: [`**/lib/${mapping[p]}`, `**/lib/${mapping[p]}/*`],
|
|
42
|
-
message: `queries/${prefix}.ts
|
|
42
|
+
message: `queries/${prefix}.ts は lib/${allowedLib} のみ import 可。lib ごとに対応する query file を使う。`,
|
|
43
43
|
}));
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -55,7 +55,10 @@ export function createQueriesConfigs({ featureRoot, prefixLibMapping }) {
|
|
|
55
55
|
{
|
|
56
56
|
name: "naming/queries",
|
|
57
57
|
files: featuresGlob(featureRoot, "**/queries/*.ts"),
|
|
58
|
-
ignores:
|
|
58
|
+
ignores: [
|
|
59
|
+
...featuresGlob(featureRoot, "shared/queries/*.ts"),
|
|
60
|
+
"**/*.test.ts",
|
|
61
|
+
],
|
|
59
62
|
plugins: { "check-file": checkFile },
|
|
60
63
|
rules: {
|
|
61
64
|
"check-file/filename-naming-convention": [
|
|
@@ -67,6 +70,7 @@ export function createQueriesConfigs({ featureRoot, prefixLibMapping }) {
|
|
|
67
70
|
{
|
|
68
71
|
name: "naming/queries-shared",
|
|
69
72
|
files: featuresGlob(featureRoot, "shared/queries/*.ts"),
|
|
73
|
+
ignores: ["**/*.test.ts"],
|
|
70
74
|
plugins: { "check-file": checkFile },
|
|
71
75
|
rules: {
|
|
72
76
|
"check-file/filename-naming-convention": [
|
|
@@ -100,42 +104,42 @@ export function createQueriesConfigs({ featureRoot, prefixLibMapping }) {
|
|
|
100
104
|
{
|
|
101
105
|
selector: "TryStatement",
|
|
102
106
|
message:
|
|
103
|
-
"try-catch
|
|
107
|
+
"queries では try-catch 禁止。エラーは `{ data, error }` で返す。",
|
|
104
108
|
},
|
|
105
109
|
{
|
|
106
110
|
selector: "IfStatement",
|
|
107
111
|
message:
|
|
108
|
-
"
|
|
112
|
+
"queries で if 文は禁止。条件分岐は services に置く。",
|
|
109
113
|
},
|
|
110
114
|
{
|
|
111
115
|
selector: "ForStatement",
|
|
112
116
|
message:
|
|
113
|
-
"
|
|
117
|
+
"queries でループは禁止。queries は薄い CRUD ラッパー、反復は services に置く。",
|
|
114
118
|
},
|
|
115
119
|
{
|
|
116
120
|
selector: "ForOfStatement",
|
|
117
121
|
message:
|
|
118
|
-
"
|
|
122
|
+
"queries でループは禁止。queries は薄い CRUD ラッパー、反復は services に置く。",
|
|
119
123
|
},
|
|
120
124
|
{
|
|
121
125
|
selector: "ForInStatement",
|
|
122
126
|
message:
|
|
123
|
-
"
|
|
127
|
+
"queries でループは禁止。queries は薄い CRUD ラッパー、反復は services に置く。",
|
|
124
128
|
},
|
|
125
129
|
{
|
|
126
130
|
selector: "WhileStatement",
|
|
127
131
|
message:
|
|
128
|
-
"
|
|
132
|
+
"queries でループは禁止。queries は薄い CRUD ラッパー、反復は services に置く。",
|
|
129
133
|
},
|
|
130
134
|
{
|
|
131
135
|
selector: "DoWhileStatement",
|
|
132
136
|
message:
|
|
133
|
-
"
|
|
137
|
+
"queries でループは禁止。queries は薄い CRUD ラッパー、反復は services に置く。",
|
|
134
138
|
},
|
|
135
139
|
{
|
|
136
140
|
selector: "ThrowStatement",
|
|
137
141
|
message:
|
|
138
|
-
"
|
|
142
|
+
"queries で throw は禁止。Supabase の `{ data, error }` をそのまま返す。",
|
|
139
143
|
},
|
|
140
144
|
{ selector: loggerSelector, message: loggerMessage },
|
|
141
145
|
{
|