@uicontract/parser-react 0.1.0
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/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/context-extractor.d.ts +37 -0
- package/dist/context-extractor.d.ts.map +1 -0
- package/dist/context-extractor.js +259 -0
- package/dist/context-extractor.js.map +1 -0
- package/dist/element-visitor.d.ts +7 -0
- package/dist/element-visitor.d.ts.map +1 -0
- package/dist/element-visitor.js +112 -0
- package/dist/element-visitor.js.map +1 -0
- package/dist/file-discovery.d.ts +11 -0
- package/dist/file-discovery.d.ts.map +1 -0
- package/dist/file-discovery.js +102 -0
- package/dist/file-discovery.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UIC Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @uicontract/parser-react
|
|
2
|
+
|
|
3
|
+
React and Next.js parser for discovering interactive UI elements.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @uicontract/parser-react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { ReactParser } from '@uicontract/parser-react';
|
|
15
|
+
|
|
16
|
+
const parser = new ReactParser();
|
|
17
|
+
|
|
18
|
+
// Detect whether a project uses React
|
|
19
|
+
const isReact = await parser.detect('/path/to/my-app');
|
|
20
|
+
|
|
21
|
+
// Discover all interactive elements
|
|
22
|
+
const { elements, warnings, metadata } = await parser.discover('/path/to/my-app', {
|
|
23
|
+
include: ['src/**/*.{tsx,jsx}'],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
console.log(`Found ${elements.length} elements in ${metadata.filesScanned} files`);
|
|
27
|
+
// elements: RawElement[] ready for @uicontract/namer
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## API
|
|
31
|
+
|
|
32
|
+
- **`ReactParser`**: Implements the `Parser` interface from `@uicontract/core`.
|
|
33
|
+
- **`detect(dir)`**: Returns `true` if the directory contains a React project.
|
|
34
|
+
- **`discover(dir, options)`**: AST-parses `.tsx` and `.jsx` files using `@babel/parser` and `@babel/traverse`. Returns `{ elements, warnings, metadata }`.
|
|
35
|
+
|
|
36
|
+
Discovered element types: `button`, `a` (link), `input`, `select`, `textarea`, `form`.
|
|
37
|
+
|
|
38
|
+
Supported patterns: JSX, TSX, `forwardRef`, `memo`, HOCs, dynamic imports, conditional rendering, file-based routing (Next.js).
|
|
39
|
+
|
|
40
|
+
Unexpected syntax produces a `warning` entry rather than throwing, so partial results are always returned.
|
|
41
|
+
|
|
42
|
+
## Part of UIC
|
|
43
|
+
|
|
44
|
+
This package is part of [UIC (UI Contracts)](https://github.com/sherifkozman/uicontract) — making web app UIs machine-readable.
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
[MIT](../../LICENSE)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context extraction helpers for the React AST visitor.
|
|
3
|
+
* Each function extracts a specific piece of metadata from a JSX element's path.
|
|
4
|
+
*/
|
|
5
|
+
import type { NodePath } from '@babel/traverse';
|
|
6
|
+
import * as t from '@babel/types';
|
|
7
|
+
/**
|
|
8
|
+
* Walk up the path ancestry to find the name of the enclosing React component.
|
|
9
|
+
* Handles function declarations, arrow functions assigned to variables,
|
|
10
|
+
* React.memo(), React.forwardRef(), and HOC wrappers.
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractComponentName(nodePath: NodePath): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Infer a Next.js route from a file path.
|
|
15
|
+
* Files under an `app/` directory named `page.tsx` or `layout.tsx` map to routes.
|
|
16
|
+
* All other files return null.
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractRoute(filePath: string, projectRoot: string): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Extract the best human-readable label for a JSX interactive element.
|
|
21
|
+
* Priority: aria-label > children text > placeholder
|
|
22
|
+
*/
|
|
23
|
+
export declare function extractLabel(openingPath: NodePath<t.JSXOpeningElement>): string | null;
|
|
24
|
+
/** Extract the event handler name from event props like onClick, onSubmit, etc. */
|
|
25
|
+
export declare function extractHandler(openingPath: NodePath<t.JSXOpeningElement>): string | null;
|
|
26
|
+
/** Collect all data-* attributes from a JSX opening element. */
|
|
27
|
+
export declare function extractDataAttributes(openingPath: NodePath<t.JSXOpeningElement>): Record<string, string>;
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if the path is nested inside a ConditionalExpression (ternary)
|
|
30
|
+
* or a LogicalExpression with && or ||.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isConditional(nodePath: NodePath): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Returns true if the path is nested inside a .map() or similar array iteration call.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isDynamic(nodePath: NodePath): boolean;
|
|
37
|
+
//# sourceMappingURL=context-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-extractor.d.ts","sourceRoot":"","sources":["../src/context-extractor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,CAAC,MAAM,cAAc,CAAC;AAMlC;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CA4DtE;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmBjF;AAwDD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,MAAM,GAAG,IAAI,CAyBtF;AAMD,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,MAAM,GAAG,IAAI,CAyBxF;AAMD,gEAAgE;AAChE,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYxG;AAMD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAkBzD;AAID;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAgBrD"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context extraction helpers for the React AST visitor.
|
|
3
|
+
* Each function extracts a specific piece of metadata from a JSX element's path.
|
|
4
|
+
*/
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import * as t from '@babel/types';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Component name
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
/**
|
|
11
|
+
* Walk up the path ancestry to find the name of the enclosing React component.
|
|
12
|
+
* Handles function declarations, arrow functions assigned to variables,
|
|
13
|
+
* React.memo(), React.forwardRef(), and HOC wrappers.
|
|
14
|
+
*/
|
|
15
|
+
export function extractComponentName(nodePath) {
|
|
16
|
+
let current = nodePath.parentPath;
|
|
17
|
+
while (current) {
|
|
18
|
+
const node = current.node;
|
|
19
|
+
// Named function declaration: `function MyComponent() { ... }`
|
|
20
|
+
if (t.isFunctionDeclaration(node) && node.id) {
|
|
21
|
+
return node.id.name;
|
|
22
|
+
}
|
|
23
|
+
// Arrow function or function expression assigned to variable:
|
|
24
|
+
// `const MyComponent = () => ...`
|
|
25
|
+
if ((t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) &&
|
|
26
|
+
t.isVariableDeclarator(current.parent) &&
|
|
27
|
+
t.isIdentifier(current.parent.id)) {
|
|
28
|
+
return current.parent.id.name;
|
|
29
|
+
}
|
|
30
|
+
// Named function expression inside call (React.memo, React.forwardRef, HOC):
|
|
31
|
+
// `React.memo(function MyComponent() { ... })`
|
|
32
|
+
if (t.isFunctionExpression(node) && node.id) {
|
|
33
|
+
return node.id.name;
|
|
34
|
+
}
|
|
35
|
+
// Arrow/function inside a call expression (HOC wrapper, memo, forwardRef):
|
|
36
|
+
// `const Foo = withAuth(() => <div>...</div>)`
|
|
37
|
+
// `const Bar = React.memo(() => <div>...</div>)`
|
|
38
|
+
if ((t.isArrowFunctionExpression(node) || t.isFunctionExpression(node)) &&
|
|
39
|
+
t.isCallExpression(current.parent)) {
|
|
40
|
+
const callPath = current.parentPath;
|
|
41
|
+
if (callPath && t.isVariableDeclarator(callPath.parent) && t.isIdentifier(callPath.parent.id)) {
|
|
42
|
+
return callPath.parent.id.name;
|
|
43
|
+
}
|
|
44
|
+
// Nested calls: `React.memo(React.forwardRef(() => ...))`
|
|
45
|
+
if (callPath?.parentPath && t.isCallExpression(callPath.parentPath.node)) {
|
|
46
|
+
const outerCallPath = callPath.parentPath;
|
|
47
|
+
if (outerCallPath && t.isVariableDeclarator(outerCallPath.parent) && t.isIdentifier(outerCallPath.parent.id)) {
|
|
48
|
+
return outerCallPath.parent.id.name;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Class component: `class MyComponent extends React.Component { ... }`
|
|
53
|
+
if (t.isClassDeclaration(node) && node.id) {
|
|
54
|
+
return node.id.name;
|
|
55
|
+
}
|
|
56
|
+
if (t.isClassExpression(node) && node.id) {
|
|
57
|
+
return node.id.name;
|
|
58
|
+
}
|
|
59
|
+
current = current.parentPath;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Route extraction (Next.js App Router)
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
/**
|
|
67
|
+
* Infer a Next.js route from a file path.
|
|
68
|
+
* Files under an `app/` directory named `page.tsx` or `layout.tsx` map to routes.
|
|
69
|
+
* All other files return null.
|
|
70
|
+
*/
|
|
71
|
+
export function extractRoute(filePath, projectRoot) {
|
|
72
|
+
const rel = path.relative(projectRoot, filePath).replace(/\\/g, '/');
|
|
73
|
+
// Look for the app/ directory segment
|
|
74
|
+
const appIndex = rel.indexOf('app/');
|
|
75
|
+
if (appIndex === -1)
|
|
76
|
+
return null;
|
|
77
|
+
const afterApp = rel.slice(appIndex + 'app/'.length);
|
|
78
|
+
const fileName = path.basename(afterApp);
|
|
79
|
+
if (fileName !== 'page.tsx' && fileName !== 'page.jsx' && fileName !== 'layout.tsx' && fileName !== 'layout.jsx') {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const dirPart = path.dirname(afterApp).replace(/\\/g, '/');
|
|
83
|
+
if (dirPart === '.') {
|
|
84
|
+
return '/';
|
|
85
|
+
}
|
|
86
|
+
return '/' + dirPart;
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Label extraction
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
/** Try to extract a string value from a JSXAttribute's value node. */
|
|
92
|
+
function jsxAttrStringValue(attr) {
|
|
93
|
+
if (t.isStringLiteral(attr.value)) {
|
|
94
|
+
return attr.value.value;
|
|
95
|
+
}
|
|
96
|
+
if (t.isJSXExpressionContainer(attr.value)) {
|
|
97
|
+
const expr = attr.value.expression;
|
|
98
|
+
if (t.isStringLiteral(expr)) {
|
|
99
|
+
return expr.value;
|
|
100
|
+
}
|
|
101
|
+
if (t.isTemplateLiteral(expr) && expr.quasis.length === 1) {
|
|
102
|
+
const quasi = expr.quasis[0];
|
|
103
|
+
return quasi?.value.cooked ?? quasi?.value.raw ?? null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
/** Get the string name of a JSX attribute. */
|
|
109
|
+
function attrName(attr) {
|
|
110
|
+
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
|
|
111
|
+
return attr.name.name;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/** Find an attribute by name in a JSX opening element. */
|
|
116
|
+
function findAttr(node, name) {
|
|
117
|
+
for (const attr of node.attributes) {
|
|
118
|
+
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === name) {
|
|
119
|
+
return attr;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
/** Extract visible text from JSX children (shallow — only literals). */
|
|
125
|
+
function childrenText(jsxElement) {
|
|
126
|
+
const texts = [];
|
|
127
|
+
for (const child of jsxElement.children) {
|
|
128
|
+
if (t.isJSXText(child)) {
|
|
129
|
+
const trimmed = child.value.trim();
|
|
130
|
+
if (trimmed)
|
|
131
|
+
texts.push(trimmed);
|
|
132
|
+
}
|
|
133
|
+
else if (t.isJSXExpressionContainer(child) && t.isStringLiteral(child.expression)) {
|
|
134
|
+
texts.push(child.expression.value);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return texts.length > 0 ? texts.join(' ') : null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Extract the best human-readable label for a JSX interactive element.
|
|
141
|
+
* Priority: aria-label > children text > placeholder
|
|
142
|
+
*/
|
|
143
|
+
export function extractLabel(openingPath) {
|
|
144
|
+
const node = openingPath.node;
|
|
145
|
+
// 1. aria-label
|
|
146
|
+
const ariaLabel = findAttr(node, 'aria-label');
|
|
147
|
+
if (ariaLabel) {
|
|
148
|
+
const val = jsxAttrStringValue(ariaLabel);
|
|
149
|
+
if (val)
|
|
150
|
+
return val;
|
|
151
|
+
}
|
|
152
|
+
// 2. children text — need access to the parent JSXElement
|
|
153
|
+
const parent = openingPath.parent;
|
|
154
|
+
if (t.isJSXElement(parent)) {
|
|
155
|
+
const text = childrenText(parent);
|
|
156
|
+
if (text)
|
|
157
|
+
return text;
|
|
158
|
+
}
|
|
159
|
+
// 3. placeholder
|
|
160
|
+
const placeholder = findAttr(node, 'placeholder');
|
|
161
|
+
if (placeholder) {
|
|
162
|
+
const val = jsxAttrStringValue(placeholder);
|
|
163
|
+
if (val)
|
|
164
|
+
return val;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Handler extraction
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
/** Extract the event handler name from event props like onClick, onSubmit, etc. */
|
|
172
|
+
export function extractHandler(openingPath) {
|
|
173
|
+
const eventProps = ['onClick', 'onSubmit', 'onChange', 'onInput', 'onFocus', 'onBlur', 'onKeyDown', 'onKeyUp', 'onKeyPress'];
|
|
174
|
+
for (const attr of openingPath.node.attributes) {
|
|
175
|
+
if (!t.isJSXAttribute(attr))
|
|
176
|
+
continue;
|
|
177
|
+
const name = attrName(attr);
|
|
178
|
+
if (!name || !eventProps.includes(name))
|
|
179
|
+
continue;
|
|
180
|
+
if (!t.isJSXExpressionContainer(attr.value))
|
|
181
|
+
continue;
|
|
182
|
+
const expr = attr.value.expression;
|
|
183
|
+
// onClick={handleFoo}
|
|
184
|
+
if (t.isIdentifier(expr)) {
|
|
185
|
+
return expr.name;
|
|
186
|
+
}
|
|
187
|
+
// onClick={this.handleFoo}
|
|
188
|
+
if (t.isMemberExpression(expr) && t.isIdentifier(expr.property)) {
|
|
189
|
+
return expr.property.name;
|
|
190
|
+
}
|
|
191
|
+
// Inline arrow / complex expression → null
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Data attribute extraction
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
/** Collect all data-* attributes from a JSX opening element. */
|
|
199
|
+
export function extractDataAttributes(openingPath) {
|
|
200
|
+
const result = {};
|
|
201
|
+
for (const attr of openingPath.node.attributes) {
|
|
202
|
+
if (!t.isJSXAttribute(attr))
|
|
203
|
+
continue;
|
|
204
|
+
const name = attrName(attr);
|
|
205
|
+
if (!name || !name.startsWith('data-'))
|
|
206
|
+
continue;
|
|
207
|
+
const val = jsxAttrStringValue(attr);
|
|
208
|
+
result[name] = val ?? '';
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Conditional / Dynamic detection
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
/**
|
|
216
|
+
* Returns true if the path is nested inside a ConditionalExpression (ternary)
|
|
217
|
+
* or a LogicalExpression with && or ||.
|
|
218
|
+
*/
|
|
219
|
+
export function isConditional(nodePath) {
|
|
220
|
+
let current = nodePath.parentPath;
|
|
221
|
+
while (current) {
|
|
222
|
+
if (t.isConditionalExpression(current.node))
|
|
223
|
+
return true;
|
|
224
|
+
if (t.isLogicalExpression(current.node) && (current.node.operator === '&&' || current.node.operator === '||')) {
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
// Stop at function boundaries
|
|
228
|
+
if (t.isFunctionDeclaration(current.node) ||
|
|
229
|
+
t.isFunctionExpression(current.node) ||
|
|
230
|
+
t.isArrowFunctionExpression(current.node)) {
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
current = current.parentPath;
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const DYNAMIC_CALLEES = new Set(['map', 'flatMap', 'reduce', 'filter', 'forEach', 'find', 'findIndex']);
|
|
238
|
+
/**
|
|
239
|
+
* Returns true if the path is nested inside a .map() or similar array iteration call.
|
|
240
|
+
*/
|
|
241
|
+
export function isDynamic(nodePath) {
|
|
242
|
+
let current = nodePath.parentPath;
|
|
243
|
+
while (current) {
|
|
244
|
+
if (t.isCallExpression(current.node)) {
|
|
245
|
+
const callee = current.node.callee;
|
|
246
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
|
|
247
|
+
if (DYNAMIC_CALLEES.has(callee.property.name))
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// Stop at function boundaries (but not arrow functions passed as callbacks)
|
|
252
|
+
if (t.isFunctionDeclaration(current.node)) {
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
current = current.parentPath;
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=context-extractor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-extractor.js","sourceRoot":"","sources":["../src/context-extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST visitor that discovers interactive JSX elements in a parsed file.
|
|
3
|
+
*/
|
|
4
|
+
import type { RawElement } from '@uicontract/core';
|
|
5
|
+
/** Parse a React/JSX/TS source file and return discovered interactive elements. */
|
|
6
|
+
export declare function parseFile(source: string, absoluteFilePath: string, projectRoot: string): RawElement[];
|
|
7
|
+
//# sourceMappingURL=element-visitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element-visitor.d.ts","sourceRoot":"","sources":["../src/element-visitor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,UAAU,EAA0B,MAAM,kBAAkB,CAAC;AA+D3E,mFAAmF;AACnF,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,gBAAgB,EAAE,MAAM,EACxB,WAAW,EAAE,MAAM,GAClB,UAAU,EAAE,CAiEd"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST visitor that discovers interactive JSX elements in a parsed file.
|
|
3
|
+
*/
|
|
4
|
+
import { parse } from '@babel/parser';
|
|
5
|
+
import _traverse from '@babel/traverse';
|
|
6
|
+
import * as t from '@babel/types';
|
|
7
|
+
import { extractComponentName, extractRoute, extractLabel, extractHandler, extractDataAttributes, isConditional, isDynamic, } from './context-extractor.js';
|
|
8
|
+
// @babel/traverse ships as CJS; handle default import for ESM interop
|
|
9
|
+
const traverse = (_traverse.default ?? _traverse);
|
|
10
|
+
/** Interactive HTML element tag names that are always captured. */
|
|
11
|
+
const ALWAYS_INTERACTIVE = new Set([
|
|
12
|
+
'button',
|
|
13
|
+
'input',
|
|
14
|
+
'select',
|
|
15
|
+
'textarea',
|
|
16
|
+
'a',
|
|
17
|
+
'form',
|
|
18
|
+
]);
|
|
19
|
+
/** JSX event handler prop names that make any element interactive. */
|
|
20
|
+
const EVENT_HANDLER_PROPS = new Set([
|
|
21
|
+
'onClick',
|
|
22
|
+
'onSubmit',
|
|
23
|
+
'onChange',
|
|
24
|
+
'onInput',
|
|
25
|
+
'onFocus',
|
|
26
|
+
'onBlur',
|
|
27
|
+
'onKeyDown',
|
|
28
|
+
'onKeyUp',
|
|
29
|
+
'onKeyPress',
|
|
30
|
+
]);
|
|
31
|
+
/** Elements that can appear in InteractiveElementType but aren't always interactive. */
|
|
32
|
+
const GENERIC_INTERACTIVE_TYPES = new Set(['div', 'span', 'img', 'label']);
|
|
33
|
+
/** All valid InteractiveElementType values. */
|
|
34
|
+
const VALID_ELEMENT_TYPES = new Set([
|
|
35
|
+
'button', 'input', 'select', 'textarea', 'a', 'form',
|
|
36
|
+
'div', 'span', 'img', 'label',
|
|
37
|
+
]);
|
|
38
|
+
function toInteractiveType(tagName) {
|
|
39
|
+
if (VALID_ELEMENT_TYPES.has(tagName)) {
|
|
40
|
+
return tagName;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
/** Check whether a JSX opening element has any event handler props. */
|
|
45
|
+
function hasEventHandlerProp(node) {
|
|
46
|
+
for (const attr of node.attributes) {
|
|
47
|
+
if (t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name)) {
|
|
48
|
+
if (EVENT_HANDLER_PROPS.has(attr.name.name))
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
/** Parse a React/JSX/TS source file and return discovered interactive elements. */
|
|
55
|
+
export function parseFile(source, absoluteFilePath, projectRoot) {
|
|
56
|
+
const ast = parse(source, {
|
|
57
|
+
sourceType: 'module',
|
|
58
|
+
plugins: [
|
|
59
|
+
'typescript',
|
|
60
|
+
'jsx',
|
|
61
|
+
'decorators-legacy',
|
|
62
|
+
'classProperties',
|
|
63
|
+
'optionalChaining',
|
|
64
|
+
'nullishCoalescingOperator',
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
const elements = [];
|
|
68
|
+
traverse(ast, {
|
|
69
|
+
JSXOpeningElement(nodePath) {
|
|
70
|
+
const nameNode = nodePath.node.name;
|
|
71
|
+
// Only handle plain HTML elements (lowercase tag names)
|
|
72
|
+
if (!t.isJSXIdentifier(nameNode))
|
|
73
|
+
return;
|
|
74
|
+
const tagName = nameNode.name;
|
|
75
|
+
const isAlwaysInteractive = ALWAYS_INTERACTIVE.has(tagName);
|
|
76
|
+
const isGenericWithHandler = GENERIC_INTERACTIVE_TYPES.has(tagName) && hasEventHandlerProp(nodePath.node);
|
|
77
|
+
if (!isAlwaysInteractive && !isGenericWithHandler)
|
|
78
|
+
return;
|
|
79
|
+
const elementType = toInteractiveType(tagName);
|
|
80
|
+
if (!elementType)
|
|
81
|
+
return;
|
|
82
|
+
const loc = nodePath.node.loc;
|
|
83
|
+
const line = loc?.start.line ?? 0;
|
|
84
|
+
const column = (loc?.start.column ?? 0) + 1; // convert 0-based to 1-based
|
|
85
|
+
const componentName = extractComponentName(nodePath);
|
|
86
|
+
const route = extractRoute(absoluteFilePath, projectRoot);
|
|
87
|
+
const label = extractLabel(nodePath);
|
|
88
|
+
const handler = extractHandler(nodePath);
|
|
89
|
+
const attributes = extractDataAttributes(nodePath);
|
|
90
|
+
const conditional = isConditional(nodePath);
|
|
91
|
+
const dynamic = isDynamic(nodePath);
|
|
92
|
+
const relFilePath = absoluteFilePath
|
|
93
|
+
.replace(/\\/g, '/')
|
|
94
|
+
.replace(projectRoot.replace(/\\/g, '/').replace(/\/?$/, '/'), '');
|
|
95
|
+
elements.push({
|
|
96
|
+
type: elementType,
|
|
97
|
+
filePath: relFilePath,
|
|
98
|
+
line,
|
|
99
|
+
column,
|
|
100
|
+
componentName,
|
|
101
|
+
route,
|
|
102
|
+
label,
|
|
103
|
+
handler,
|
|
104
|
+
attributes,
|
|
105
|
+
conditional,
|
|
106
|
+
dynamic,
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
return elements;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=element-visitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element-visitor.js","sourceRoot":"","sources":["../src/element-visitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File discovery for React/JSX projects.
|
|
3
|
+
* Finds .tsx/.jsx/.ts/.js files respecting include/exclude patterns and maxDepth.
|
|
4
|
+
*/
|
|
5
|
+
import type { ParserOptions } from '@uicontract/core';
|
|
6
|
+
/**
|
|
7
|
+
* Discovers React/JSX source files in the given directory.
|
|
8
|
+
* Returns absolute paths.
|
|
9
|
+
*/
|
|
10
|
+
export declare function discoverFiles(dir: string, options: ParserOptions): Promise<string[]>;
|
|
11
|
+
//# sourceMappingURL=file-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-discovery.d.ts","sourceRoot":"","sources":["../src/file-discovery.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AA4FtD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQ1F"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File discovery for React/JSX projects.
|
|
3
|
+
* Finds .tsx/.jsx/.ts/.js files respecting include/exclude patterns and maxDepth.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'node:fs/promises';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
const DEFAULT_INCLUDE = ['**/*.tsx', '**/*.jsx'];
|
|
8
|
+
const DEFAULT_EXCLUDE = [
|
|
9
|
+
'**/node_modules/**',
|
|
10
|
+
'**/dist/**',
|
|
11
|
+
'**/.next/**',
|
|
12
|
+
'**/build/**',
|
|
13
|
+
'**/.cache/**',
|
|
14
|
+
'**/coverage/**',
|
|
15
|
+
];
|
|
16
|
+
const DEFAULT_MAX_DEPTH = 20;
|
|
17
|
+
/**
|
|
18
|
+
* Converts a glob pattern segment to a RegExp source string.
|
|
19
|
+
* Supports `**`, `*`, `?`, and literal characters.
|
|
20
|
+
*/
|
|
21
|
+
function globToRegex(glob) {
|
|
22
|
+
let pattern = '^';
|
|
23
|
+
let i = 0;
|
|
24
|
+
while (i < glob.length) {
|
|
25
|
+
const char = glob[i];
|
|
26
|
+
if (char === '*' && glob[i + 1] === '*') {
|
|
27
|
+
// ** matches any number of path segments including none
|
|
28
|
+
pattern += '(?:.+/)?';
|
|
29
|
+
i += 2;
|
|
30
|
+
// skip trailing slash if present
|
|
31
|
+
if (glob[i] === '/')
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
else if (char === '*') {
|
|
35
|
+
// * matches any character except /
|
|
36
|
+
pattern += '[^/]*';
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
else if (char === '?') {
|
|
40
|
+
pattern += '[^/]';
|
|
41
|
+
i++;
|
|
42
|
+
}
|
|
43
|
+
else if ('.+^${}()|[]\\'.includes(char ?? '')) {
|
|
44
|
+
pattern += '\\' + char;
|
|
45
|
+
i++;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
pattern += char;
|
|
49
|
+
i++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
pattern += '$';
|
|
53
|
+
return new RegExp(pattern);
|
|
54
|
+
}
|
|
55
|
+
function matchesAny(relPath, patterns) {
|
|
56
|
+
const normalized = relPath.replace(/\\/g, '/');
|
|
57
|
+
return patterns.some((glob) => {
|
|
58
|
+
const regex = globToRegex(glob);
|
|
59
|
+
return regex.test(normalized);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Recursively walks a directory and collects files that match include patterns
|
|
64
|
+
* and don't match exclude patterns, up to maxDepth levels deep.
|
|
65
|
+
*/
|
|
66
|
+
async function walkDir(dir, rootDir, include, exclude, maxDepth, currentDepth, results) {
|
|
67
|
+
if (currentDepth > maxDepth)
|
|
68
|
+
return;
|
|
69
|
+
let entries;
|
|
70
|
+
try {
|
|
71
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Unreadable directory — skip silently
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
const fullPath = path.join(dir, entry.name);
|
|
79
|
+
const relPath = path.relative(rootDir, fullPath).replace(/\\/g, '/');
|
|
80
|
+
if (matchesAny(relPath, exclude))
|
|
81
|
+
continue;
|
|
82
|
+
if (entry.isDirectory()) {
|
|
83
|
+
await walkDir(fullPath, rootDir, include, exclude, maxDepth, currentDepth + 1, results);
|
|
84
|
+
}
|
|
85
|
+
else if (entry.isFile() && matchesAny(relPath, include)) {
|
|
86
|
+
results.push(fullPath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Discovers React/JSX source files in the given directory.
|
|
92
|
+
* Returns absolute paths.
|
|
93
|
+
*/
|
|
94
|
+
export async function discoverFiles(dir, options) {
|
|
95
|
+
const include = options.include ?? DEFAULT_INCLUDE;
|
|
96
|
+
const exclude = options.exclude ?? DEFAULT_EXCLUDE;
|
|
97
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
98
|
+
const results = [];
|
|
99
|
+
await walkDir(dir, dir, include, exclude, maxDepth, 0, results);
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=file-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-discovery.js","sourceRoot":"","sources":["../src/file-discovery.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @uicontract/parser-react — React/JSX parser for UIC.
|
|
3
|
+
* Implements the Parser interface from @uicontract/core.
|
|
4
|
+
*/
|
|
5
|
+
import type { Parser, DiscoveryResult, ParserOptions } from '@uicontract/core';
|
|
6
|
+
export declare const VERSION = "0.0.0";
|
|
7
|
+
/** The React/Next.js parser implementation. */
|
|
8
|
+
export declare class ReactParser implements Parser {
|
|
9
|
+
readonly framework = "react";
|
|
10
|
+
detect(dir: string): Promise<boolean>;
|
|
11
|
+
discover(dir: string, options: ParserOptions): Promise<DiscoveryResult>;
|
|
12
|
+
}
|
|
13
|
+
export declare const reactParser: ReactParser;
|
|
14
|
+
export { discoverFiles } from './file-discovery.js';
|
|
15
|
+
export { parseFile } from './element-visitor.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,aAAa,EAA6B,MAAM,kBAAkB,CAAC;AAI1G,eAAO,MAAM,OAAO,UAAU,CAAC;AA4B/B,+CAA+C;AAC/C,qBAAa,WAAY,YAAW,MAAM;IACxC,QAAQ,CAAC,SAAS,WAAW;IAEvB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrC,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC;CAiC9E;AAED,eAAO,MAAM,WAAW,aAAoB,CAAC;AAG7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @uicontract/parser-react — React/JSX parser for UIC.
|
|
3
|
+
* Implements the Parser interface from @uicontract/core.
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'node:fs/promises';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { discoverFiles } from './file-discovery.js';
|
|
8
|
+
import { parseFile } from './element-visitor.js';
|
|
9
|
+
export const VERSION = '0.0.0';
|
|
10
|
+
/** Detect whether a directory likely contains a React project. */
|
|
11
|
+
async function detectReact(dir) {
|
|
12
|
+
// Check for package.json with react dependency
|
|
13
|
+
try {
|
|
14
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
15
|
+
const raw = await fs.readFile(pkgPath, 'utf-8');
|
|
16
|
+
const pkg = JSON.parse(raw);
|
|
17
|
+
const deps = {
|
|
18
|
+
...(typeof pkg['dependencies'] === 'object' && pkg['dependencies'] !== null ? pkg['dependencies'] : {}),
|
|
19
|
+
...(typeof pkg['devDependencies'] === 'object' && pkg['devDependencies'] !== null ? pkg['devDependencies'] : {}),
|
|
20
|
+
...(typeof pkg['peerDependencies'] === 'object' && pkg['peerDependencies'] !== null ? pkg['peerDependencies'] : {}),
|
|
21
|
+
};
|
|
22
|
+
if ('react' in deps)
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// No package.json or unreadable — fall through to file check
|
|
27
|
+
}
|
|
28
|
+
// Fallback: any .tsx or .jsx file present
|
|
29
|
+
try {
|
|
30
|
+
const files = await discoverFiles(dir, { include: ['**/*.tsx', '**/*.jsx'], maxDepth: 3 });
|
|
31
|
+
return files.length > 0;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** The React/Next.js parser implementation. */
|
|
38
|
+
export class ReactParser {
|
|
39
|
+
framework = 'react';
|
|
40
|
+
async detect(dir) {
|
|
41
|
+
return detectReact(dir);
|
|
42
|
+
}
|
|
43
|
+
async discover(dir, options) {
|
|
44
|
+
const startTime = Date.now();
|
|
45
|
+
const warnings = [];
|
|
46
|
+
const elements = [];
|
|
47
|
+
let filesSkipped = 0;
|
|
48
|
+
const files = await discoverFiles(dir, options);
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
try {
|
|
51
|
+
const source = await fs.readFile(file, 'utf-8');
|
|
52
|
+
const fileElements = parseFile(source, file, dir);
|
|
53
|
+
elements.push(...fileElements);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
filesSkipped++;
|
|
57
|
+
warnings.push({
|
|
58
|
+
code: 'PARSE_ERROR',
|
|
59
|
+
message: `Failed to parse: ${error instanceof Error ? error.message : String(error)}`,
|
|
60
|
+
filePath: path.relative(dir, file),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
elements,
|
|
66
|
+
warnings,
|
|
67
|
+
metadata: {
|
|
68
|
+
filesScanned: files.length,
|
|
69
|
+
filesSkipped,
|
|
70
|
+
scanDurationMs: Date.now() - startTime,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export const reactParser = new ReactParser();
|
|
76
|
+
// Re-export internal modules for consumers that need direct access
|
|
77
|
+
export { discoverFiles } from './file-discovery.js';
|
|
78
|
+
export { parseFile } from './element-visitor.js';
|
|
79
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uicontract/parser-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React and Next.js parser for discovering interactive UI elements",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "UIC Contributors",
|
|
8
|
+
"homepage": "https://github.com/sherifkozman/uicontract/tree/main/packages/parser-react",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/sherifkozman/uicontract.git",
|
|
12
|
+
"directory": "packages/parser-react"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/sherifkozman/uicontract/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"uic",
|
|
19
|
+
"ui-contracts",
|
|
20
|
+
"parser",
|
|
21
|
+
"react",
|
|
22
|
+
"nextjs",
|
|
23
|
+
"ast"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"README.md"
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@babel/parser": "^7.29.0",
|
|
46
|
+
"@babel/traverse": "^7.29.0",
|
|
47
|
+
"@babel/types": "^7.29.0",
|
|
48
|
+
"@uicontract/core": "0.1.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/babel__traverse": "^7.28.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc -b",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"lint": "eslint src"
|
|
58
|
+
}
|
|
59
|
+
}
|