has-jsx 0.0.1
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 +203 -0
- package/bin/has-jsx.js +8 -0
- package/dist/index.cjs +63 -0
- package/dist/index.mjs +60 -0
- package/package.json +53 -0
- package/src/cli.js +100 -0
- package/src/detector.js +27 -0
- package/src/index.js +30 -0
- package/src/utils.js +22 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Elchin Valiyev
|
|
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,203 @@
|
|
|
1
|
+
# has-jsx
|
|
2
|
+
|
|
3
|
+
Detect JSX in files and strings using AST analysis.
|
|
4
|
+
|
|
5
|
+
## Why has-jsx?
|
|
6
|
+
|
|
7
|
+
Detecting JSX isn't as simple as checking file extensions. JavaScript and TypeScript files (`.js`, `.ts`) can contain JSX syntax. This tool uses [ast-grep](https://github.com/ast-grep/ast-grep)'s powerful AST parsing to reliably detect JSX node types from the [JSX specification](https://facebook.github.io/jsx/):
|
|
8
|
+
|
|
9
|
+
- `jsx_element` - Standard tags: `<div>...</div>`
|
|
10
|
+
- `jsx_self_closing_element` - Self-closing: `<Component />`
|
|
11
|
+
- `jsx_fragment` - Fragments: `<>...</>`
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install has-jsx
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## CLI Usage
|
|
20
|
+
|
|
21
|
+
### Check Files
|
|
22
|
+
|
|
23
|
+
Use the `-f` or `--file` flag to check files:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
has-jsx -f src/Component.tsx
|
|
27
|
+
# Output: JSX detected
|
|
28
|
+
# Exit code: 0
|
|
29
|
+
|
|
30
|
+
has-jsx --file src/utils.ts
|
|
31
|
+
# Output: No JSX detected
|
|
32
|
+
# Exit code: 1
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Check Strings
|
|
36
|
+
|
|
37
|
+
Pass source code directly without any flags:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
has-jsx "const App = () => <div>Hello</div>"
|
|
41
|
+
# Output: JSX detected
|
|
42
|
+
# Exit code: 0
|
|
43
|
+
|
|
44
|
+
has-jsx "const x = 5;"
|
|
45
|
+
# Output: No JSX detected
|
|
46
|
+
# Exit code: 1
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Options
|
|
50
|
+
|
|
51
|
+
| Flag | Description |
|
|
52
|
+
|------|-------------|
|
|
53
|
+
| `-f, --file <path>` | Check a file for JSX |
|
|
54
|
+
| `--verbose` | Output results as JSON |
|
|
55
|
+
| `--quiet` | Silent mode, exit code only |
|
|
56
|
+
| `--version` | Show version number |
|
|
57
|
+
| `--help` | Show help |
|
|
58
|
+
|
|
59
|
+
### Exit Codes
|
|
60
|
+
|
|
61
|
+
| Code | Meaning |
|
|
62
|
+
|------|---------|
|
|
63
|
+
| `0` | JSX detected |
|
|
64
|
+
| `1` | No JSX detected |
|
|
65
|
+
| `2` | Error (file not found, read error, etc.) |
|
|
66
|
+
|
|
67
|
+
## Programmatic API
|
|
68
|
+
|
|
69
|
+
### File-based Detection
|
|
70
|
+
|
|
71
|
+
Check if a file contains JSX:
|
|
72
|
+
|
|
73
|
+
**ESM (recommended):**
|
|
74
|
+
```javascript
|
|
75
|
+
import hasJSX from 'has-jsx';
|
|
76
|
+
|
|
77
|
+
const result = await hasJSX('src/Component.tsx');
|
|
78
|
+
console.log(result); // true or false
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**CommonJS:**
|
|
82
|
+
```javascript
|
|
83
|
+
const { hasJSX } = require('has-jsx');
|
|
84
|
+
|
|
85
|
+
(async () => {
|
|
86
|
+
const result = await hasJSX('src/Component.tsx');
|
|
87
|
+
console.log(result);
|
|
88
|
+
})();
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### String-based Detection
|
|
92
|
+
|
|
93
|
+
Check if a string contains JSX (synchronous, no file I/O):
|
|
94
|
+
|
|
95
|
+
**ESM:**
|
|
96
|
+
```javascript
|
|
97
|
+
import { hasJSXInString } from 'has-jsx';
|
|
98
|
+
|
|
99
|
+
const code = `
|
|
100
|
+
function App() {
|
|
101
|
+
return <div>Hello World</div>;
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
const result = hasJSXInString(code);
|
|
106
|
+
console.log(result); // true
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**CommonJS:**
|
|
110
|
+
```javascript
|
|
111
|
+
const { hasJSXInString } = require('has-jsx');
|
|
112
|
+
|
|
113
|
+
const code = 'const App = () => <div>Hello</div>';
|
|
114
|
+
const result = hasJSXInString(code);
|
|
115
|
+
console.log(result); // true
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### API Reference
|
|
119
|
+
|
|
120
|
+
#### `hasJSX(filepath: string): Promise<boolean>`
|
|
121
|
+
|
|
122
|
+
Analyzes a file to detect JSX syntax.
|
|
123
|
+
|
|
124
|
+
**Parameters:**
|
|
125
|
+
- `filepath` - Path to the file to analyze (relative or absolute)
|
|
126
|
+
|
|
127
|
+
**Returns:**
|
|
128
|
+
- `Promise<boolean>` - `true` if JSX is detected, `false` otherwise
|
|
129
|
+
|
|
130
|
+
**Throws:**
|
|
131
|
+
- `Error` - If file doesn't exist, is not a file, or can't be read
|
|
132
|
+
|
|
133
|
+
**Example:**
|
|
134
|
+
|
|
135
|
+
```javascript
|
|
136
|
+
import hasJSX from 'has-jsx';
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const result = await hasJSX('src/Component.tsx');
|
|
140
|
+
if (result) {
|
|
141
|
+
console.log('This file contains JSX');
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error('Error:', error.message);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### `hasJSXInString(source: string): boolean`
|
|
149
|
+
|
|
150
|
+
Analyzes a source code string to detect JSX syntax (synchronous).
|
|
151
|
+
|
|
152
|
+
**Parameters:**
|
|
153
|
+
- `source` - Source code string to analyze
|
|
154
|
+
|
|
155
|
+
**Returns:**
|
|
156
|
+
- `boolean` - `true` if JSX is detected, `false` otherwise
|
|
157
|
+
|
|
158
|
+
**Throws:**
|
|
159
|
+
- `TypeError` - If source is not a string
|
|
160
|
+
|
|
161
|
+
**Example:**
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
import { hasJSXInString } from 'has-jsx';
|
|
165
|
+
|
|
166
|
+
const code = 'const Button = () => <button>Click</button>';
|
|
167
|
+
const result = hasJSXInString(code);
|
|
168
|
+
console.log(result); // true
|
|
169
|
+
|
|
170
|
+
// No false positives for TypeScript generics
|
|
171
|
+
const genericCode = 'function identity<T>(arg: T): T { return arg; }';
|
|
172
|
+
console.log(hasJSXInString(genericCode)); // false
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## How It Works
|
|
176
|
+
|
|
177
|
+
1. **AST Parsing**: Uses [ast-grep](https://github.com/ast-grep/ast-grep) to parse files as TypeScript/TSX
|
|
178
|
+
2. **Node Detection**: Searches the AST for JSX-specific node types defined in the [JSX specification](https://facebook.github.io/jsx/)
|
|
179
|
+
3. **Reliable Results**: Avoids false positives from regex-based detection or angle brackets in comparisons
|
|
180
|
+
|
|
181
|
+
## Use Cases
|
|
182
|
+
|
|
183
|
+
- **Build Tools**: Determine which files need JSX transformation
|
|
184
|
+
- **Linters**: Validate JSX usage against project rules
|
|
185
|
+
- **Code Analysis**: Audit codebase for JSX patterns
|
|
186
|
+
- **CI/CD**: Verify file types in automated pipelines
|
|
187
|
+
- **Migration Tools**: Identify files during framework migrations
|
|
188
|
+
|
|
189
|
+
## Requirements
|
|
190
|
+
|
|
191
|
+
- Node.js >= 20.0.0
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT
|
|
196
|
+
|
|
197
|
+
## Contributing
|
|
198
|
+
|
|
199
|
+
Issues and pull requests welcome! Please ensure all tests pass before submitting:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npm test
|
|
203
|
+
```
|
package/bin/has-jsx.js
ADDED
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
|
|
2
|
+
let fs_promises = require("fs/promises");
|
|
3
|
+
let fs = require("fs");
|
|
4
|
+
let _ast_grep_napi = require("@ast-grep/napi");
|
|
5
|
+
|
|
6
|
+
//#region src/utils.js
|
|
7
|
+
/**
|
|
8
|
+
* Reads file content from the filesystem with validation.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} filepath - Absolute or relative path to file
|
|
11
|
+
* @returns {Promise<string>} File content
|
|
12
|
+
* @throws {Error} If file doesn't exist, is not a file, or can't be read
|
|
13
|
+
*/
|
|
14
|
+
async function readFileContent(filepath) {
|
|
15
|
+
if (!(0, fs.existsSync)(filepath)) throw new Error(`File not found: ${filepath}`);
|
|
16
|
+
if (!(0, fs.statSync)(filepath).isFile()) throw new Error(`Path is not a file: ${filepath}`);
|
|
17
|
+
return (0, fs_promises.readFile)(filepath, "utf-8");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/detector.js
|
|
22
|
+
/**
|
|
23
|
+
* Detects if source code contains JSX syntax using AST analysis.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} source - Source code to analyze
|
|
26
|
+
* @returns {boolean} true if JSX is detected, false otherwise
|
|
27
|
+
*/
|
|
28
|
+
function containsJSX(source) {
|
|
29
|
+
try {
|
|
30
|
+
return _ast_grep_napi.tsx.parse(source).root().findAll({ rule: { any: [{ kind: "jsx_element" }, { kind: "jsx_self_closing_element" }] } }).length > 0;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/index.js
|
|
38
|
+
/**
|
|
39
|
+
* Checks if a file contains JSX syntax.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} filepath - Path to the file to analyze
|
|
42
|
+
* @returns {Promise<boolean>} true if JSX is detected, false otherwise
|
|
43
|
+
* @throws {Error} If file can't be read
|
|
44
|
+
*/
|
|
45
|
+
async function hasJSX(filepath) {
|
|
46
|
+
return containsJSX(await readFileContent(filepath));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Checks if a string contains JSX syntax.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} source - Source code string to analyze
|
|
52
|
+
* @returns {boolean} true if JSX is detected, false otherwise
|
|
53
|
+
* @throws {TypeError} If source is not a string
|
|
54
|
+
*/
|
|
55
|
+
function hasJSXInString(source) {
|
|
56
|
+
if (typeof source !== "string") throw new TypeError("Expected source to be a string");
|
|
57
|
+
return containsJSX(source);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
exports.default = hasJSX;
|
|
62
|
+
exports.hasJSX = hasJSX;
|
|
63
|
+
exports.hasJSXInString = hasJSXInString;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFile } from "fs/promises";
|
|
2
|
+
import { existsSync, statSync } from "fs";
|
|
3
|
+
import { tsx } from "@ast-grep/napi";
|
|
4
|
+
|
|
5
|
+
//#region src/utils.js
|
|
6
|
+
/**
|
|
7
|
+
* Reads file content from the filesystem with validation.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} filepath - Absolute or relative path to file
|
|
10
|
+
* @returns {Promise<string>} File content
|
|
11
|
+
* @throws {Error} If file doesn't exist, is not a file, or can't be read
|
|
12
|
+
*/
|
|
13
|
+
async function readFileContent(filepath) {
|
|
14
|
+
if (!existsSync(filepath)) throw new Error(`File not found: ${filepath}`);
|
|
15
|
+
if (!statSync(filepath).isFile()) throw new Error(`Path is not a file: ${filepath}`);
|
|
16
|
+
return readFile(filepath, "utf-8");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/detector.js
|
|
21
|
+
/**
|
|
22
|
+
* Detects if source code contains JSX syntax using AST analysis.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} source - Source code to analyze
|
|
25
|
+
* @returns {boolean} true if JSX is detected, false otherwise
|
|
26
|
+
*/
|
|
27
|
+
function containsJSX(source) {
|
|
28
|
+
try {
|
|
29
|
+
return tsx.parse(source).root().findAll({ rule: { any: [{ kind: "jsx_element" }, { kind: "jsx_self_closing_element" }] } }).length > 0;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/index.js
|
|
37
|
+
/**
|
|
38
|
+
* Checks if a file contains JSX syntax.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} filepath - Path to the file to analyze
|
|
41
|
+
* @returns {Promise<boolean>} true if JSX is detected, false otherwise
|
|
42
|
+
* @throws {Error} If file can't be read
|
|
43
|
+
*/
|
|
44
|
+
async function hasJSX(filepath) {
|
|
45
|
+
return containsJSX(await readFileContent(filepath));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Checks if a string contains JSX syntax.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} source - Source code string to analyze
|
|
51
|
+
* @returns {boolean} true if JSX is detected, false otherwise
|
|
52
|
+
* @throws {TypeError} If source is not a string
|
|
53
|
+
*/
|
|
54
|
+
function hasJSXInString(source) {
|
|
55
|
+
if (typeof source !== "string") throw new TypeError("Expected source to be a string");
|
|
56
|
+
return containsJSX(source);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
//#endregion
|
|
60
|
+
export { hasJSX as default, hasJSX, hasJSXInString };
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "has-jsx",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "CLI and programmatic tool to detect JSX in files using AST analysis",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"bin": {
|
|
9
|
+
"has-jsx": "bin/has-jsx.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"require": "./dist/index.cjs",
|
|
15
|
+
"default": "./dist/index.mjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"bin",
|
|
21
|
+
"src",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "npx tsdown",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"test:coverage": "vitest run --coverage",
|
|
30
|
+
"prepublishOnly": "npm run build && npm test"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"jsx",
|
|
34
|
+
"detect",
|
|
35
|
+
"ast",
|
|
36
|
+
"cli",
|
|
37
|
+
"typescript",
|
|
38
|
+
"react"
|
|
39
|
+
],
|
|
40
|
+
"author": "",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20.0.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@ast-grep/napi": "^0.40.5",
|
|
47
|
+
"commander": "^14.0.3"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"tsdown": "^0.20.3",
|
|
51
|
+
"vitest": "^4.0.18"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
|
|
7
|
+
import { hasJSX, hasJSXInString } from './index.js';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Runs the CLI with the provided arguments.
|
|
14
|
+
*
|
|
15
|
+
* @param {string[]} argv - Command line arguments (process.argv)
|
|
16
|
+
* @returns {Promise<void>}
|
|
17
|
+
*/
|
|
18
|
+
export async function run(argv) {
|
|
19
|
+
const packageJson = JSON.parse(
|
|
20
|
+
readFileSync(join(__dirname, '../package.json'), 'utf-8')
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const program = new Command();
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.name('has-jsx')
|
|
27
|
+
.description('Detect JSX in files or strings using AST analysis')
|
|
28
|
+
.version(packageJson.version)
|
|
29
|
+
.argument('[source]', 'Source code string to analyze (use -f for files)')
|
|
30
|
+
.option('-f, --file <filepath>', 'Path to file to analyze')
|
|
31
|
+
.option('-v, --verbose', 'Show detailed JSON output')
|
|
32
|
+
.option('-q, --quiet', 'Silent mode (exit codes only)')
|
|
33
|
+
.exitOverride()
|
|
34
|
+
.configureOutput({
|
|
35
|
+
writeOut: (str) => console.log(str.trimEnd()),
|
|
36
|
+
writeErr: (str) => console.error(str.trimEnd()),
|
|
37
|
+
})
|
|
38
|
+
.action(async (source, options) => {
|
|
39
|
+
try {
|
|
40
|
+
let result;
|
|
41
|
+
let inputType;
|
|
42
|
+
let inputValue;
|
|
43
|
+
|
|
44
|
+
if (options.file) {
|
|
45
|
+
result = await hasJSX(options.file);
|
|
46
|
+
inputType = 'file';
|
|
47
|
+
inputValue = options.file;
|
|
48
|
+
} else if (source) {
|
|
49
|
+
result = hasJSXInString(source);
|
|
50
|
+
inputType = 'string';
|
|
51
|
+
inputValue = source;
|
|
52
|
+
} else {
|
|
53
|
+
if (!options.quiet) {
|
|
54
|
+
console.error('Error: No input provided. Use -f <filepath> or provide source code string.');
|
|
55
|
+
}
|
|
56
|
+
process.exit(2);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const exitCode = result ? 0 : 1;
|
|
61
|
+
|
|
62
|
+
if (options.quiet) {
|
|
63
|
+
process.exit(exitCode);
|
|
64
|
+
} else if (options.verbose) {
|
|
65
|
+
const inputKey = inputType === 'file' ? 'file' : 'source';
|
|
66
|
+
const output = { hasJSX: result, inputType, [inputKey]: inputValue };
|
|
67
|
+
console.log(JSON.stringify(output, null, 2));
|
|
68
|
+
process.exit(exitCode);
|
|
69
|
+
} else {
|
|
70
|
+
console.log(result ? 'JSX detected' : 'No JSX detected');
|
|
71
|
+
process.exit(exitCode);
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (isProcessExitError(error)) {
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
if (!options.quiet) {
|
|
78
|
+
console.error(`Error: ${error.message}`);
|
|
79
|
+
}
|
|
80
|
+
process.exit(2);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await program.parseAsync(argv);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (isProcessExitError(error) || isCommanderError(error)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isProcessExitError(error) {
|
|
95
|
+
return error.message?.startsWith('process.exit(');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isCommanderError(error) {
|
|
99
|
+
return error.code?.startsWith('commander.');
|
|
100
|
+
}
|
package/src/detector.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { tsx } from '@ast-grep/napi';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Detects if source code contains JSX syntax using AST analysis.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} source - Source code to analyze
|
|
7
|
+
* @returns {boolean} true if JSX is detected, false otherwise
|
|
8
|
+
*/
|
|
9
|
+
export function containsJSX(source) {
|
|
10
|
+
try {
|
|
11
|
+
const root = tsx.parse(source).root();
|
|
12
|
+
|
|
13
|
+
// jsx_element covers standard elements and fragments (<>...</>)
|
|
14
|
+
const jsxNodes = root.findAll({
|
|
15
|
+
rule: {
|
|
16
|
+
any: [
|
|
17
|
+
{ kind: 'jsx_element' },
|
|
18
|
+
{ kind: 'jsx_self_closing_element' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return jsxNodes.length > 0;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { readFileContent } from './utils.js';
|
|
2
|
+
import { containsJSX } from './detector.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Checks if a file contains JSX syntax.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} filepath - Path to the file to analyze
|
|
8
|
+
* @returns {Promise<boolean>} true if JSX is detected, false otherwise
|
|
9
|
+
* @throws {Error} If file can't be read
|
|
10
|
+
*/
|
|
11
|
+
export async function hasJSX(filepath) {
|
|
12
|
+
const content = await readFileContent(filepath);
|
|
13
|
+
return containsJSX(content);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Checks if a string contains JSX syntax.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} source - Source code string to analyze
|
|
20
|
+
* @returns {boolean} true if JSX is detected, false otherwise
|
|
21
|
+
* @throws {TypeError} If source is not a string
|
|
22
|
+
*/
|
|
23
|
+
export function hasJSXInString(source) {
|
|
24
|
+
if (typeof source !== 'string') {
|
|
25
|
+
throw new TypeError('Expected source to be a string');
|
|
26
|
+
}
|
|
27
|
+
return containsJSX(source);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default hasJSX;
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { existsSync, statSync } from 'fs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reads file content from the filesystem with validation.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} filepath - Absolute or relative path to file
|
|
8
|
+
* @returns {Promise<string>} File content
|
|
9
|
+
* @throws {Error} If file doesn't exist, is not a file, or can't be read
|
|
10
|
+
*/
|
|
11
|
+
export async function readFileContent(filepath) {
|
|
12
|
+
if (!existsSync(filepath)) {
|
|
13
|
+
throw new Error(`File not found: ${filepath}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const stats = statSync(filepath);
|
|
17
|
+
if (!stats.isFile()) {
|
|
18
|
+
throw new Error(`Path is not a file: ${filepath}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return readFile(filepath, 'utf-8');
|
|
22
|
+
}
|