@prover-coder-ai/component-tagger 1.0.21 → 1.0.24
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/babel.cjs +80 -0
- package/dist/core/jsx-tagger.d.ts +70 -0
- package/dist/core/jsx-tagger.js +117 -0
- package/dist/core/path-service.d.ts +38 -0
- package/dist/core/path-service.js +55 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/shell/babel-plugin.d.ts +59 -0
- package/dist/shell/babel-plugin.js +102 -0
- package/dist/shell/component-tagger.js +12 -44
- package/package.json +17 -3
package/babel.cjs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommonJS entry point for the component-tagger Babel plugin.
|
|
3
|
+
*
|
|
4
|
+
* This file provides a CommonJS-compatible wrapper for the Babel plugin,
|
|
5
|
+
* allowing it to be used with Next.js and other tools that require CJS modules.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // .babelrc
|
|
9
|
+
* {
|
|
10
|
+
* "presets": ["next/babel"],
|
|
11
|
+
* "plugins": ["@prover-coder-ai/component-tagger/babel"]
|
|
12
|
+
* }
|
|
13
|
+
*/
|
|
14
|
+
// CHANGE: provide CommonJS entry point for Babel plugin.
|
|
15
|
+
// WHY: Babel configuration often requires CommonJS modules.
|
|
16
|
+
// REF: issue-12
|
|
17
|
+
// FORMAT THEOREM: forall require: require(babel.cjs) -> PluginFactory
|
|
18
|
+
// PURITY: SHELL
|
|
19
|
+
// EFFECT: n/a
|
|
20
|
+
// INVARIANT: exports match Babel plugin signature
|
|
21
|
+
// COMPLEXITY: O(1)/O(1)
|
|
22
|
+
|
|
23
|
+
const path = require("node:path")
|
|
24
|
+
|
|
25
|
+
const componentPathAttributeName = "path"
|
|
26
|
+
const jsxFilePattern = /\.(tsx|jsx)(\?.*)?$/u
|
|
27
|
+
|
|
28
|
+
const isJsxFile = (id) => jsxFilePattern.test(id)
|
|
29
|
+
|
|
30
|
+
const formatComponentPathValue = (relativeFilename, line, column) =>
|
|
31
|
+
`${relativeFilename}:${line}:${column}`
|
|
32
|
+
|
|
33
|
+
const attrExists = (node, attrName, t) =>
|
|
34
|
+
node.attributes.some(
|
|
35
|
+
(attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name: attrName })
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
module.exports = function componentTaggerBabelPlugin({ types: t }) {
|
|
39
|
+
return {
|
|
40
|
+
name: "component-path-babel-tagger",
|
|
41
|
+
visitor: {
|
|
42
|
+
JSXOpeningElement(nodePath, state) {
|
|
43
|
+
const { node } = nodePath
|
|
44
|
+
const filename = state.filename
|
|
45
|
+
|
|
46
|
+
// Skip if no filename
|
|
47
|
+
if (filename === undefined) {
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Skip if no location info
|
|
52
|
+
if (node.loc === null || node.loc === undefined) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Skip if not a JSX/TSX file
|
|
57
|
+
if (!isJsxFile(filename)) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Skip if already has path attribute
|
|
62
|
+
if (attrExists(node, componentPathAttributeName, t)) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Compute relative path from root
|
|
67
|
+
const opts = state.opts || {}
|
|
68
|
+
const rootDir = opts.rootDir || state.cwd || process.cwd()
|
|
69
|
+
const relativeFilename = path.relative(rootDir, filename)
|
|
70
|
+
|
|
71
|
+
const { column, line } = node.loc.start
|
|
72
|
+
const value = formatComponentPathValue(relativeFilename, line, column)
|
|
73
|
+
|
|
74
|
+
node.attributes.push(
|
|
75
|
+
t.jsxAttribute(t.jsxIdentifier(componentPathAttributeName), t.stringLiteral(value))
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { types as t, Visitor } from "@babel/core";
|
|
2
|
+
/**
|
|
3
|
+
* Context required for JSX tagging.
|
|
4
|
+
*
|
|
5
|
+
* @pure true
|
|
6
|
+
*/
|
|
7
|
+
export type JsxTaggerContext = {
|
|
8
|
+
/**
|
|
9
|
+
* Relative file path from the project root.
|
|
10
|
+
*/
|
|
11
|
+
readonly relativeFilename: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Checks if a JSX attribute with the given name already exists on the element.
|
|
15
|
+
*
|
|
16
|
+
* @param node - JSX opening element to check.
|
|
17
|
+
* @param attrName - Name of the attribute to look for.
|
|
18
|
+
* @returns true if attribute exists, false otherwise.
|
|
19
|
+
*
|
|
20
|
+
* @pure true
|
|
21
|
+
* @invariant returns true iff attribute with exact name exists
|
|
22
|
+
* @complexity O(n) where n = number of attributes
|
|
23
|
+
*/
|
|
24
|
+
export declare const attrExists: (node: t.JSXOpeningElement, attrName: string, types: typeof t) => boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Creates a JSX attribute with the component path value.
|
|
27
|
+
*
|
|
28
|
+
* @param relativeFilename - Relative path to the file.
|
|
29
|
+
* @param line - 1-based line number.
|
|
30
|
+
* @param column - 0-based column number.
|
|
31
|
+
* @param types - Babel types module.
|
|
32
|
+
* @returns JSX attribute node with the path value.
|
|
33
|
+
*
|
|
34
|
+
* @pure true
|
|
35
|
+
* @invariant attribute name is always componentPathAttributeName
|
|
36
|
+
* @complexity O(1)
|
|
37
|
+
*/
|
|
38
|
+
export declare const createPathAttribute: (relativeFilename: string, line: number, column: number, types: typeof t) => t.JSXAttribute;
|
|
39
|
+
/**
|
|
40
|
+
* Processes a single JSX opening element and adds path attribute if needed.
|
|
41
|
+
*
|
|
42
|
+
* This is the unified business logic for tagging JSX elements with source location.
|
|
43
|
+
* Both the Vite plugin and standalone Babel plugin use this function.
|
|
44
|
+
*
|
|
45
|
+
* @param node - JSX opening element to process.
|
|
46
|
+
* @param context - Tagging context with relative filename.
|
|
47
|
+
* @param types - Babel types module.
|
|
48
|
+
* @returns true if attribute was added, false if skipped.
|
|
49
|
+
*
|
|
50
|
+
* @pure false (mutates node)
|
|
51
|
+
* @invariant each JSX element has at most one path attribute after processing
|
|
52
|
+
* @complexity O(n) where n = number of existing attributes
|
|
53
|
+
*/
|
|
54
|
+
export declare const processJsxElement: (node: t.JSXOpeningElement, context: JsxTaggerContext, types: typeof t) => boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Creates a Babel visitor for JSX elements that uses the unified tagging logic.
|
|
57
|
+
*
|
|
58
|
+
* This is the shared visitor factory used by both:
|
|
59
|
+
* - Vite plugin (componentTagger) - passes relative filename directly
|
|
60
|
+
* - Standalone Babel plugin - computes relative filename from state
|
|
61
|
+
*
|
|
62
|
+
* @param getContext - Function to extract context from Babel state.
|
|
63
|
+
* @param types - Babel types module.
|
|
64
|
+
* @returns Babel visitor object for JSXOpeningElement.
|
|
65
|
+
*
|
|
66
|
+
* @pure true (returns immutable visitor object)
|
|
67
|
+
* @invariant visitor applies processJsxElement to all JSX opening elements
|
|
68
|
+
* @complexity O(1) for visitor creation
|
|
69
|
+
*/
|
|
70
|
+
export declare const createJsxTaggerVisitor: <TState>(getContext: (state: TState) => JsxTaggerContext | null, types: typeof t) => Visitor<TState>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { componentPathAttributeName, formatComponentPathValue } from "./component-path.js";
|
|
2
|
+
/**
|
|
3
|
+
* Checks if a JSX attribute with the given name already exists on the element.
|
|
4
|
+
*
|
|
5
|
+
* @param node - JSX opening element to check.
|
|
6
|
+
* @param attrName - Name of the attribute to look for.
|
|
7
|
+
* @returns true if attribute exists, false otherwise.
|
|
8
|
+
*
|
|
9
|
+
* @pure true
|
|
10
|
+
* @invariant returns true iff attribute with exact name exists
|
|
11
|
+
* @complexity O(n) where n = number of attributes
|
|
12
|
+
*/
|
|
13
|
+
// CHANGE: extract attribute existence check as a pure utility.
|
|
14
|
+
// WHY: enable reuse across Vite and Babel plugin implementations.
|
|
15
|
+
// REF: issue-12 (unified interface request)
|
|
16
|
+
// FORMAT THEOREM: ∀ node, name: attrExists(node, name) ↔ ∃ attr ∈ node.attributes: attr.name = name
|
|
17
|
+
// PURITY: CORE
|
|
18
|
+
// EFFECT: n/a
|
|
19
|
+
// INVARIANT: predicate is deterministic for fixed inputs
|
|
20
|
+
// COMPLEXITY: O(n)/O(1)
|
|
21
|
+
export const attrExists = (node, attrName, types) => node.attributes.some((attr) => types.isJSXAttribute(attr) && types.isJSXIdentifier(attr.name, { name: attrName }));
|
|
22
|
+
/**
|
|
23
|
+
* Creates a JSX attribute with the component path value.
|
|
24
|
+
*
|
|
25
|
+
* @param relativeFilename - Relative path to the file.
|
|
26
|
+
* @param line - 1-based line number.
|
|
27
|
+
* @param column - 0-based column number.
|
|
28
|
+
* @param types - Babel types module.
|
|
29
|
+
* @returns JSX attribute node with the path value.
|
|
30
|
+
*
|
|
31
|
+
* @pure true
|
|
32
|
+
* @invariant attribute name is always componentPathAttributeName
|
|
33
|
+
* @complexity O(1)
|
|
34
|
+
*/
|
|
35
|
+
// CHANGE: extract attribute creation as a pure factory.
|
|
36
|
+
// WHY: single point for attribute creation ensures consistency.
|
|
37
|
+
// REF: issue-12 (unified interface request)
|
|
38
|
+
// FORMAT THEOREM: ∀ f, l, c: createPathAttribute(f, l, c) = JSXAttribute(path, f:l:c)
|
|
39
|
+
// PURITY: CORE
|
|
40
|
+
// EFFECT: n/a
|
|
41
|
+
// INVARIANT: output format is always path:line:column
|
|
42
|
+
// COMPLEXITY: O(1)/O(1)
|
|
43
|
+
export const createPathAttribute = (relativeFilename, line, column, types) => {
|
|
44
|
+
const value = formatComponentPathValue(relativeFilename, line, column);
|
|
45
|
+
return types.jsxAttribute(types.jsxIdentifier(componentPathAttributeName), types.stringLiteral(value));
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Processes a single JSX opening element and adds path attribute if needed.
|
|
49
|
+
*
|
|
50
|
+
* This is the unified business logic for tagging JSX elements with source location.
|
|
51
|
+
* Both the Vite plugin and standalone Babel plugin use this function.
|
|
52
|
+
*
|
|
53
|
+
* @param node - JSX opening element to process.
|
|
54
|
+
* @param context - Tagging context with relative filename.
|
|
55
|
+
* @param types - Babel types module.
|
|
56
|
+
* @returns true if attribute was added, false if skipped.
|
|
57
|
+
*
|
|
58
|
+
* @pure false (mutates node)
|
|
59
|
+
* @invariant each JSX element has at most one path attribute after processing
|
|
60
|
+
* @complexity O(n) where n = number of existing attributes
|
|
61
|
+
*/
|
|
62
|
+
// CHANGE: extract unified JSX element processing logic.
|
|
63
|
+
// WHY: satisfy user request for single business logic shared by Vite and Babel.
|
|
64
|
+
// QUOTE(TZ): "А ты можешь сделать что бы бизнес логика оставалось одной? Ну типо переиспользуй код с vite версии на babel"
|
|
65
|
+
// REF: issue-12-comment (unified interface request)
|
|
66
|
+
// FORMAT THEOREM: ∀ jsx ∈ JSXOpeningElement: processElement(jsx) → tagged(jsx) ∨ skipped(jsx)
|
|
67
|
+
// PURITY: SHELL (mutates AST)
|
|
68
|
+
// EFFECT: AST mutation
|
|
69
|
+
// INVARIANT: idempotent - processing same element twice produces same result
|
|
70
|
+
// COMPLEXITY: O(n)/O(1)
|
|
71
|
+
export const processJsxElement = (node, context, types) => {
|
|
72
|
+
// Skip if no location info
|
|
73
|
+
if (node.loc === null || node.loc === undefined) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
// Skip if already has path attribute (idempotency)
|
|
77
|
+
if (attrExists(node, componentPathAttributeName, types)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const { column, line } = node.loc.start;
|
|
81
|
+
const attr = createPathAttribute(context.relativeFilename, line, column, types);
|
|
82
|
+
node.attributes.push(attr);
|
|
83
|
+
return true;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Creates a Babel visitor for JSX elements that uses the unified tagging logic.
|
|
87
|
+
*
|
|
88
|
+
* This is the shared visitor factory used by both:
|
|
89
|
+
* - Vite plugin (componentTagger) - passes relative filename directly
|
|
90
|
+
* - Standalone Babel plugin - computes relative filename from state
|
|
91
|
+
*
|
|
92
|
+
* @param getContext - Function to extract context from Babel state.
|
|
93
|
+
* @param types - Babel types module.
|
|
94
|
+
* @returns Babel visitor object for JSXOpeningElement.
|
|
95
|
+
*
|
|
96
|
+
* @pure true (returns immutable visitor object)
|
|
97
|
+
* @invariant visitor applies processJsxElement to all JSX opening elements
|
|
98
|
+
* @complexity O(1) for visitor creation
|
|
99
|
+
*/
|
|
100
|
+
// CHANGE: create shared visitor factory for both plugin types.
|
|
101
|
+
// WHY: single unified interface as requested by user.
|
|
102
|
+
// QUOTE(TZ): "Сделай единный интерфейс для этого"
|
|
103
|
+
// REF: issue-12-comment (unified interface request)
|
|
104
|
+
// FORMAT THEOREM: ∀ visitor = createVisitor(ctx): visitor processes all JSX elements uniformly
|
|
105
|
+
// PURITY: CORE
|
|
106
|
+
// EFFECT: n/a (visitor application has effects)
|
|
107
|
+
// INVARIANT: visitor behavior is consistent across plugin implementations
|
|
108
|
+
// COMPLEXITY: O(1)/O(1)
|
|
109
|
+
export const createJsxTaggerVisitor = (getContext, types) => ({
|
|
110
|
+
JSXOpeningElement(nodePath, state) {
|
|
111
|
+
const context = getContext(state);
|
|
112
|
+
if (context === null) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
processJsxElement(nodePath.node, context, types);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path service utilities using Effect-TS.
|
|
3
|
+
*
|
|
4
|
+
* PURITY: SHELL (uses Effect for file system operations)
|
|
5
|
+
* PURPOSE: Centralize Effect-based path operations to avoid code duplication.
|
|
6
|
+
*/
|
|
7
|
+
import { Path } from "@effect/platform/Path";
|
|
8
|
+
import { Effect } from "effect";
|
|
9
|
+
/**
|
|
10
|
+
* Computes relative path using Effect's Path service.
|
|
11
|
+
*
|
|
12
|
+
* @param rootDir - Root directory for relative path calculation.
|
|
13
|
+
* @param absolutePath - Absolute file path to convert.
|
|
14
|
+
* @returns Effect that produces relative path string.
|
|
15
|
+
*
|
|
16
|
+
* @pure false
|
|
17
|
+
* @effect Path service access
|
|
18
|
+
* @invariant result is a valid relative path
|
|
19
|
+
* @complexity O(n)/O(1) where n = path length
|
|
20
|
+
*/
|
|
21
|
+
export declare const relativeFromRoot: (rootDir: string, absolutePath: string) => Effect.Effect<string, never, Path>;
|
|
22
|
+
/**
|
|
23
|
+
* Synchronously computes relative path using Effect's Path service.
|
|
24
|
+
*
|
|
25
|
+
* @param rootDir - Root directory for relative path calculation.
|
|
26
|
+
* @param absolutePath - Absolute file path to convert.
|
|
27
|
+
* @returns Relative path string.
|
|
28
|
+
*
|
|
29
|
+
* @pure false
|
|
30
|
+
* @effect Path service access (synchronous)
|
|
31
|
+
* @invariant result is a valid relative path
|
|
32
|
+
* @complexity O(n)/O(1) where n = path length
|
|
33
|
+
*/
|
|
34
|
+
export declare const computeRelativePath: (rootDir: string, absolutePath: string) => string;
|
|
35
|
+
/**
|
|
36
|
+
* Re-export NodePathLayer for plugins that need to provide it explicitly.
|
|
37
|
+
*/
|
|
38
|
+
export { layer as NodePathLayer } from "@effect/platform-node/NodePath";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path service utilities using Effect-TS.
|
|
3
|
+
*
|
|
4
|
+
* PURITY: SHELL (uses Effect for file system operations)
|
|
5
|
+
* PURPOSE: Centralize Effect-based path operations to avoid code duplication.
|
|
6
|
+
*/
|
|
7
|
+
import { layer as NodePathLayer } from "@effect/platform-node/NodePath";
|
|
8
|
+
import { Path } from "@effect/platform/Path";
|
|
9
|
+
import { Effect, pipe } from "effect";
|
|
10
|
+
/**
|
|
11
|
+
* Computes relative path using Effect's Path service.
|
|
12
|
+
*
|
|
13
|
+
* @param rootDir - Root directory for relative path calculation.
|
|
14
|
+
* @param absolutePath - Absolute file path to convert.
|
|
15
|
+
* @returns Effect that produces relative path string.
|
|
16
|
+
*
|
|
17
|
+
* @pure false
|
|
18
|
+
* @effect Path service access
|
|
19
|
+
* @invariant result is a valid relative path
|
|
20
|
+
* @complexity O(n)/O(1) where n = path length
|
|
21
|
+
*/
|
|
22
|
+
// CHANGE: extract common path calculation logic from both plugins.
|
|
23
|
+
// WHY: eliminate code duplication detected by vibecode-linter.
|
|
24
|
+
// REF: lint error DUPLICATE #1
|
|
25
|
+
// FORMAT THEOREM: ∀ (root, path): relativePath(root, path) = Path.relative(root, path)
|
|
26
|
+
// PURITY: SHELL
|
|
27
|
+
// EFFECT: Effect<string, never, Path>
|
|
28
|
+
// INVARIANT: always returns a valid relative path for valid inputs
|
|
29
|
+
// COMPLEXITY: O(n)/O(1)
|
|
30
|
+
export const relativeFromRoot = (rootDir, absolutePath) => pipe(Path, Effect.map((pathService) => pathService.relative(rootDir, absolutePath)));
|
|
31
|
+
/**
|
|
32
|
+
* Synchronously computes relative path using Effect's Path service.
|
|
33
|
+
*
|
|
34
|
+
* @param rootDir - Root directory for relative path calculation.
|
|
35
|
+
* @param absolutePath - Absolute file path to convert.
|
|
36
|
+
* @returns Relative path string.
|
|
37
|
+
*
|
|
38
|
+
* @pure false
|
|
39
|
+
* @effect Path service access (synchronous)
|
|
40
|
+
* @invariant result is a valid relative path
|
|
41
|
+
* @complexity O(n)/O(1) where n = path length
|
|
42
|
+
*/
|
|
43
|
+
// CHANGE: provide synchronous variant for Babel plugin (which requires sync operations).
|
|
44
|
+
// WHY: Babel plugins must operate synchronously; Effect.runSync bridges Effect-style code.
|
|
45
|
+
// REF: babel-plugin.ts:65-71
|
|
46
|
+
// FORMAT THEOREM: ∀ (root, path): computeRelativePath(root, path) = runSync(relativePath(root, path))
|
|
47
|
+
// PURITY: SHELL
|
|
48
|
+
// EFFECT: Path service (executed synchronously)
|
|
49
|
+
// INVARIANT: always returns a valid string for valid inputs
|
|
50
|
+
// COMPLEXITY: O(n)/O(1)
|
|
51
|
+
export const computeRelativePath = (rootDir, absolutePath) => pipe(relativeFromRoot(rootDir, absolutePath), Effect.provide(NodePathLayer), Effect.runSync);
|
|
52
|
+
/**
|
|
53
|
+
* Re-export NodePathLayer for plugins that need to provide it explicitly.
|
|
54
|
+
*/
|
|
55
|
+
export { layer as NodePathLayer } from "@effect/platform-node/NodePath";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export { componentPathAttributeName, formatComponentPathValue, isJsxFile } from "./core/component-path.js";
|
|
2
|
+
export { attrExists, createJsxTaggerVisitor, createPathAttribute, type JsxTaggerContext, processJsxElement } from "./core/jsx-tagger.js";
|
|
3
|
+
export { componentTaggerBabelPlugin, type ComponentTaggerBabelPluginOptions } from "./shell/babel-plugin.js";
|
|
2
4
|
export { componentTagger } from "./shell/component-tagger.js";
|
package/dist/index.js
CHANGED
|
@@ -9,4 +9,6 @@
|
|
|
9
9
|
// INVARIANT: exports remain stable for consumers
|
|
10
10
|
// COMPLEXITY: O(1)/O(1)
|
|
11
11
|
export { componentPathAttributeName, formatComponentPathValue, isJsxFile } from "./core/component-path.js";
|
|
12
|
+
export { attrExists, createJsxTaggerVisitor, createPathAttribute, processJsxElement } from "./core/jsx-tagger.js";
|
|
13
|
+
export { componentTaggerBabelPlugin } from "./shell/babel-plugin.js";
|
|
12
14
|
export { componentTagger } from "./shell/component-tagger.js";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type PluginObj } from "@babel/core";
|
|
2
|
+
/**
|
|
3
|
+
* Options for the component path Babel plugin.
|
|
4
|
+
*/
|
|
5
|
+
export type ComponentTaggerBabelPluginOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* Root directory for computing relative paths.
|
|
8
|
+
* Defaults to process.cwd().
|
|
9
|
+
*/
|
|
10
|
+
readonly rootDir?: string;
|
|
11
|
+
};
|
|
12
|
+
type BabelState = {
|
|
13
|
+
readonly filename?: string;
|
|
14
|
+
readonly cwd?: string;
|
|
15
|
+
readonly opts?: ComponentTaggerBabelPluginOptions;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Creates a Babel plugin that injects component path attributes into JSX elements.
|
|
19
|
+
*
|
|
20
|
+
* This plugin is designed to be used standalone with build tools that support
|
|
21
|
+
* Babel plugins directly (e.g., Next.js via .babelrc).
|
|
22
|
+
*
|
|
23
|
+
* Uses the unified JSX tagger core that is shared with the Vite plugin,
|
|
24
|
+
* ensuring consistent behavior across both build tools.
|
|
25
|
+
*
|
|
26
|
+
* @returns Babel plugin object
|
|
27
|
+
*
|
|
28
|
+
* @pure false
|
|
29
|
+
* @effect Babel AST transformation
|
|
30
|
+
* @invariant JSX elements are tagged with path attribute containing file:line:column
|
|
31
|
+
* @complexity O(n) where n = number of JSX elements
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // .babelrc or babel.config.js
|
|
35
|
+
* {
|
|
36
|
+
* "plugins": ["@prover-coder-ai/component-tagger/babel"]
|
|
37
|
+
* }
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Next.js .babelrc with options
|
|
41
|
+
* {
|
|
42
|
+
* "presets": ["next/babel"],
|
|
43
|
+
* "env": {
|
|
44
|
+
* "development": {
|
|
45
|
+
* "plugins": [
|
|
46
|
+
* ["@prover-coder-ai/component-tagger/babel", { "rootDir": "/custom/root" }]
|
|
47
|
+
* ]
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
*/
|
|
52
|
+
export declare const componentTaggerBabelPlugin: () => PluginObj<BabelState>;
|
|
53
|
+
/**
|
|
54
|
+
* Default export for Babel plugin resolution.
|
|
55
|
+
*
|
|
56
|
+
* Babel resolves plugins by looking for a default export function that
|
|
57
|
+
* returns a plugin object when called.
|
|
58
|
+
*/
|
|
59
|
+
export default componentTaggerBabelPlugin;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { types as t } from "@babel/core";
|
|
2
|
+
import { isJsxFile } from "../core/component-path.js";
|
|
3
|
+
import { createJsxTaggerVisitor } from "../core/jsx-tagger.js";
|
|
4
|
+
import { computeRelativePath } from "../core/path-service.js";
|
|
5
|
+
/**
|
|
6
|
+
* Creates context for JSX tagging from Babel state.
|
|
7
|
+
*
|
|
8
|
+
* @param state - Babel plugin state containing filename and options.
|
|
9
|
+
* @returns JsxTaggerContext or null if context cannot be created.
|
|
10
|
+
*
|
|
11
|
+
* @pure true
|
|
12
|
+
* @invariant returns null when filename is undefined or not a JSX file
|
|
13
|
+
* @complexity O(n) where n = path length
|
|
14
|
+
*/
|
|
15
|
+
// CHANGE: extract context creation for standalone Babel plugin.
|
|
16
|
+
// WHY: enable unified visitor to work with Babel state.
|
|
17
|
+
// QUOTE(TZ): "А ты можешь сделать что бы бизнес логика оставалось одной?"
|
|
18
|
+
// REF: issue-12-comment (unified interface request)
|
|
19
|
+
// FORMAT THEOREM: ∀ state: getContext(state) = context ↔ isValidState(state)
|
|
20
|
+
// PURITY: CORE
|
|
21
|
+
// EFFECT: n/a
|
|
22
|
+
// INVARIANT: context contains valid relative path
|
|
23
|
+
// COMPLEXITY: O(n)/O(1)
|
|
24
|
+
const getContextFromState = (state) => {
|
|
25
|
+
const filename = state.filename;
|
|
26
|
+
// Skip if no filename
|
|
27
|
+
if (filename === undefined) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
// Skip if not a JSX/TSX file
|
|
31
|
+
if (!isJsxFile(filename)) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
// Compute relative path from root using Effect's Path service
|
|
35
|
+
const rootDir = state.opts?.rootDir ?? state.cwd ?? "";
|
|
36
|
+
const relativeFilename = computeRelativePath(rootDir, filename);
|
|
37
|
+
return { relativeFilename };
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Creates a Babel plugin that injects component path attributes into JSX elements.
|
|
41
|
+
*
|
|
42
|
+
* This plugin is designed to be used standalone with build tools that support
|
|
43
|
+
* Babel plugins directly (e.g., Next.js via .babelrc).
|
|
44
|
+
*
|
|
45
|
+
* Uses the unified JSX tagger core that is shared with the Vite plugin,
|
|
46
|
+
* ensuring consistent behavior across both build tools.
|
|
47
|
+
*
|
|
48
|
+
* @returns Babel plugin object
|
|
49
|
+
*
|
|
50
|
+
* @pure false
|
|
51
|
+
* @effect Babel AST transformation
|
|
52
|
+
* @invariant JSX elements are tagged with path attribute containing file:line:column
|
|
53
|
+
* @complexity O(n) where n = number of JSX elements
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* // .babelrc or babel.config.js
|
|
57
|
+
* {
|
|
58
|
+
* "plugins": ["@prover-coder-ai/component-tagger/babel"]
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Next.js .babelrc with options
|
|
63
|
+
* {
|
|
64
|
+
* "presets": ["next/babel"],
|
|
65
|
+
* "env": {
|
|
66
|
+
* "development": {
|
|
67
|
+
* "plugins": [
|
|
68
|
+
* ["@prover-coder-ai/component-tagger/babel", { "rootDir": "/custom/root" }]
|
|
69
|
+
* ]
|
|
70
|
+
* }
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
*/
|
|
74
|
+
// CHANGE: use unified JSX tagger visitor from core module.
|
|
75
|
+
// WHY: share business logic between Vite and Babel plugins as requested.
|
|
76
|
+
// QUOTE(TZ): "А ты можешь сделать что бы бизнес логика оставалось одной? Ну типо переиспользуй код с vite версии на babel. Сделай единный интерфейс для этого"
|
|
77
|
+
// REF: issue-12-comment (unified interface request)
|
|
78
|
+
// SOURCE: https://babeljs.io/docs/plugins
|
|
79
|
+
// FORMAT THEOREM: forall jsx in JSXOpeningElement: transform(jsx) -> tagged(jsx, path)
|
|
80
|
+
// PURITY: SHELL
|
|
81
|
+
// EFFECT: Babel AST mutation
|
|
82
|
+
// INVARIANT: each JSX opening element has at most one path attribute
|
|
83
|
+
// COMPLEXITY: O(n)/O(1)
|
|
84
|
+
export const componentTaggerBabelPlugin = () => ({
|
|
85
|
+
name: "component-path-babel-tagger",
|
|
86
|
+
visitor: createJsxTaggerVisitor(getContextFromState, t)
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* Default export for Babel plugin resolution.
|
|
90
|
+
*
|
|
91
|
+
* Babel resolves plugins by looking for a default export function that
|
|
92
|
+
* returns a plugin object when called.
|
|
93
|
+
*/
|
|
94
|
+
// CHANGE: provide default export for standard Babel plugin resolution.
|
|
95
|
+
// WHY: Babel expects plugins to be functions that return plugin objects.
|
|
96
|
+
// REF: issue-12
|
|
97
|
+
// FORMAT THEOREM: forall babel: require(plugin) -> callable -> PluginObj
|
|
98
|
+
// PURITY: SHELL
|
|
99
|
+
// EFFECT: n/a
|
|
100
|
+
// INVARIANT: default export matches Babel plugin signature
|
|
101
|
+
// COMPLEXITY: O(1)/O(1)
|
|
102
|
+
export default componentTaggerBabelPlugin;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
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
2
|
import { Effect, pipe } from "effect";
|
|
5
|
-
import {
|
|
3
|
+
import { isJsxFile } from "../core/component-path.js";
|
|
4
|
+
import { createJsxTaggerVisitor } from "../core/jsx-tagger.js";
|
|
5
|
+
import { NodePathLayer, relativeFromRoot } from "../core/path-service.js";
|
|
6
6
|
class ComponentTaggerError extends Error {
|
|
7
7
|
cause;
|
|
8
8
|
_tag = "ComponentTaggerError";
|
|
@@ -15,18 +15,6 @@ const stripQuery = (id) => {
|
|
|
15
15
|
const queryIndex = id.indexOf("?");
|
|
16
16
|
return queryIndex === -1 ? id : id.slice(0, queryIndex);
|
|
17
17
|
};
|
|
18
|
-
// CHANGE: compute relative paths from the resolved Vite root instead of process.cwd().
|
|
19
|
-
// WHY: keep component paths stable across monorepos and custom Vite roots.
|
|
20
|
-
// QUOTE(TZ): "Сам компонент должен быть в текущем app но вот что бы его протестировать надо создать ещё один проект который наш текущий апп будет подключать"
|
|
21
|
-
// REF: user-2026-01-14-frontend-consumer
|
|
22
|
-
// SOURCE: n/a
|
|
23
|
-
// FORMAT THEOREM: forall p in Path: relative(root, p) = r -> resolve(root, r) = p
|
|
24
|
-
// PURITY: SHELL
|
|
25
|
-
// EFFECT: Effect<string, never, Path>
|
|
26
|
-
// INVARIANT: output is deterministic for a fixed root
|
|
27
|
-
// COMPLEXITY: O(n)/O(1)
|
|
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
18
|
const toViteResult = (result) => {
|
|
31
19
|
if (result === null || result.code === null || result.code === undefined) {
|
|
32
20
|
return null;
|
|
@@ -37,33 +25,13 @@ const toViteResult = (result) => {
|
|
|
37
25
|
map: result.map ?? null
|
|
38
26
|
};
|
|
39
27
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// EFFECT: Effect<ViteTransformResult | null, ComponentTaggerError, Path>
|
|
48
|
-
// INVARIANT: each JSX opening element has at most one path attribute
|
|
49
|
-
// COMPLEXITY: O(n)/O(1), n = number of JSX elements
|
|
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
|
-
}
|
|
65
|
-
}
|
|
66
|
-
});
|
|
28
|
+
const makeBabelTagger = (relativeFilename) => {
|
|
29
|
+
const context = { relativeFilename };
|
|
30
|
+
return {
|
|
31
|
+
name: "component-path-babel-tagger",
|
|
32
|
+
visitor: createJsxTaggerVisitor(() => context, t)
|
|
33
|
+
};
|
|
34
|
+
};
|
|
67
35
|
/**
|
|
68
36
|
* Builds a Vite transform result with a single component-path attribute per JSX element.
|
|
69
37
|
*
|
|
@@ -78,7 +46,7 @@ const makeBabelTagger = (relativeFilename) => ({
|
|
|
78
46
|
*/
|
|
79
47
|
// CHANGE: wrap Babel transform in Effect for typed errors and controlled effects.
|
|
80
48
|
// WHY: satisfy the shell-only effect boundary while avoiding async/await.
|
|
81
|
-
// QUOTE(TZ): "
|
|
49
|
+
// QUOTE(TZ): "Сам компонент должен быть в текущем app но вот что бы его протестировать надо создать ещё один проект который наш текущий апп будет подключать"
|
|
82
50
|
// REF: user-2026-01-14-frontend-consumer
|
|
83
51
|
// SOURCE: n/a
|
|
84
52
|
// FORMAT THEOREM: forall c in Code: transform(c) = r -> r is tagged or null
|
|
@@ -119,7 +87,7 @@ const runTransform = (code, id, rootDir) => {
|
|
|
119
87
|
*/
|
|
120
88
|
// CHANGE: expose a Vite plugin that tags JSX with only path.
|
|
121
89
|
// WHY: reduce attribute noise while keeping full path metadata.
|
|
122
|
-
// QUOTE(TZ): "
|
|
90
|
+
// QUOTE(TZ): "Сам компонент должен быть в текущем app но вот что бы его протестировать надо создать ещё один проект который наш текущий апп будет подключать"
|
|
123
91
|
// REF: user-2026-01-14-frontend-consumer
|
|
124
92
|
// SOURCE: n/a
|
|
125
93
|
// FORMAT THEOREM: forall id: isJsxFile(id) -> transform(id) adds component-path
|
package/package.json
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prover-coder-ai/component-tagger",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Component tagger Vite plugin for JSX metadata",
|
|
3
|
+
"version": "1.0.24",
|
|
4
|
+
"description": "Component tagger Vite plugin and Babel plugin for JSX metadata",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./babel": {
|
|
13
|
+
"types": "./dist/shell/babel-plugin.d.ts",
|
|
14
|
+
"require": "./babel.cjs",
|
|
15
|
+
"import": "./dist/shell/babel-plugin.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
7
18
|
"files": [
|
|
8
|
-
"dist"
|
|
19
|
+
"dist",
|
|
20
|
+
"babel.cjs"
|
|
9
21
|
],
|
|
10
22
|
"directories": {
|
|
11
23
|
"doc": "doc"
|
|
@@ -17,6 +29,8 @@
|
|
|
17
29
|
"keywords": [
|
|
18
30
|
"effect",
|
|
19
31
|
"vite",
|
|
32
|
+
"babel",
|
|
33
|
+
"nextjs",
|
|
20
34
|
"plugin",
|
|
21
35
|
"tagger"
|
|
22
36
|
],
|