@prover-coder-ai/component-tagger 1.0.5 → 1.0.6
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/dist/src/core/component-path.d.ts +25 -0
- package/{src/core/component-path.ts → dist/src/core/component-path.js} +4 -11
- package/dist/src/index.d.ts +2 -0
- package/{src/index.ts → dist/src/index.js} +2 -2
- package/dist/src/shell/component-tagger.d.ts +13 -0
- package/{src/shell/component-tagger.ts → dist/src/shell/component-tagger.js} +71 -116
- package/dist/tests/core/component-path.test.d.ts +1 -0
- package/dist/tests/core/component-path.test.js +17 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +67 -0
- package/package.json +4 -1
- package/.jscpd.json +0 -16
- package/CHANGELOG.md +0 -31
- package/biome.json +0 -34
- package/eslint.config.mts +0 -305
- package/eslint.effect-ts-check.config.mjs +0 -220
- package/linter.config.json +0 -33
- package/tests/core/component-path.test.ts +0 -24
- package/tsconfig.json +0 -18
- package/vitest.config.ts +0 -73
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare const componentPathAttributeName = "path";
|
|
2
|
+
/**
|
|
3
|
+
* Checks whether the Vite id represents a JSX or TSX module.
|
|
4
|
+
*
|
|
5
|
+
* @param id - Vite module id (may include query parameters).
|
|
6
|
+
* @returns true when the id ends with .jsx/.tsx (optionally with query).
|
|
7
|
+
*
|
|
8
|
+
* @pure true
|
|
9
|
+
* @invariant isJsxFile(id) = true -> id matches /\.(tsx|jsx)(\?.*)?$/u
|
|
10
|
+
* @complexity O(n) time / O(1) space where n = |id|
|
|
11
|
+
*/
|
|
12
|
+
export declare const isJsxFile: (id: string) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Formats the component path payload containing file, line, and column.
|
|
15
|
+
*
|
|
16
|
+
* @param relativeFilename - Path relative to the project root.
|
|
17
|
+
* @param line - 1-based line number from the parser location.
|
|
18
|
+
* @param column - 0-based column number from the parser location.
|
|
19
|
+
* @returns Encoded location string: "<path>:<line>:<column>".
|
|
20
|
+
*
|
|
21
|
+
* @pure true
|
|
22
|
+
* @invariant line >= 1 and column >= 0
|
|
23
|
+
* @complexity O(1) time / O(1) space
|
|
24
|
+
*/
|
|
25
|
+
export declare const formatComponentPathValue: (relativeFilename: string, line: number, column: number) => string;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
const jsxFilePattern = /\.(tsx|jsx)(\?.*)?$/u
|
|
2
|
-
|
|
1
|
+
const jsxFilePattern = /\.(tsx|jsx)(\?.*)?$/u;
|
|
3
2
|
// CHANGE: define canonical attribute name for component path tagging.
|
|
4
3
|
// WHY: reduce metadata to a single attribute while keeping full source location.
|
|
5
4
|
// QUOTE(TZ): "\u0421\u0430\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u043c app \u043d\u043e \u0432\u043e\u0442 \u0447\u0442\u043e \u0431\u044b \u0435\u0433\u043e \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0434\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0435\u043a\u0442 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0448 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0430\u043f\u043f \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c"
|
|
@@ -10,8 +9,7 @@ const jsxFilePattern = /\.(tsx|jsx)(\?.*)?$/u
|
|
|
10
9
|
// EFFECT: n/a
|
|
11
10
|
// INVARIANT: attribute name remains stable across transforms
|
|
12
11
|
// COMPLEXITY: O(1)/O(1)
|
|
13
|
-
export const componentPathAttributeName = "path"
|
|
14
|
-
|
|
12
|
+
export const componentPathAttributeName = "path";
|
|
15
13
|
/**
|
|
16
14
|
* Checks whether the Vite id represents a JSX or TSX module.
|
|
17
15
|
*
|
|
@@ -32,8 +30,7 @@ export const componentPathAttributeName = "path"
|
|
|
32
30
|
// EFFECT: n/a
|
|
33
31
|
// INVARIANT: predicate depends only on id content
|
|
34
32
|
// COMPLEXITY: O(n)/O(1)
|
|
35
|
-
export const isJsxFile = (id
|
|
36
|
-
|
|
33
|
+
export const isJsxFile = (id) => jsxFilePattern.test(id);
|
|
37
34
|
/**
|
|
38
35
|
* Formats the component path payload containing file, line, and column.
|
|
39
36
|
*
|
|
@@ -56,8 +53,4 @@ export const isJsxFile = (id: string): boolean => jsxFilePattern.test(id)
|
|
|
56
53
|
// EFFECT: n/a
|
|
57
54
|
// INVARIANT: output encodes path + line + column without loss
|
|
58
55
|
// COMPLEXITY: O(1)/O(1)
|
|
59
|
-
export const formatComponentPathValue = (
|
|
60
|
-
relativeFilename: string,
|
|
61
|
-
line: number,
|
|
62
|
-
column: number
|
|
63
|
-
): string => `${relativeFilename}:${line}:${column}`
|
|
56
|
+
export const formatComponentPathValue = (relativeFilename, line, column) => `${relativeFilename}:${line}:${column}`;
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
// EFFECT: n/a
|
|
9
9
|
// INVARIANT: exports remain stable for consumers
|
|
10
10
|
// COMPLEXITY: O(1)/O(1)
|
|
11
|
-
export { componentPathAttributeName, formatComponentPathValue, isJsxFile } from "./core/component-path.js"
|
|
12
|
-
export { componentTagger } from "./shell/component-tagger.js"
|
|
11
|
+
export { componentPathAttributeName, formatComponentPathValue, isJsxFile } from "./core/component-path.js";
|
|
12
|
+
export { componentTagger } from "./shell/component-tagger.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PluginOption } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* Creates a Vite plugin that injects a single component-path data attribute.
|
|
4
|
+
*
|
|
5
|
+
* @returns Vite PluginOption for pre-transform tagging.
|
|
6
|
+
*
|
|
7
|
+
* @pure false
|
|
8
|
+
* @effect Babel transform through Effect
|
|
9
|
+
* @invariant only JSX/TSX modules are transformed
|
|
10
|
+
* @complexity O(n) time / O(1) space per JSX module
|
|
11
|
+
* @throws Never - errors are typed and surfaced by Effect
|
|
12
|
+
*/
|
|
13
|
+
export declare const componentTagger: () => PluginOption;
|
|
@@ -1,31 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { layer as NodePathLayer } from "@effect/platform-node/NodePath"
|
|
3
|
-
import { Path } from "@effect/platform/Path"
|
|
4
|
-
import { Effect, pipe } from "effect"
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
import { componentPathAttributeName, formatComponentPathValue, isJsxFile } from "../core/component-path.js"
|
|
8
|
-
|
|
9
|
-
type BabelTransformResult = Awaited<ReturnType<typeof transformAsync>>
|
|
10
|
-
|
|
11
|
-
type ViteTransformResult = {
|
|
12
|
-
readonly code: string
|
|
13
|
-
readonly map: NonNullable<BabelTransformResult>["map"] | null
|
|
14
|
-
}
|
|
15
|
-
|
|
1
|
+
import { transformAsync, types as t } from "@babel/core";
|
|
2
|
+
import { layer as NodePathLayer } from "@effect/platform-node/NodePath";
|
|
3
|
+
import { Path } from "@effect/platform/Path";
|
|
4
|
+
import { Effect, pipe } from "effect";
|
|
5
|
+
import { componentPathAttributeName, formatComponentPathValue, isJsxFile } from "../core/component-path.js";
|
|
16
6
|
class ComponentTaggerError extends Error {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const stripQuery = (id: string): string => {
|
|
25
|
-
const queryIndex = id.indexOf("?")
|
|
26
|
-
return queryIndex === -1 ? id : id.slice(0, queryIndex)
|
|
7
|
+
cause;
|
|
8
|
+
_tag = "ComponentTaggerError";
|
|
9
|
+
constructor(message, cause) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.cause = cause;
|
|
12
|
+
}
|
|
27
13
|
}
|
|
28
|
-
|
|
14
|
+
const stripQuery = (id) => {
|
|
15
|
+
const queryIndex = id.indexOf("?");
|
|
16
|
+
return queryIndex === -1 ? id : id.slice(0, queryIndex);
|
|
17
|
+
};
|
|
29
18
|
// CHANGE: compute relative paths from the resolved Vite root instead of process.cwd().
|
|
30
19
|
// WHY: keep component paths stable across monorepos and custom Vite roots.
|
|
31
20
|
// QUOTE(TZ): "Сам компонент должен быть в текущем app но вот что бы его протестировать надо создать ещё один проект который наш текущий апп будет подключать"
|
|
@@ -36,30 +25,18 @@ const stripQuery = (id: string): string => {
|
|
|
36
25
|
// EFFECT: Effect<string, never, Path>
|
|
37
26
|
// INVARIANT: output is deterministic for a fixed root
|
|
38
27
|
// COMPLEXITY: O(n)/O(1)
|
|
39
|
-
const relativeFromRoot = (rootDir
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (result === null || result.code === null || result.code === undefined) {
|
|
52
|
-
return null
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const { code } = result
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
code,
|
|
59
|
-
map: result.map ?? null
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
28
|
+
const relativeFromRoot = (rootDir, absolutePath) => pipe(Path, Effect.map((pathService) => pathService.relative(rootDir, absolutePath)));
|
|
29
|
+
const attrExists = (node, attrName) => node.attributes.some((attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name: attrName }));
|
|
30
|
+
const toViteResult = (result) => {
|
|
31
|
+
if (result === null || result.code === null || result.code === undefined) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const { code } = result;
|
|
35
|
+
return {
|
|
36
|
+
code,
|
|
37
|
+
map: result.map ?? null
|
|
38
|
+
};
|
|
39
|
+
};
|
|
63
40
|
// CHANGE: inject a single path attribute into JSX opening elements.
|
|
64
41
|
// WHY: remove redundant metadata while preserving the full source location payload.
|
|
65
42
|
// QUOTE(TZ): "\u0421\u0430\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0432 \u0442\u0435\u043a\u0443\u0449\u0435\u043c app \u043d\u043e \u0432\u043e\u0442 \u0447\u0442\u043e \u0431\u044b \u0435\u0433\u043e \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0434\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0435\u0449\u0451 \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0435\u043a\u0442 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0430\u0448 \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0430\u043f\u043f \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0442\u044c"
|
|
@@ -70,30 +47,23 @@ const toViteResult = (result: BabelTransformResult): ViteTransformResult | null
|
|
|
70
47
|
// EFFECT: Effect<ViteTransformResult | null, ComponentTaggerError, Path>
|
|
71
48
|
// INVARIANT: each JSX opening element has at most one path attribute
|
|
72
49
|
// COMPLEXITY: O(n)/O(1), n = number of JSX elements
|
|
73
|
-
const makeBabelTagger = (relativeFilename
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const value = formatComponentPathValue(relativeFilename, line, column)
|
|
89
|
-
|
|
90
|
-
node.attributes.push(
|
|
91
|
-
t.jsxAttribute(t.jsxIdentifier(componentPathAttributeName), t.stringLiteral(value))
|
|
92
|
-
)
|
|
50
|
+
const makeBabelTagger = (relativeFilename) => ({
|
|
51
|
+
name: "component-path-babel-tagger",
|
|
52
|
+
visitor: {
|
|
53
|
+
JSXOpeningElement(openPath) {
|
|
54
|
+
const { node } = openPath;
|
|
55
|
+
if (node.loc === null || node.loc === undefined) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (attrExists(node, componentPathAttributeName)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const { column, line } = node.loc.start;
|
|
62
|
+
const value = formatComponentPathValue(relativeFilename, line, column);
|
|
63
|
+
node.attributes.push(t.jsxAttribute(t.jsxIdentifier(componentPathAttributeName), t.stringLiteral(value)));
|
|
64
|
+
}
|
|
93
65
|
}
|
|
94
|
-
|
|
95
|
-
})
|
|
96
|
-
|
|
66
|
+
});
|
|
97
67
|
/**
|
|
98
68
|
* Builds a Vite transform result with a single component-path attribute per JSX element.
|
|
99
69
|
*
|
|
@@ -116,39 +86,26 @@ const makeBabelTagger = (relativeFilename: string): PluginObj => ({
|
|
|
116
86
|
// EFFECT: Effect<ViteTransformResult | null, ComponentTaggerError, never>
|
|
117
87
|
// INVARIANT: errors are surfaced as ComponentTaggerError only
|
|
118
88
|
// COMPLEXITY: O(n)/O(1)
|
|
119
|
-
const runTransform = (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
): Effect.Effect<ViteTransformResult | null, ComponentTaggerError, Path> => {
|
|
124
|
-
const cleanId = stripQuery(id)
|
|
125
|
-
|
|
126
|
-
return pipe(
|
|
127
|
-
relativeFromRoot(rootDir, cleanId),
|
|
128
|
-
Effect.flatMap((relative) =>
|
|
129
|
-
Effect.tryPromise({
|
|
130
|
-
try: () =>
|
|
131
|
-
transformAsync(code, {
|
|
89
|
+
const runTransform = (code, id, rootDir) => {
|
|
90
|
+
const cleanId = stripQuery(id);
|
|
91
|
+
return pipe(relativeFromRoot(rootDir, cleanId), Effect.flatMap((relative) => Effect.tryPromise({
|
|
92
|
+
try: () => transformAsync(code, {
|
|
132
93
|
filename: cleanId,
|
|
133
94
|
babelrc: false,
|
|
134
95
|
configFile: false,
|
|
135
96
|
parserOpts: {
|
|
136
|
-
|
|
137
|
-
|
|
97
|
+
sourceType: "module",
|
|
98
|
+
plugins: ["typescript", "jsx", "decorators-legacy"]
|
|
138
99
|
},
|
|
139
100
|
plugins: [makeBabelTagger(relative)],
|
|
140
101
|
sourceMaps: true
|
|
141
|
-
|
|
102
|
+
}),
|
|
142
103
|
catch: (cause) => {
|
|
143
|
-
|
|
144
|
-
|
|
104
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
105
|
+
return new ComponentTaggerError("Babel transform failed", error);
|
|
145
106
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Effect.map((result) => toViteResult(result))
|
|
149
|
-
)
|
|
150
|
-
}
|
|
151
|
-
|
|
107
|
+
})), Effect.map((result) => toViteResult(result)));
|
|
108
|
+
};
|
|
152
109
|
/**
|
|
153
110
|
* Creates a Vite plugin that injects a single component-path data attribute.
|
|
154
111
|
*
|
|
@@ -170,22 +127,20 @@ const runTransform = (
|
|
|
170
127
|
// EFFECT: Effect<ViteTransformResult | null, ComponentTaggerError, never>
|
|
171
128
|
// INVARIANT: no duplicate path attributes
|
|
172
129
|
// COMPLEXITY: O(n)/O(1)
|
|
173
|
-
export const componentTagger = ()
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
}
|
|
130
|
+
export const componentTagger = () => {
|
|
131
|
+
let resolvedRoot = process.cwd();
|
|
132
|
+
return {
|
|
133
|
+
name: "component-path-tagger",
|
|
134
|
+
enforce: "pre",
|
|
135
|
+
apply: "serve",
|
|
136
|
+
configResolved(config) {
|
|
137
|
+
resolvedRoot = config.root;
|
|
138
|
+
},
|
|
139
|
+
transform(code, id) {
|
|
140
|
+
if (!isJsxFile(id)) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return Effect.runPromise(pipe(runTransform(code, id, resolvedRoot), Effect.provide(NodePathLayer)));
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from "@effect/vitest";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { componentPathAttributeName, formatComponentPathValue, isJsxFile } from "../../src/core/component-path.js";
|
|
4
|
+
describe("component-path", () => {
|
|
5
|
+
it.effect("exposes the path attribute name", () => Effect.sync(() => {
|
|
6
|
+
expect(componentPathAttributeName).toBe("path");
|
|
7
|
+
}));
|
|
8
|
+
it.effect("formats the component path payload", () => Effect.sync(() => {
|
|
9
|
+
const result = formatComponentPathValue("src/App.tsx", 12, 3);
|
|
10
|
+
expect(result).toBe("src/App.tsx:12:3");
|
|
11
|
+
}));
|
|
12
|
+
it.effect("detects JSX/TSX module ids", () => Effect.sync(() => {
|
|
13
|
+
expect(isJsxFile("src/App.tsx")).toBe(true);
|
|
14
|
+
expect(isJsxFile("src/App.jsx?import")).toBe(true);
|
|
15
|
+
expect(isJsxFile("src/App.ts")).toBe(false);
|
|
16
|
+
}));
|
|
17
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// CHANGE: Migrate from Jest to Vitest with mathematical equivalence
|
|
2
|
+
// WHY: Faster execution, native ESM, Effect integration via @effect/vitest
|
|
3
|
+
// QUOTE(ТЗ): "Проект использует Effect + функциональную парадигму"
|
|
4
|
+
// REF: Migration from jest.config.mjs
|
|
5
|
+
// PURITY: SHELL (configuration only)
|
|
6
|
+
// INVARIANT: ∀ test: behavior_jest ≡ behavior_vitest
|
|
7
|
+
// EFFECT: Effect<TestReport, never, TestEnvironment>
|
|
8
|
+
// COMPLEXITY: O(n) test execution where n = |test_files|
|
|
9
|
+
import { defineConfig } from "vitest/config";
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
test: {
|
|
12
|
+
// CHANGE: Native ESM support without experimental flags
|
|
13
|
+
// WHY: Vitest designed for ESM, no need for --experimental-vm-modules
|
|
14
|
+
// INVARIANT: Deterministic test execution without side effects
|
|
15
|
+
globals: false, // IMPORTANT: Use explicit imports for type safety
|
|
16
|
+
environment: "node",
|
|
17
|
+
// CHANGE: Match Jest's test file patterns
|
|
18
|
+
// INVARIANT: Same test discovery as Jest
|
|
19
|
+
include: ["tests/**/*.{test,spec}.ts"],
|
|
20
|
+
exclude: ["node_modules", "dist", "dist-test"],
|
|
21
|
+
// CHANGE: Coverage with 100% threshold for CORE (same as Jest)
|
|
22
|
+
// WHY: CORE must maintain mathematical guarantees via complete coverage
|
|
23
|
+
// INVARIANT: coverage_vitest ≥ coverage_jest ∧ ∀ f ∈ CORE: coverage(f) = 100%
|
|
24
|
+
coverage: {
|
|
25
|
+
provider: "v8", // Faster than babel (istanbul), native V8 coverage
|
|
26
|
+
reporter: ["text", "json", "html"],
|
|
27
|
+
include: ["src/**/*.ts"],
|
|
28
|
+
exclude: [
|
|
29
|
+
"src/**/*.test.ts",
|
|
30
|
+
"src/**/*.spec.ts",
|
|
31
|
+
"src/**/__tests__/**",
|
|
32
|
+
"scripts/**/*.ts"
|
|
33
|
+
],
|
|
34
|
+
// CHANGE: Maintain exact same thresholds as Jest
|
|
35
|
+
// WHY: Enforce 100% coverage for CORE, 10% minimum for SHELL
|
|
36
|
+
// INVARIANT: ∀ f ∈ src/core/**/*.ts: all_metrics(f) = 100%
|
|
37
|
+
// NOTE: Vitest v8 provider collects coverage for all matched files by default
|
|
38
|
+
thresholds: {
|
|
39
|
+
"src/core/**/*.ts": {
|
|
40
|
+
branches: 100,
|
|
41
|
+
functions: 100,
|
|
42
|
+
lines: 100,
|
|
43
|
+
statements: 100
|
|
44
|
+
},
|
|
45
|
+
global: {
|
|
46
|
+
branches: 10,
|
|
47
|
+
functions: 10,
|
|
48
|
+
lines: 10,
|
|
49
|
+
statements: 10
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
// CHANGE: Faster test execution via thread pooling
|
|
54
|
+
// WHY: Vitest uses worker threads by default (faster than Jest's processes)
|
|
55
|
+
// COMPLEXITY: O(n/k) where n = tests, k = worker_count
|
|
56
|
+
// NOTE: Vitest runs tests in parallel by default, no additional config needed
|
|
57
|
+
// CHANGE: Clear mocks between tests (Jest equivalence)
|
|
58
|
+
// WHY: Prevent test contamination, ensure test independence
|
|
59
|
+
// INVARIANT: ∀ test_i, test_j: independent(test_i, test_j) ⇒ no_shared_state
|
|
60
|
+
clearMocks: true,
|
|
61
|
+
mockReset: true,
|
|
62
|
+
restoreMocks: true
|
|
63
|
+
// CHANGE: Disable globals to enforce explicit imports
|
|
64
|
+
// WHY: Type safety, explicit dependencies, functional purity
|
|
65
|
+
// NOTE: Tests must import { describe, it, expect } from "vitest"
|
|
66
|
+
}
|
|
67
|
+
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prover-coder-ai/component-tagger",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Component tagger Vite plugin for JSX metadata",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
7
10
|
"directories": {
|
|
8
11
|
"doc": "doc"
|
|
9
12
|
},
|
package/.jscpd.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"threshold": 0,
|
|
3
|
-
"minTokens": 30,
|
|
4
|
-
"minLines": 5,
|
|
5
|
-
"ignore": [
|
|
6
|
-
"**/node_modules/**",
|
|
7
|
-
"**/build/**",
|
|
8
|
-
"**/dist/**",
|
|
9
|
-
"**/*.min.js",
|
|
10
|
-
"**/reports/**"
|
|
11
|
-
],
|
|
12
|
-
"skipComments": true,
|
|
13
|
-
"ignorePattern": [
|
|
14
|
-
"private readonly \\w+: \\w+;\\s*private readonly \\w+: \\w+;\\s*private \\w+: \\w+ \\| null = null;\\s*private \\w+: \\w+ \\| null = null;"
|
|
15
|
-
]
|
|
16
|
-
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# @prover-coder-ai/component-tagger
|
|
2
|
-
|
|
3
|
-
## 1.0.5
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- chore: automated version bump
|
|
8
|
-
|
|
9
|
-
## 1.0.4
|
|
10
|
-
|
|
11
|
-
### Patch Changes
|
|
12
|
-
|
|
13
|
-
- chore: automated version bump
|
|
14
|
-
|
|
15
|
-
## 1.0.3
|
|
16
|
-
|
|
17
|
-
### Patch Changes
|
|
18
|
-
|
|
19
|
-
- chore: automated version bump
|
|
20
|
-
|
|
21
|
-
## 1.0.2
|
|
22
|
-
|
|
23
|
-
### Patch Changes
|
|
24
|
-
|
|
25
|
-
- chore: automated version bump
|
|
26
|
-
|
|
27
|
-
## 1.0.1
|
|
28
|
-
|
|
29
|
-
### Patch Changes
|
|
30
|
-
|
|
31
|
-
- chore: automated version bump
|
package/biome.json
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
|
|
3
|
-
"vcs": {
|
|
4
|
-
"enabled": false,
|
|
5
|
-
"clientKind": "git",
|
|
6
|
-
"useIgnoreFile": false
|
|
7
|
-
},
|
|
8
|
-
"files": {
|
|
9
|
-
"ignoreUnknown": false
|
|
10
|
-
},
|
|
11
|
-
"formatter": {
|
|
12
|
-
"enabled": false,
|
|
13
|
-
"indentStyle": "tab"
|
|
14
|
-
},
|
|
15
|
-
"assist": {
|
|
16
|
-
"enabled": false
|
|
17
|
-
},
|
|
18
|
-
"linter": {
|
|
19
|
-
"enabled": false,
|
|
20
|
-
"rules": {
|
|
21
|
-
"recommended": false,
|
|
22
|
-
"suspicious": {
|
|
23
|
-
"noExplicitAny": "off"
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
"javascript": {
|
|
28
|
-
"formatter": {
|
|
29
|
-
"enabled": false,
|
|
30
|
-
"quoteStyle": "double",
|
|
31
|
-
"semicolons": "asNeeded"
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
package/eslint.config.mts
DELETED
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
// eslint.config.mjs
|
|
2
|
-
// @ts-check
|
|
3
|
-
import eslint from '@eslint/js';
|
|
4
|
-
import { defineConfig } from 'eslint/config';
|
|
5
|
-
import tseslint from 'typescript-eslint';
|
|
6
|
-
import vitest from "@vitest/eslint-plugin";
|
|
7
|
-
import suggestMembers from "@prover-coder-ai/eslint-plugin-suggest-members";
|
|
8
|
-
import sonarjs from "eslint-plugin-sonarjs";
|
|
9
|
-
import unicorn from "eslint-plugin-unicorn";
|
|
10
|
-
import * as effectEslint from "@effect/eslint-plugin";
|
|
11
|
-
import { fixupPluginRules } from "@eslint/compat";
|
|
12
|
-
import codegen from "eslint-plugin-codegen";
|
|
13
|
-
import importPlugin from "eslint-plugin-import";
|
|
14
|
-
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
|
15
|
-
import sortDestructureKeys from "eslint-plugin-sort-destructure-keys";
|
|
16
|
-
import globals from "globals";
|
|
17
|
-
import eslintCommentsConfigs from "@eslint-community/eslint-plugin-eslint-comments/configs";
|
|
18
|
-
|
|
19
|
-
const codegenPlugin = fixupPluginRules(
|
|
20
|
-
codegen as unknown as Parameters<typeof fixupPluginRules>[0],
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
const noFetchExample = [
|
|
24
|
-
"Пример:",
|
|
25
|
-
" import { FetchHttpClient, HttpClient } from \"@effect/platform\"",
|
|
26
|
-
" import { Effect } from \"effect\"",
|
|
27
|
-
" const program = Effect.gen(function* () {",
|
|
28
|
-
" const client = yield* HttpClient.HttpClient",
|
|
29
|
-
" return yield* client.get(`${api}/robots`)",
|
|
30
|
-
" }).pipe(",
|
|
31
|
-
" Effect.scoped,",
|
|
32
|
-
" Effect.provide(FetchHttpClient.layer)",
|
|
33
|
-
" )",
|
|
34
|
-
].join("\n");
|
|
35
|
-
|
|
36
|
-
export default defineConfig(
|
|
37
|
-
eslint.configs.recommended,
|
|
38
|
-
tseslint.configs.strictTypeChecked,
|
|
39
|
-
effectEslint.configs.dprint,
|
|
40
|
-
suggestMembers.configs.recommended,
|
|
41
|
-
eslintCommentsConfigs.recommended,
|
|
42
|
-
{
|
|
43
|
-
name: "analyzers",
|
|
44
|
-
languageOptions: {
|
|
45
|
-
parser: tseslint.parser,
|
|
46
|
-
globals: { ...globals.node, ...globals.browser },
|
|
47
|
-
parserOptions: {
|
|
48
|
-
projectService: true,
|
|
49
|
-
tsconfigRootDir: import.meta.dirname,
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
plugins: {
|
|
53
|
-
sonarjs,
|
|
54
|
-
unicorn,
|
|
55
|
-
import: fixupPluginRules(importPlugin),
|
|
56
|
-
"sort-destructure-keys": sortDestructureKeys,
|
|
57
|
-
"simple-import-sort": simpleImportSort,
|
|
58
|
-
codegen: codegenPlugin,
|
|
59
|
-
},
|
|
60
|
-
files: ["**/*.ts", '**/*.{test,spec}.{ts,tsx}', '**/tests/**', '**/__tests__/**'],
|
|
61
|
-
settings: {
|
|
62
|
-
"import/parsers": {
|
|
63
|
-
"@typescript-eslint/parser": [".ts", ".tsx"],
|
|
64
|
-
},
|
|
65
|
-
"import/resolver": {
|
|
66
|
-
typescript: {
|
|
67
|
-
alwaysTryTypes: true,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
rules: {
|
|
72
|
-
...sonarjs.configs.recommended.rules,
|
|
73
|
-
...unicorn.configs.recommended.rules,
|
|
74
|
-
"no-restricted-imports": ["error", {
|
|
75
|
-
paths: [
|
|
76
|
-
{
|
|
77
|
-
name: "ts-pattern",
|
|
78
|
-
message: "Use Effect.Match instead of ts-pattern.",
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
name: "zod",
|
|
82
|
-
message: "Use @effect/schema for schemas and validation.",
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
}],
|
|
86
|
-
"codegen/codegen": "error",
|
|
87
|
-
"import/first": "error",
|
|
88
|
-
"import/newline-after-import": "error",
|
|
89
|
-
"import/no-duplicates": "error",
|
|
90
|
-
"import/no-unresolved": "off",
|
|
91
|
-
"import/order": "off",
|
|
92
|
-
"simple-import-sort/imports": "off",
|
|
93
|
-
"sort-destructure-keys/sort-destructure-keys": "error",
|
|
94
|
-
"no-fallthrough": "off",
|
|
95
|
-
"no-irregular-whitespace": "off",
|
|
96
|
-
"object-shorthand": "error",
|
|
97
|
-
"prefer-destructuring": "off",
|
|
98
|
-
"sort-imports": "off",
|
|
99
|
-
"no-unused-vars": "off",
|
|
100
|
-
"prefer-rest-params": "off",
|
|
101
|
-
"prefer-spread": "off",
|
|
102
|
-
"unicorn/prefer-top-level-await": "off",
|
|
103
|
-
"unicorn/prevent-abbreviations": "off",
|
|
104
|
-
"unicorn/no-null": "off",
|
|
105
|
-
complexity: ["error", 8],
|
|
106
|
-
"max-lines-per-function": [
|
|
107
|
-
"error",
|
|
108
|
-
{ max: 50, skipBlankLines: true, skipComments: true },
|
|
109
|
-
],
|
|
110
|
-
"max-params": ["error", 5],
|
|
111
|
-
"max-depth": ["error", 4],
|
|
112
|
-
"max-lines": [
|
|
113
|
-
"error",
|
|
114
|
-
{ max: 300, skipBlankLines: true, skipComments: true },
|
|
115
|
-
],
|
|
116
|
-
|
|
117
|
-
"@typescript-eslint/restrict-template-expressions": ["error", {
|
|
118
|
-
allowNumber: true,
|
|
119
|
-
allowBoolean: true,
|
|
120
|
-
allowNullish: false,
|
|
121
|
-
allowAny: false,
|
|
122
|
-
allowRegExp: false
|
|
123
|
-
}],
|
|
124
|
-
"@eslint-community/eslint-comments/no-use": "error",
|
|
125
|
-
"@eslint-community/eslint-comments/no-unlimited-disable": "error",
|
|
126
|
-
"@eslint-community/eslint-comments/disable-enable-pair": "error",
|
|
127
|
-
"@eslint-community/eslint-comments/no-unused-disable": "error",
|
|
128
|
-
"no-restricted-syntax": [
|
|
129
|
-
"error",
|
|
130
|
-
{
|
|
131
|
-
selector: "TSUnknownKeyword",
|
|
132
|
-
message: "Запрещено 'unknown'.",
|
|
133
|
-
},
|
|
134
|
-
// CHANGE: запрет прямого fetch в коде
|
|
135
|
-
// WHY: enforce Effect-TS httpClient as единственный источник сетевых эффектов
|
|
136
|
-
// QUOTE(ТЗ): "Вместо fetch должно быть всегда написано httpClient от библиотеки Effect-TS"
|
|
137
|
-
// REF: user-msg-1
|
|
138
|
-
// SOURCE: n/a
|
|
139
|
-
// FORMAT THEOREM: ∀call ∈ Calls: callee(call)=fetch → lint_error(call)
|
|
140
|
-
// PURITY: SHELL
|
|
141
|
-
// EFFECT: Effect<never, never, never>
|
|
142
|
-
// INVARIANT: direct fetch calls are forbidden
|
|
143
|
-
// COMPLEXITY: O(1)
|
|
144
|
-
{
|
|
145
|
-
selector: "CallExpression[callee.name='fetch']",
|
|
146
|
-
message: `Запрещён fetch — используй HttpClient (Effect-TS).\n${noFetchExample}`,
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
selector:
|
|
150
|
-
"CallExpression[callee.object.name='window'][callee.property.name='fetch']",
|
|
151
|
-
message: `Запрещён window.fetch — используй HttpClient (Effect-TS).\n${noFetchExample}`,
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
selector:
|
|
155
|
-
"CallExpression[callee.object.name='globalThis'][callee.property.name='fetch']",
|
|
156
|
-
message: `Запрещён globalThis.fetch — используй HttpClient (Effect-TS).\n${noFetchExample}`,
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
selector:
|
|
160
|
-
"CallExpression[callee.object.name='self'][callee.property.name='fetch']",
|
|
161
|
-
message: `Запрещён self.fetch — используй HttpClient (Effect-TS).\n${noFetchExample}`,
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
selector:
|
|
165
|
-
"CallExpression[callee.object.name='global'][callee.property.name='fetch']",
|
|
166
|
-
message: `Запрещён global.fetch — используй HttpClient (Effect-TS).\n${noFetchExample}`,
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
selector: "TryStatement",
|
|
170
|
-
message: "Используй Effect.try / catchAll вместо try/catch в core/app/domain.",
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
selector: "SwitchStatement",
|
|
174
|
-
message: [
|
|
175
|
-
"Switch statements are forbidden in functional programming paradigm.",
|
|
176
|
-
"How to fix: Use Effect.Match instead.",
|
|
177
|
-
"Example:",
|
|
178
|
-
" import { Match } from 'effect';",
|
|
179
|
-
" type Item = { type: 'this' } | { type: 'that' };",
|
|
180
|
-
" const result = Match.value(item).pipe(",
|
|
181
|
-
" Match.when({ type: 'this' }, (it) => processThis(it)),",
|
|
182
|
-
" Match.when({ type: 'that' }, (it) => processThat(it)),",
|
|
183
|
-
" Match.exhaustive,",
|
|
184
|
-
" );",
|
|
185
|
-
].join("\n"),
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
selector: 'CallExpression[callee.name="require"]',
|
|
189
|
-
message: "Avoid using require(). Use ES6 imports instead.",
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
selector: "ThrowStatement > Literal:not([value=/^\\w+Error:/])",
|
|
193
|
-
message:
|
|
194
|
-
'Do not throw string literals or non-Error objects. Throw new Error("...") instead.',
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
selector:
|
|
198
|
-
"FunctionDeclaration[async=true], FunctionExpression[async=true], ArrowFunctionExpression[async=true]",
|
|
199
|
-
message:
|
|
200
|
-
"Запрещён async/await — используй Effect.gen / Effect.tryPromise.",
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
selector: "NewExpression[callee.name='Promise']",
|
|
204
|
-
message:
|
|
205
|
-
"Запрещён new Promise — используй Effect.async / Effect.tryPromise.",
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
selector: "CallExpression[callee.object.name='Promise']",
|
|
209
|
-
message:
|
|
210
|
-
"Запрещены Promise.* — используй комбинаторы Effect (all, forEach, etc.).",
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
selector: "CallExpression[callee.property.name='push'] > SpreadElement.arguments",
|
|
214
|
-
message: "Do not use spread arguments in Array.push",
|
|
215
|
-
},
|
|
216
|
-
],
|
|
217
|
-
"no-throw-literal": "error",
|
|
218
|
-
"@typescript-eslint/no-restricted-types": [
|
|
219
|
-
"error",
|
|
220
|
-
{
|
|
221
|
-
types: {
|
|
222
|
-
unknown: {
|
|
223
|
-
message:
|
|
224
|
-
"Не используем 'unknown'. Уточни тип или наведи порядок в источнике данных.",
|
|
225
|
-
},
|
|
226
|
-
Promise: {
|
|
227
|
-
message: "Запрещён Promise — используй Effect.Effect<A, E, R>.",
|
|
228
|
-
suggest: ["Effect.Effect"],
|
|
229
|
-
},
|
|
230
|
-
"Promise<*>": {
|
|
231
|
-
message:
|
|
232
|
-
"Запрещён Promise<T> — используй Effect.Effect<T, E, R>.",
|
|
233
|
-
suggest: ["Effect.Effect<T, E, R>"],
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
"@typescript-eslint/use-unknown-in-catch-callback-variable": "off",
|
|
239
|
-
// "no-throw-literal": "off",
|
|
240
|
-
"@typescript-eslint/only-throw-error": [
|
|
241
|
-
"error",
|
|
242
|
-
{ allowThrowingUnknown: false, allowThrowingAny: false },
|
|
243
|
-
],
|
|
244
|
-
"@typescript-eslint/array-type": ["warn", {
|
|
245
|
-
default: "generic",
|
|
246
|
-
readonly: "generic"
|
|
247
|
-
}],
|
|
248
|
-
"@typescript-eslint/member-delimiter-style": 0,
|
|
249
|
-
"@typescript-eslint/no-non-null-assertion": "off",
|
|
250
|
-
"@typescript-eslint/ban-types": "off",
|
|
251
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
252
|
-
"@typescript-eslint/no-empty-interface": "off",
|
|
253
|
-
"@typescript-eslint/consistent-type-imports": "warn",
|
|
254
|
-
"@typescript-eslint/no-unused-vars": ["error", {
|
|
255
|
-
argsIgnorePattern: "^_",
|
|
256
|
-
varsIgnorePattern: "^_"
|
|
257
|
-
}],
|
|
258
|
-
"@typescript-eslint/ban-ts-comment": "off",
|
|
259
|
-
"@typescript-eslint/camelcase": "off",
|
|
260
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
261
|
-
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
262
|
-
"@typescript-eslint/interface-name-prefix": "off",
|
|
263
|
-
"@typescript-eslint/no-array-constructor": "off",
|
|
264
|
-
"@typescript-eslint/no-use-before-define": "off",
|
|
265
|
-
"@typescript-eslint/no-namespace": "off",
|
|
266
|
-
"@effect/dprint": ["error", {
|
|
267
|
-
config: {
|
|
268
|
-
indentWidth: 2,
|
|
269
|
-
lineWidth: 120,
|
|
270
|
-
semiColons: "asi",
|
|
271
|
-
quoteStyle: "alwaysDouble",
|
|
272
|
-
trailingCommas: "never",
|
|
273
|
-
operatorPosition: "maintain",
|
|
274
|
-
"arrowFunction.useParentheses": "force"
|
|
275
|
-
}
|
|
276
|
-
}]
|
|
277
|
-
}
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
files: ['**/*.{test,spec}.{ts,tsx}', 'tests/**', '**/__tests__/**'],
|
|
281
|
-
...vitest.configs.all,
|
|
282
|
-
languageOptions: {
|
|
283
|
-
globals: {
|
|
284
|
-
...vitest.environments.env.globals,
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
rules: {
|
|
288
|
-
// Allow eslint-disable/enable comments in test files for fine-grained control
|
|
289
|
-
'@eslint-community/eslint-comments/no-use': 'off',
|
|
290
|
-
// Disable line count limit for E2E tests that contain multiple test cases
|
|
291
|
-
'max-lines-per-function': 'off',
|
|
292
|
-
// `it.effect` is not recognized by sonar rule; disable to avoid false positives
|
|
293
|
-
'sonarjs/no-empty-test-file': 'off',
|
|
294
|
-
},
|
|
295
|
-
},
|
|
296
|
-
|
|
297
|
-
// 3) Для JS-файлов отключим типо-зависимые проверки
|
|
298
|
-
{
|
|
299
|
-
files: ['**/*.{js,cjs,mjs}'],
|
|
300
|
-
extends: [tseslint.configs.disableTypeChecked],
|
|
301
|
-
},
|
|
302
|
-
|
|
303
|
-
// 4) Глобальные игноры
|
|
304
|
-
{ ignores: ['dist/**', 'build/**', 'coverage/**', '**/dist/**'] },
|
|
305
|
-
);
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
// CHANGE: add Effect-TS compliance lint profile
|
|
2
|
-
// WHY: detect current deviations from strict Effect-TS guidance
|
|
3
|
-
// QUOTE(TZ): n/a
|
|
4
|
-
// REF: AGENTS.md Effect-TS compliance checks
|
|
5
|
-
// SOURCE: n/a
|
|
6
|
-
// PURITY: SHELL
|
|
7
|
-
// EFFECT: eslint config
|
|
8
|
-
// INVARIANT: config only flags explicit policy deviations
|
|
9
|
-
// COMPLEXITY: O(1)/O(1)
|
|
10
|
-
import eslintComments from "@eslint-community/eslint-plugin-eslint-comments"
|
|
11
|
-
import globals from "globals"
|
|
12
|
-
import tseslint from "typescript-eslint"
|
|
13
|
-
|
|
14
|
-
const restrictedImports = [
|
|
15
|
-
{
|
|
16
|
-
name: "node:fs",
|
|
17
|
-
message: "Use @effect/platform FileSystem instead of node:fs."
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: "fs",
|
|
21
|
-
message: "Use @effect/platform FileSystem instead of fs."
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: "node:fs/promises",
|
|
25
|
-
message: "Use @effect/platform FileSystem instead of node:fs/promises."
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
name: "node:path/posix",
|
|
29
|
-
message: "Use @effect/platform Path instead of node:path/posix."
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
name: "node:path",
|
|
33
|
-
message: "Use @effect/platform Path instead of node:path."
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
name: "path",
|
|
37
|
-
message: "Use @effect/platform Path instead of path."
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: "node:child_process",
|
|
41
|
-
message: "Use @effect/platform Command instead of node:child_process."
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
name: "child_process",
|
|
45
|
-
message: "Use @effect/platform Command instead of child_process."
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: "node:process",
|
|
49
|
-
message: "Use @effect/platform Runtime instead of node:process."
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
name: "process",
|
|
53
|
-
message: "Use @effect/platform Runtime instead of process."
|
|
54
|
-
}
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
const restrictedSyntaxBase = [
|
|
58
|
-
{
|
|
59
|
-
selector: "SwitchStatement",
|
|
60
|
-
message: "Switch is forbidden. Use Match.exhaustive."
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
selector: "TryStatement",
|
|
64
|
-
message: "Avoid try/catch in product code. Use Effect.try / Effect.catch*."
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
selector: "AwaitExpression",
|
|
68
|
-
message: "Avoid await. Use Effect.gen / Effect.flatMap."
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
selector: "FunctionDeclaration[async=true], FunctionExpression[async=true], ArrowFunctionExpression[async=true]",
|
|
72
|
-
message: "Avoid async/await. Use Effect.gen / Effect.tryPromise."
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
selector: "NewExpression[callee.name='Promise']",
|
|
76
|
-
message: "Avoid new Promise. Use Effect.async / Effect.tryPromise."
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
selector: "CallExpression[callee.object.name='Promise']",
|
|
80
|
-
message: "Avoid Promise.*. Use Effect combinators."
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
selector: "CallExpression[callee.name='require']",
|
|
84
|
-
message: "Avoid require(). Use ES module imports."
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
selector: "TSAsExpression",
|
|
88
|
-
message: "Casting is only allowed in src/core/axioms.ts."
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
selector: "TSTypeAssertion",
|
|
92
|
-
message: "Casting is only allowed in src/core/axioms.ts."
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
selector: "CallExpression[callee.name='makeFilesystemService']",
|
|
96
|
-
message: "Do not instantiate FilesystemService directly. Provide Layer and access via Tag."
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
selector: "CallExpression[callee.property.name='catchAll']",
|
|
100
|
-
message: "Avoid catchAll that discards typed errors; map or propagate explicitly."
|
|
101
|
-
}
|
|
102
|
-
]
|
|
103
|
-
|
|
104
|
-
const restrictedSyntaxCore = [
|
|
105
|
-
...restrictedSyntaxBase,
|
|
106
|
-
{
|
|
107
|
-
selector: "TSUnknownKeyword",
|
|
108
|
-
message: "unknown is allowed only at shell boundaries with decoding."
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
selector: "CallExpression[callee.property.name='runSyncExit']",
|
|
112
|
-
message: "Effect.runSyncExit is shell-only. Move to a runner."
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
selector: "CallExpression[callee.property.name='runSync']",
|
|
116
|
-
message: "Effect.runSync is shell-only. Move to a runner."
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
selector: "CallExpression[callee.property.name='runPromise']",
|
|
120
|
-
message: "Effect.runPromise is shell-only. Move to a runner."
|
|
121
|
-
}
|
|
122
|
-
]
|
|
123
|
-
|
|
124
|
-
const restrictedSyntaxCoreNoAs = [
|
|
125
|
-
...restrictedSyntaxCore.filter((rule) =>
|
|
126
|
-
rule.selector !== "TSAsExpression" && rule.selector !== "TSTypeAssertion"
|
|
127
|
-
)
|
|
128
|
-
]
|
|
129
|
-
|
|
130
|
-
const restrictedSyntaxBaseNoServiceFactory = [
|
|
131
|
-
...restrictedSyntaxBase.filter((rule) =>
|
|
132
|
-
rule.selector !== "CallExpression[callee.name='makeFilesystemService']"
|
|
133
|
-
)
|
|
134
|
-
]
|
|
135
|
-
|
|
136
|
-
export default tseslint.config(
|
|
137
|
-
{
|
|
138
|
-
name: "effect-ts-compliance-check",
|
|
139
|
-
files: ["src/**/*.ts", "scripts/**/*.ts"],
|
|
140
|
-
languageOptions: {
|
|
141
|
-
parser: tseslint.parser,
|
|
142
|
-
globals: { ...globals.node }
|
|
143
|
-
},
|
|
144
|
-
plugins: {
|
|
145
|
-
"@typescript-eslint": tseslint.plugin,
|
|
146
|
-
"eslint-comments": eslintComments
|
|
147
|
-
},
|
|
148
|
-
rules: {
|
|
149
|
-
"no-console": "error",
|
|
150
|
-
"no-restricted-imports": ["error", {
|
|
151
|
-
paths: restrictedImports,
|
|
152
|
-
patterns: [
|
|
153
|
-
{
|
|
154
|
-
group: ["node:*"],
|
|
155
|
-
message: "Do not import from node:* directly. Use @effect/platform-node or @effect/platform services."
|
|
156
|
-
}
|
|
157
|
-
]
|
|
158
|
-
}],
|
|
159
|
-
"no-restricted-syntax": ["error", ...restrictedSyntaxBase],
|
|
160
|
-
"@typescript-eslint/no-explicit-any": "error",
|
|
161
|
-
"@typescript-eslint/ban-ts-comment": ["error", {
|
|
162
|
-
"ts-ignore": true,
|
|
163
|
-
"ts-nocheck": true,
|
|
164
|
-
"ts-check": false,
|
|
165
|
-
"ts-expect-error": true
|
|
166
|
-
}],
|
|
167
|
-
"@typescript-eslint/no-restricted-types": ["error", {
|
|
168
|
-
types: {
|
|
169
|
-
Promise: {
|
|
170
|
-
message: "Avoid Promise in types. Use Effect.Effect<A, E, R>."
|
|
171
|
-
},
|
|
172
|
-
"Promise<*>": {
|
|
173
|
-
message: "Avoid Promise<T>. Use Effect.Effect<T, E, R>."
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}],
|
|
177
|
-
"eslint-comments/no-use": "error",
|
|
178
|
-
"eslint-comments/no-unlimited-disable": "error",
|
|
179
|
-
"eslint-comments/disable-enable-pair": "error",
|
|
180
|
-
"eslint-comments/no-unused-disable": "error"
|
|
181
|
-
}
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
name: "effect-ts-compliance-core",
|
|
185
|
-
files: ["src/core/**/*.ts"],
|
|
186
|
-
rules: {
|
|
187
|
-
"no-restricted-syntax": ["error", ...restrictedSyntaxCore],
|
|
188
|
-
"no-restricted-imports": ["error", {
|
|
189
|
-
paths: restrictedImports,
|
|
190
|
-
patterns: [
|
|
191
|
-
{
|
|
192
|
-
group: [
|
|
193
|
-
"../shell/**",
|
|
194
|
-
"../../shell/**",
|
|
195
|
-
"../../../shell/**",
|
|
196
|
-
"./shell/**",
|
|
197
|
-
"src/shell/**",
|
|
198
|
-
"shell/**"
|
|
199
|
-
],
|
|
200
|
-
message: "CORE must not import from SHELL."
|
|
201
|
-
}
|
|
202
|
-
]
|
|
203
|
-
}]
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
name: "effect-ts-compliance-axioms",
|
|
208
|
-
files: ["src/core/axioms.ts"],
|
|
209
|
-
rules: {
|
|
210
|
-
"no-restricted-syntax": ["error", ...restrictedSyntaxCoreNoAs]
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
{
|
|
214
|
-
name: "effect-ts-compliance-filesystem-service",
|
|
215
|
-
files: ["src/shell/services/filesystem.ts"],
|
|
216
|
-
rules: {
|
|
217
|
-
"no-restricted-syntax": ["error", ...restrictedSyntaxBaseNoServiceFactory]
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
)
|
package/linter.config.json
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"priorityLevels": [
|
|
3
|
-
{
|
|
4
|
-
"level": 1,
|
|
5
|
-
"name": "Critical Compiler Errors",
|
|
6
|
-
"rules": [
|
|
7
|
-
"ts(2835)",
|
|
8
|
-
"ts(2307)",
|
|
9
|
-
"@prover-coder-ai/suggest-members/suggest-members",
|
|
10
|
-
"@prover-coder-ai/suggest-members/suggest-imports",
|
|
11
|
-
"@prover-coder-ai/suggest-members/suggest-module-paths",
|
|
12
|
-
"@prover-coder-ai/suggest-members/suggest-exports",
|
|
13
|
-
"@prover-coder-ai/suggest-members/suggest-missing-names",
|
|
14
|
-
"@typescript-eslint/no-explicit-any"
|
|
15
|
-
]
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
"level": 2,
|
|
19
|
-
"name": "Critical Compiler Errors",
|
|
20
|
-
"rules": ["all"]
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"level": 3,
|
|
24
|
-
"name": "Critical Compiler Errors (Code must follow Clean Code and best practices)",
|
|
25
|
-
"rules": ["max-lines-per-function", "max-lines"]
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"level": 4,
|
|
29
|
-
"name": "Critical Compiler Errors (Code must follow Clean Code and best practices)",
|
|
30
|
-
"rules": ["complexity", "max-params", "max-depth"]
|
|
31
|
-
}
|
|
32
|
-
]
|
|
33
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "@effect/vitest"
|
|
2
|
-
import { Effect } from "effect"
|
|
3
|
-
|
|
4
|
-
import { componentPathAttributeName, formatComponentPathValue, isJsxFile } from "../../src/core/component-path.js"
|
|
5
|
-
|
|
6
|
-
describe("component-path", () => {
|
|
7
|
-
it.effect("exposes the path attribute name", () =>
|
|
8
|
-
Effect.sync(() => {
|
|
9
|
-
expect(componentPathAttributeName).toBe("path")
|
|
10
|
-
}))
|
|
11
|
-
|
|
12
|
-
it.effect("formats the component path payload", () =>
|
|
13
|
-
Effect.sync(() => {
|
|
14
|
-
const result = formatComponentPathValue("src/App.tsx", 12, 3)
|
|
15
|
-
expect(result).toBe("src/App.tsx:12:3")
|
|
16
|
-
}))
|
|
17
|
-
|
|
18
|
-
it.effect("detects JSX/TSX module ids", () =>
|
|
19
|
-
Effect.sync(() => {
|
|
20
|
-
expect(isJsxFile("src/App.tsx")).toBe(true)
|
|
21
|
-
expect(isJsxFile("src/App.jsx?import")).toBe(true)
|
|
22
|
-
expect(isJsxFile("src/App.ts")).toBe(false)
|
|
23
|
-
}))
|
|
24
|
-
})
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"rootDir": ".",
|
|
5
|
-
"outDir": "dist",
|
|
6
|
-
"types": ["vitest"],
|
|
7
|
-
"baseUrl": ".",
|
|
8
|
-
"paths": {
|
|
9
|
-
"@/*": ["src/*"]
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
"include": [
|
|
13
|
-
"src/**/*",
|
|
14
|
-
"tests/**/*",
|
|
15
|
-
"vitest.config.ts"
|
|
16
|
-
],
|
|
17
|
-
"exclude": ["dist", "node_modules"]
|
|
18
|
-
}
|
package/vitest.config.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
// CHANGE: Migrate from Jest to Vitest with mathematical equivalence
|
|
2
|
-
// WHY: Faster execution, native ESM, Effect integration via @effect/vitest
|
|
3
|
-
// QUOTE(ТЗ): "Проект использует Effect + функциональную парадигму"
|
|
4
|
-
// REF: Migration from jest.config.mjs
|
|
5
|
-
// PURITY: SHELL (configuration only)
|
|
6
|
-
// INVARIANT: ∀ test: behavior_jest ≡ behavior_vitest
|
|
7
|
-
// EFFECT: Effect<TestReport, never, TestEnvironment>
|
|
8
|
-
// COMPLEXITY: O(n) test execution where n = |test_files|
|
|
9
|
-
|
|
10
|
-
import { defineConfig } from "vitest/config"
|
|
11
|
-
|
|
12
|
-
export default defineConfig({
|
|
13
|
-
test: {
|
|
14
|
-
// CHANGE: Native ESM support without experimental flags
|
|
15
|
-
// WHY: Vitest designed for ESM, no need for --experimental-vm-modules
|
|
16
|
-
// INVARIANT: Deterministic test execution without side effects
|
|
17
|
-
globals: false, // IMPORTANT: Use explicit imports for type safety
|
|
18
|
-
environment: "node",
|
|
19
|
-
|
|
20
|
-
// CHANGE: Match Jest's test file patterns
|
|
21
|
-
// INVARIANT: Same test discovery as Jest
|
|
22
|
-
include: ["tests/**/*.{test,spec}.ts"],
|
|
23
|
-
exclude: ["node_modules", "dist", "dist-test"],
|
|
24
|
-
|
|
25
|
-
// CHANGE: Coverage with 100% threshold for CORE (same as Jest)
|
|
26
|
-
// WHY: CORE must maintain mathematical guarantees via complete coverage
|
|
27
|
-
// INVARIANT: coverage_vitest ≥ coverage_jest ∧ ∀ f ∈ CORE: coverage(f) = 100%
|
|
28
|
-
coverage: {
|
|
29
|
-
provider: "v8", // Faster than babel (istanbul), native V8 coverage
|
|
30
|
-
reporter: ["text", "json", "html"],
|
|
31
|
-
include: ["src/**/*.ts"],
|
|
32
|
-
exclude: [
|
|
33
|
-
"src/**/*.test.ts",
|
|
34
|
-
"src/**/*.spec.ts",
|
|
35
|
-
"src/**/__tests__/**",
|
|
36
|
-
"scripts/**/*.ts"
|
|
37
|
-
],
|
|
38
|
-
// CHANGE: Maintain exact same thresholds as Jest
|
|
39
|
-
// WHY: Enforce 100% coverage for CORE, 10% minimum for SHELL
|
|
40
|
-
// INVARIANT: ∀ f ∈ src/core/**/*.ts: all_metrics(f) = 100%
|
|
41
|
-
// NOTE: Vitest v8 provider collects coverage for all matched files by default
|
|
42
|
-
thresholds: {
|
|
43
|
-
"src/core/**/*.ts": {
|
|
44
|
-
branches: 100,
|
|
45
|
-
functions: 100,
|
|
46
|
-
lines: 100,
|
|
47
|
-
statements: 100
|
|
48
|
-
},
|
|
49
|
-
global: {
|
|
50
|
-
branches: 10,
|
|
51
|
-
functions: 10,
|
|
52
|
-
lines: 10,
|
|
53
|
-
statements: 10
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
// CHANGE: Faster test execution via thread pooling
|
|
59
|
-
// WHY: Vitest uses worker threads by default (faster than Jest's processes)
|
|
60
|
-
// COMPLEXITY: O(n/k) where n = tests, k = worker_count
|
|
61
|
-
// NOTE: Vitest runs tests in parallel by default, no additional config needed
|
|
62
|
-
|
|
63
|
-
// CHANGE: Clear mocks between tests (Jest equivalence)
|
|
64
|
-
// WHY: Prevent test contamination, ensure test independence
|
|
65
|
-
// INVARIANT: ∀ test_i, test_j: independent(test_i, test_j) ⇒ no_shared_state
|
|
66
|
-
clearMocks: true,
|
|
67
|
-
mockReset: true,
|
|
68
|
-
restoreMocks: true
|
|
69
|
-
// CHANGE: Disable globals to enforce explicit imports
|
|
70
|
-
// WHY: Type safety, explicit dependencies, functional purity
|
|
71
|
-
// NOTE: Tests must import { describe, it, expect } from "vitest"
|
|
72
|
-
}
|
|
73
|
-
})
|