@prover-coder-ai/component-tagger 1.0.22 → 1.0.25
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 +99 -3
- package/babel.cjs +81 -0
- package/dist/core/component-path.d.ts +1 -1
- package/dist/core/component-path.js +7 -7
- package/dist/core/jsx-tagger.d.ts +75 -0
- package/dist/core/jsx-tagger.js +118 -0
- package/dist/core/path-service.d.ts +38 -0
- package/dist/core/path-service.js +55 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/shell/babel-plugin.d.ts +64 -0
- package/dist/shell/babel-plugin.js +103 -0
- package/dist/shell/component-tagger.d.ts +12 -1
- package/dist/shell/component-tagger.js +23 -53
- package/package.json +17 -3
package/README.md
CHANGED
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
# @prover-coder-ai/component-tagger
|
|
2
2
|
|
|
3
|
-
Vite plugin that adds a
|
|
3
|
+
Vite and Babel plugin that adds a `data-path` attribute to every JSX opening tag, enabling component source location tracking.
|
|
4
4
|
|
|
5
|
-
Example output
|
|
5
|
+
## Example output
|
|
6
6
|
|
|
7
7
|
```html
|
|
8
|
-
<h1 path="src/App.tsx:22:4">Hello</h1>
|
|
8
|
+
<h1 data-path="src/App.tsx:22:4">Hello</h1>
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
Format: `<relative-file-path>:<line>:<column>`
|
|
12
12
|
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- ✅ **Idempotent**: adds `data-path` only if it doesn't already exist
|
|
16
|
+
- ✅ **HTML5 compliant**: uses standard `data-*` attributes
|
|
17
|
+
- ✅ **Configurable**: customize the attribute name via options
|
|
18
|
+
- ✅ **Dual plugin support**: works with both Vite and Babel
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install @prover-coder-ai/component-tagger
|
|
24
|
+
```
|
|
25
|
+
|
|
13
26
|
## Usage
|
|
14
27
|
|
|
28
|
+
### Vite Plugin
|
|
29
|
+
|
|
15
30
|
```ts
|
|
16
31
|
import { defineConfig, type PluginOption } from "vite"
|
|
17
32
|
import { componentTagger } from "@prover-coder-ai/component-tagger"
|
|
@@ -23,3 +38,84 @@ export default defineConfig(({ mode }) => {
|
|
|
23
38
|
return { plugins }
|
|
24
39
|
})
|
|
25
40
|
```
|
|
41
|
+
|
|
42
|
+
**With custom attribute name:**
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const plugins = [
|
|
46
|
+
isDevelopment && componentTagger({ attributeName: "data-component-path" })
|
|
47
|
+
].filter(Boolean) as PluginOption[]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Babel Plugin (e.g., Next.js)
|
|
51
|
+
|
|
52
|
+
Add to your `.babelrc`:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"presets": ["next/babel"],
|
|
57
|
+
"env": {
|
|
58
|
+
"development": {
|
|
59
|
+
"plugins": ["@prover-coder-ai/component-tagger/babel"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**With options:**
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"presets": ["next/babel"],
|
|
70
|
+
"env": {
|
|
71
|
+
"development": {
|
|
72
|
+
"plugins": [
|
|
73
|
+
[
|
|
74
|
+
"@prover-coder-ai/component-tagger/babel",
|
|
75
|
+
{
|
|
76
|
+
"rootDir": "/custom/root",
|
|
77
|
+
"attributeName": "data-component-path"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Options
|
|
87
|
+
|
|
88
|
+
### Vite Plugin Options
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
type ComponentTaggerOptions = {
|
|
92
|
+
/**
|
|
93
|
+
* Name of the attribute to add to JSX elements.
|
|
94
|
+
* @default "data-path"
|
|
95
|
+
*/
|
|
96
|
+
attributeName?: string
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Babel Plugin Options
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
type ComponentTaggerBabelPluginOptions = {
|
|
104
|
+
/**
|
|
105
|
+
* Root directory for computing relative paths.
|
|
106
|
+
* @default process.cwd()
|
|
107
|
+
*/
|
|
108
|
+
rootDir?: string
|
|
109
|
+
/**
|
|
110
|
+
* Name of the attribute to add to JSX elements.
|
|
111
|
+
* @default "data-path"
|
|
112
|
+
*/
|
|
113
|
+
attributeName?: string
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Behavior Guarantees
|
|
118
|
+
|
|
119
|
+
- **Idempotency**: If `data-path` (or custom attribute) already exists on an element, no duplicate is added
|
|
120
|
+
- **Default attribute**: `data-path` is used when no `attributeName` is specified
|
|
121
|
+
- **Standard compliance**: Uses HTML5 `data-*` custom attributes by default
|
package/babel.cjs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
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 with configurable attributeName.
|
|
15
|
+
// WHY: Babel configuration often requires CommonJS modules; support custom attribute names.
|
|
16
|
+
// REF: issue-12, issue-14
|
|
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 = "data-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
|
+
// Compute relative path from root and get attribute name
|
|
62
|
+
const opts = state.opts || {}
|
|
63
|
+
const rootDir = opts.rootDir || state.cwd || process.cwd()
|
|
64
|
+
const attributeName = opts.attributeName || componentPathAttributeName
|
|
65
|
+
const relativeFilename = path.relative(rootDir, filename)
|
|
66
|
+
|
|
67
|
+
// Skip if already has the specified attribute (idempotency)
|
|
68
|
+
if (attrExists(node, attributeName, t)) {
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const { column, line } = node.loc.start
|
|
73
|
+
const value = formatComponentPathValue(relativeFilename, line, column)
|
|
74
|
+
|
|
75
|
+
node.attributes.push(
|
|
76
|
+
t.jsxAttribute(t.jsxIdentifier(attributeName), t.stringLiteral(value))
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
const jsxFilePattern = /\.(tsx|jsx)(\?.*)?$/u;
|
|
2
|
-
// CHANGE:
|
|
3
|
-
// WHY:
|
|
4
|
-
// QUOTE(
|
|
5
|
-
// REF:
|
|
6
|
-
// SOURCE:
|
|
7
|
-
// FORMAT THEOREM: forall a in AttributeName: a = "path"
|
|
2
|
+
// CHANGE: rename attribute from "path" to "data-path" for HTML5 compliance.
|
|
3
|
+
// WHY: data-* attributes are standard HTML5 custom data attributes, improving compatibility.
|
|
4
|
+
// QUOTE(issue-14): "Rename attribute path → data-path (breaking change)"
|
|
5
|
+
// REF: issue-14
|
|
6
|
+
// SOURCE: https://html.spec.whatwg.org/multipage/dom.html#custom-data-attribute
|
|
7
|
+
// FORMAT THEOREM: forall a in AttributeName: a = "data-path"
|
|
8
8
|
// PURITY: CORE
|
|
9
9
|
// EFFECT: n/a
|
|
10
10
|
// INVARIANT: attribute name remains stable across transforms
|
|
11
11
|
// COMPLEXITY: O(1)/O(1)
|
|
12
|
-
export const componentPathAttributeName = "path";
|
|
12
|
+
export const componentPathAttributeName = "data-path";
|
|
13
13
|
/**
|
|
14
14
|
* Checks whether the Vite id represents a JSX or TSX module.
|
|
15
15
|
*
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
* Name of the attribute to add (defaults to "data-path").
|
|
14
|
+
*/
|
|
15
|
+
readonly attributeName: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Checks if a JSX attribute with the given name already exists on the element.
|
|
19
|
+
*
|
|
20
|
+
* @param node - JSX opening element to check.
|
|
21
|
+
* @param attrName - Name of the attribute to look for.
|
|
22
|
+
* @returns true if attribute exists, false otherwise.
|
|
23
|
+
*
|
|
24
|
+
* @pure true
|
|
25
|
+
* @invariant returns true iff attribute with exact name exists
|
|
26
|
+
* @complexity O(n) where n = number of attributes
|
|
27
|
+
*/
|
|
28
|
+
export declare const attrExists: (node: t.JSXOpeningElement, attrName: string, types: typeof t) => boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a JSX attribute with the component path value.
|
|
31
|
+
*
|
|
32
|
+
* @param attributeName - Name of the attribute to create.
|
|
33
|
+
* @param relativeFilename - Relative path to the file.
|
|
34
|
+
* @param line - 1-based line number.
|
|
35
|
+
* @param column - 0-based column number.
|
|
36
|
+
* @param types - Babel types module.
|
|
37
|
+
* @returns JSX attribute node with the path value.
|
|
38
|
+
*
|
|
39
|
+
* @pure true
|
|
40
|
+
* @invariant attribute name matches the provided attributeName parameter
|
|
41
|
+
* @complexity O(1)
|
|
42
|
+
*/
|
|
43
|
+
export declare const createPathAttribute: (attributeName: string, relativeFilename: string, line: number, column: number, types: typeof t) => t.JSXAttribute;
|
|
44
|
+
/**
|
|
45
|
+
* Processes a single JSX opening element and adds path attribute if needed.
|
|
46
|
+
*
|
|
47
|
+
* This is the unified business logic for tagging JSX elements with source location.
|
|
48
|
+
* Both the Vite plugin and standalone Babel plugin use this function.
|
|
49
|
+
*
|
|
50
|
+
* @param node - JSX opening element to process.
|
|
51
|
+
* @param context - Tagging context with relative filename and attribute name.
|
|
52
|
+
* @param types - Babel types module.
|
|
53
|
+
* @returns true if attribute was added, false if skipped.
|
|
54
|
+
*
|
|
55
|
+
* @pure false (mutates node)
|
|
56
|
+
* @invariant each JSX element has at most one instance of the specified attribute after processing
|
|
57
|
+
* @complexity O(n) where n = number of existing attributes
|
|
58
|
+
*/
|
|
59
|
+
export declare const processJsxElement: (node: t.JSXOpeningElement, context: JsxTaggerContext, types: typeof t) => boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Creates a Babel visitor for JSX elements that uses the unified tagging logic.
|
|
62
|
+
*
|
|
63
|
+
* This is the shared visitor factory used by both:
|
|
64
|
+
* - Vite plugin (componentTagger) - passes relative filename directly
|
|
65
|
+
* - Standalone Babel plugin - computes relative filename from state
|
|
66
|
+
*
|
|
67
|
+
* @param getContext - Function to extract context from Babel state.
|
|
68
|
+
* @param types - Babel types module.
|
|
69
|
+
* @returns Babel visitor object for JSXOpeningElement.
|
|
70
|
+
*
|
|
71
|
+
* @pure true (returns immutable visitor object)
|
|
72
|
+
* @invariant visitor applies processJsxElement to all JSX opening elements
|
|
73
|
+
* @complexity O(1) for visitor creation
|
|
74
|
+
*/
|
|
75
|
+
export declare const createJsxTaggerVisitor: <TState>(getContext: (state: TState) => JsxTaggerContext | null, types: typeof t) => Visitor<TState>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { 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 attributeName - Name of the attribute to create.
|
|
26
|
+
* @param relativeFilename - Relative path to the file.
|
|
27
|
+
* @param line - 1-based line number.
|
|
28
|
+
* @param column - 0-based column number.
|
|
29
|
+
* @param types - Babel types module.
|
|
30
|
+
* @returns JSX attribute node with the path value.
|
|
31
|
+
*
|
|
32
|
+
* @pure true
|
|
33
|
+
* @invariant attribute name matches the provided attributeName parameter
|
|
34
|
+
* @complexity O(1)
|
|
35
|
+
*/
|
|
36
|
+
// CHANGE: add attributeName parameter for configurable attribute names.
|
|
37
|
+
// WHY: support customizable attribute names while maintaining default "data-path".
|
|
38
|
+
// REF: issue-14 (add attributeName option)
|
|
39
|
+
// FORMAT THEOREM: ∀ n, f, l, c: createPathAttribute(n, f, l, c) = JSXAttribute(n, f:l:c)
|
|
40
|
+
// PURITY: CORE
|
|
41
|
+
// EFFECT: n/a
|
|
42
|
+
// INVARIANT: output format is always path:line:column with configurable attribute name
|
|
43
|
+
// COMPLEXITY: O(1)/O(1)
|
|
44
|
+
export const createPathAttribute = (attributeName, relativeFilename, line, column, types) => {
|
|
45
|
+
const value = formatComponentPathValue(relativeFilename, line, column);
|
|
46
|
+
return types.jsxAttribute(types.jsxIdentifier(attributeName), types.stringLiteral(value));
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Processes a single JSX opening element and adds path attribute if needed.
|
|
50
|
+
*
|
|
51
|
+
* This is the unified business logic for tagging JSX elements with source location.
|
|
52
|
+
* Both the Vite plugin and standalone Babel plugin use this function.
|
|
53
|
+
*
|
|
54
|
+
* @param node - JSX opening element to process.
|
|
55
|
+
* @param context - Tagging context with relative filename and attribute name.
|
|
56
|
+
* @param types - Babel types module.
|
|
57
|
+
* @returns true if attribute was added, false if skipped.
|
|
58
|
+
*
|
|
59
|
+
* @pure false (mutates node)
|
|
60
|
+
* @invariant each JSX element has at most one instance of the specified attribute after processing
|
|
61
|
+
* @complexity O(n) where n = number of existing attributes
|
|
62
|
+
*/
|
|
63
|
+
// CHANGE: extract unified JSX element processing logic.
|
|
64
|
+
// WHY: satisfy user request for single business logic shared by Vite and Babel.
|
|
65
|
+
// QUOTE(TZ): "А ты можешь сделать что бы бизнес логика оставалось одной? Ну типо переиспользуй код с vite версии на babel"
|
|
66
|
+
// REF: issue-12-comment (unified interface request)
|
|
67
|
+
// FORMAT THEOREM: ∀ jsx ∈ JSXOpeningElement: processElement(jsx) → tagged(jsx) ∨ skipped(jsx)
|
|
68
|
+
// PURITY: SHELL (mutates AST)
|
|
69
|
+
// EFFECT: AST mutation
|
|
70
|
+
// INVARIANT: idempotent - processing same element twice produces same result
|
|
71
|
+
// COMPLEXITY: O(n)/O(1)
|
|
72
|
+
export const processJsxElement = (node, context, types) => {
|
|
73
|
+
// Skip if no location info
|
|
74
|
+
if (node.loc === null || node.loc === undefined) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
// Skip if already has the specified attribute (idempotency)
|
|
78
|
+
if (attrExists(node, context.attributeName, types)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const { column, line } = node.loc.start;
|
|
82
|
+
const attr = createPathAttribute(context.attributeName, context.relativeFilename, line, column, types);
|
|
83
|
+
node.attributes.push(attr);
|
|
84
|
+
return true;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Creates a Babel visitor for JSX elements that uses the unified tagging logic.
|
|
88
|
+
*
|
|
89
|
+
* This is the shared visitor factory used by both:
|
|
90
|
+
* - Vite plugin (componentTagger) - passes relative filename directly
|
|
91
|
+
* - Standalone Babel plugin - computes relative filename from state
|
|
92
|
+
*
|
|
93
|
+
* @param getContext - Function to extract context from Babel state.
|
|
94
|
+
* @param types - Babel types module.
|
|
95
|
+
* @returns Babel visitor object for JSXOpeningElement.
|
|
96
|
+
*
|
|
97
|
+
* @pure true (returns immutable visitor object)
|
|
98
|
+
* @invariant visitor applies processJsxElement to all JSX opening elements
|
|
99
|
+
* @complexity O(1) for visitor creation
|
|
100
|
+
*/
|
|
101
|
+
// CHANGE: create shared visitor factory for both plugin types.
|
|
102
|
+
// WHY: single unified interface as requested by user.
|
|
103
|
+
// QUOTE(TZ): "Сделай единный интерфейс для этого"
|
|
104
|
+
// REF: issue-12-comment (unified interface request)
|
|
105
|
+
// FORMAT THEOREM: ∀ visitor = createVisitor(ctx): visitor processes all JSX elements uniformly
|
|
106
|
+
// PURITY: CORE
|
|
107
|
+
// EFFECT: n/a (visitor application has effects)
|
|
108
|
+
// INVARIANT: visitor behavior is consistent across plugin implementations
|
|
109
|
+
// COMPLEXITY: O(1)/O(1)
|
|
110
|
+
export const createJsxTaggerVisitor = (getContext, types) => ({
|
|
111
|
+
JSXOpeningElement(nodePath, state) {
|
|
112
|
+
const context = getContext(state);
|
|
113
|
+
if (context === null) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
processJsxElement(nodePath.node, context, types);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
@@ -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 {
|
|
2
|
+
export { attrExists, createJsxTaggerVisitor, createPathAttribute, type JsxTaggerContext, processJsxElement } from "./core/jsx-tagger.js";
|
|
3
|
+
export { componentTaggerBabelPlugin, type ComponentTaggerBabelPluginOptions } from "./shell/babel-plugin.js";
|
|
4
|
+
export { componentTagger, type ComponentTaggerOptions } 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,64 @@
|
|
|
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
|
+
* Name of the attribute to add to JSX elements.
|
|
13
|
+
* Defaults to "data-path".
|
|
14
|
+
*/
|
|
15
|
+
readonly attributeName?: string;
|
|
16
|
+
};
|
|
17
|
+
type BabelState = {
|
|
18
|
+
readonly filename?: string;
|
|
19
|
+
readonly cwd?: string;
|
|
20
|
+
readonly opts?: ComponentTaggerBabelPluginOptions;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Creates a Babel plugin that injects component path attributes into JSX elements.
|
|
24
|
+
*
|
|
25
|
+
* This plugin is designed to be used standalone with build tools that support
|
|
26
|
+
* Babel plugins directly (e.g., Next.js via .babelrc).
|
|
27
|
+
*
|
|
28
|
+
* Uses the unified JSX tagger core that is shared with the Vite plugin,
|
|
29
|
+
* ensuring consistent behavior across both build tools.
|
|
30
|
+
*
|
|
31
|
+
* @returns Babel plugin object
|
|
32
|
+
*
|
|
33
|
+
* @pure false
|
|
34
|
+
* @effect Babel AST transformation
|
|
35
|
+
* @invariant JSX elements are tagged with path attribute containing file:line:column
|
|
36
|
+
* @complexity O(n) where n = number of JSX elements
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // .babelrc or babel.config.js
|
|
40
|
+
* {
|
|
41
|
+
* "plugins": ["@prover-coder-ai/component-tagger/babel"]
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* // Next.js .babelrc with options
|
|
46
|
+
* {
|
|
47
|
+
* "presets": ["next/babel"],
|
|
48
|
+
* "env": {
|
|
49
|
+
* "development": {
|
|
50
|
+
* "plugins": [
|
|
51
|
+
* ["@prover-coder-ai/component-tagger/babel", { "rootDir": "/custom/root" }]
|
|
52
|
+
* ]
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* }
|
|
56
|
+
*/
|
|
57
|
+
export declare const componentTaggerBabelPlugin: () => PluginObj<BabelState>;
|
|
58
|
+
/**
|
|
59
|
+
* Default export for Babel plugin resolution.
|
|
60
|
+
*
|
|
61
|
+
* Babel resolves plugins by looking for a default export function that
|
|
62
|
+
* returns a plugin object when called.
|
|
63
|
+
*/
|
|
64
|
+
export default componentTaggerBabelPlugin;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { types as t } from "@babel/core";
|
|
2
|
+
import { componentPathAttributeName, 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: add support for configurable attributeName from options.
|
|
16
|
+
// WHY: enable unified visitor to work with Babel state and custom attribute names.
|
|
17
|
+
// QUOTE(issue-14): "Add option attributeName (default: data-path) for both plugins"
|
|
18
|
+
// REF: issue-14
|
|
19
|
+
// FORMAT THEOREM: ∀ state: getContext(state) = context ↔ isValidState(state)
|
|
20
|
+
// PURITY: CORE
|
|
21
|
+
// EFFECT: n/a
|
|
22
|
+
// INVARIANT: context contains valid relative path and attribute name
|
|
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 ?? process.cwd();
|
|
36
|
+
const relativeFilename = computeRelativePath(rootDir, filename);
|
|
37
|
+
const attributeName = state.opts?.attributeName ?? componentPathAttributeName;
|
|
38
|
+
return { relativeFilename, attributeName };
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Creates a Babel plugin that injects component path attributes into JSX elements.
|
|
42
|
+
*
|
|
43
|
+
* This plugin is designed to be used standalone with build tools that support
|
|
44
|
+
* Babel plugins directly (e.g., Next.js via .babelrc).
|
|
45
|
+
*
|
|
46
|
+
* Uses the unified JSX tagger core that is shared with the Vite plugin,
|
|
47
|
+
* ensuring consistent behavior across both build tools.
|
|
48
|
+
*
|
|
49
|
+
* @returns Babel plugin object
|
|
50
|
+
*
|
|
51
|
+
* @pure false
|
|
52
|
+
* @effect Babel AST transformation
|
|
53
|
+
* @invariant JSX elements are tagged with path attribute containing file:line:column
|
|
54
|
+
* @complexity O(n) where n = number of JSX elements
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* // .babelrc or babel.config.js
|
|
58
|
+
* {
|
|
59
|
+
* "plugins": ["@prover-coder-ai/component-tagger/babel"]
|
|
60
|
+
* }
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // Next.js .babelrc with options
|
|
64
|
+
* {
|
|
65
|
+
* "presets": ["next/babel"],
|
|
66
|
+
* "env": {
|
|
67
|
+
* "development": {
|
|
68
|
+
* "plugins": [
|
|
69
|
+
* ["@prover-coder-ai/component-tagger/babel", { "rootDir": "/custom/root" }]
|
|
70
|
+
* ]
|
|
71
|
+
* }
|
|
72
|
+
* }
|
|
73
|
+
* }
|
|
74
|
+
*/
|
|
75
|
+
// CHANGE: use unified JSX tagger visitor from core module.
|
|
76
|
+
// WHY: share business logic between Vite and Babel plugins as requested.
|
|
77
|
+
// QUOTE(TZ): "А ты можешь сделать что бы бизнес логика оставалось одной? Ну типо переиспользуй код с vite версии на babel. Сделай единный интерфейс для этого"
|
|
78
|
+
// REF: issue-12-comment (unified interface request)
|
|
79
|
+
// SOURCE: https://babeljs.io/docs/plugins
|
|
80
|
+
// FORMAT THEOREM: forall jsx in JSXOpeningElement: transform(jsx) -> tagged(jsx, path)
|
|
81
|
+
// PURITY: SHELL
|
|
82
|
+
// EFFECT: Babel AST mutation
|
|
83
|
+
// INVARIANT: each JSX opening element has at most one path attribute
|
|
84
|
+
// COMPLEXITY: O(n)/O(1)
|
|
85
|
+
export const componentTaggerBabelPlugin = () => ({
|
|
86
|
+
name: "component-path-babel-tagger",
|
|
87
|
+
visitor: createJsxTaggerVisitor(getContextFromState, t)
|
|
88
|
+
});
|
|
89
|
+
/**
|
|
90
|
+
* Default export for Babel plugin resolution.
|
|
91
|
+
*
|
|
92
|
+
* Babel resolves plugins by looking for a default export function that
|
|
93
|
+
* returns a plugin object when called.
|
|
94
|
+
*/
|
|
95
|
+
// CHANGE: provide default export for standard Babel plugin resolution.
|
|
96
|
+
// WHY: Babel expects plugins to be functions that return plugin objects.
|
|
97
|
+
// REF: issue-12
|
|
98
|
+
// FORMAT THEOREM: forall babel: require(plugin) -> callable -> PluginObj
|
|
99
|
+
// PURITY: SHELL
|
|
100
|
+
// EFFECT: n/a
|
|
101
|
+
// INVARIANT: default export matches Babel plugin signature
|
|
102
|
+
// COMPLEXITY: O(1)/O(1)
|
|
103
|
+
export default componentTaggerBabelPlugin;
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import type { PluginOption } from "vite";
|
|
2
|
+
/**
|
|
3
|
+
* Options for the component tagger Vite plugin.
|
|
4
|
+
*/
|
|
5
|
+
export type ComponentTaggerOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* Name of the attribute to add to JSX elements.
|
|
8
|
+
* Defaults to "data-path".
|
|
9
|
+
*/
|
|
10
|
+
readonly attributeName?: string;
|
|
11
|
+
};
|
|
2
12
|
/**
|
|
3
13
|
* Creates a Vite plugin that injects a single component-path data attribute.
|
|
4
14
|
*
|
|
15
|
+
* @param options - Configuration options for the plugin.
|
|
5
16
|
* @returns Vite PluginOption for pre-transform tagging.
|
|
6
17
|
*
|
|
7
18
|
* @pure false
|
|
@@ -10,4 +21,4 @@ import type { PluginOption } from "vite";
|
|
|
10
21
|
* @complexity O(n) time / O(1) space per JSX module
|
|
11
22
|
* @throws Never - errors are typed and surfaced by Effect
|
|
12
23
|
*/
|
|
13
|
-
export declare const componentTagger: () => PluginOption;
|
|
24
|
+
export declare const componentTagger: (options?: ComponentTaggerOptions) => PluginOption;
|
|
@@ -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 { componentPathAttributeName,
|
|
3
|
+
import { componentPathAttributeName, 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, attributeName) => {
|
|
29
|
+
const context = { relativeFilename, attributeName };
|
|
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
|
|
@@ -86,7 +54,7 @@ const makeBabelTagger = (relativeFilename) => ({
|
|
|
86
54
|
// EFFECT: Effect<ViteTransformResult | null, ComponentTaggerError, never>
|
|
87
55
|
// INVARIANT: errors are surfaced as ComponentTaggerError only
|
|
88
56
|
// COMPLEXITY: O(n)/O(1)
|
|
89
|
-
const runTransform = (code, id, rootDir) => {
|
|
57
|
+
const runTransform = (code, id, rootDir, attributeName) => {
|
|
90
58
|
const cleanId = stripQuery(id);
|
|
91
59
|
return pipe(relativeFromRoot(rootDir, cleanId), Effect.flatMap((relative) => Effect.tryPromise({
|
|
92
60
|
try: () => transformAsync(code, {
|
|
@@ -97,7 +65,7 @@ const runTransform = (code, id, rootDir) => {
|
|
|
97
65
|
sourceType: "module",
|
|
98
66
|
plugins: ["typescript", "jsx", "decorators-legacy"]
|
|
99
67
|
},
|
|
100
|
-
plugins: [makeBabelTagger(relative)],
|
|
68
|
+
plugins: [makeBabelTagger(relative, attributeName)],
|
|
101
69
|
sourceMaps: true
|
|
102
70
|
}),
|
|
103
71
|
catch: (cause) => {
|
|
@@ -109,6 +77,7 @@ const runTransform = (code, id, rootDir) => {
|
|
|
109
77
|
/**
|
|
110
78
|
* Creates a Vite plugin that injects a single component-path data attribute.
|
|
111
79
|
*
|
|
80
|
+
* @param options - Configuration options for the plugin.
|
|
112
81
|
* @returns Vite PluginOption for pre-transform tagging.
|
|
113
82
|
*
|
|
114
83
|
* @pure false
|
|
@@ -117,17 +86,18 @@ const runTransform = (code, id, rootDir) => {
|
|
|
117
86
|
* @complexity O(n) time / O(1) space per JSX module
|
|
118
87
|
* @throws Never - errors are typed and surfaced by Effect
|
|
119
88
|
*/
|
|
120
|
-
// CHANGE:
|
|
121
|
-
// WHY:
|
|
122
|
-
// QUOTE(
|
|
123
|
-
// REF:
|
|
89
|
+
// CHANGE: add attributeName option with default "data-path".
|
|
90
|
+
// WHY: support customizable attribute names while maintaining backwards compatibility.
|
|
91
|
+
// QUOTE(issue-14): "Add option attributeName (default: data-path) for both plugins"
|
|
92
|
+
// REF: issue-14
|
|
124
93
|
// SOURCE: n/a
|
|
125
|
-
// FORMAT THEOREM: forall id: isJsxFile(id) -> transform(id) adds
|
|
94
|
+
// FORMAT THEOREM: forall id: isJsxFile(id) -> transform(id) adds specified attribute
|
|
126
95
|
// PURITY: SHELL
|
|
127
96
|
// EFFECT: Effect<ViteTransformResult | null, ComponentTaggerError, never>
|
|
128
|
-
// INVARIANT: no duplicate
|
|
97
|
+
// INVARIANT: no duplicate attributes with the same name
|
|
129
98
|
// COMPLEXITY: O(n)/O(1)
|
|
130
|
-
export const componentTagger = () => {
|
|
99
|
+
export const componentTagger = (options) => {
|
|
100
|
+
const attributeName = options?.attributeName ?? componentPathAttributeName;
|
|
131
101
|
let resolvedRoot = process.cwd();
|
|
132
102
|
return {
|
|
133
103
|
name: "component-path-tagger",
|
|
@@ -140,7 +110,7 @@ export const componentTagger = () => {
|
|
|
140
110
|
if (!isJsxFile(id)) {
|
|
141
111
|
return null;
|
|
142
112
|
}
|
|
143
|
-
return Effect.runPromise(pipe(runTransform(code, id, resolvedRoot), Effect.provide(NodePathLayer)));
|
|
113
|
+
return Effect.runPromise(pipe(runTransform(code, id, resolvedRoot, attributeName), Effect.provide(NodePathLayer)));
|
|
144
114
|
}
|
|
145
115
|
};
|
|
146
116
|
};
|
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.25",
|
|
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
|
],
|