semantic-inspector 0.1.0 → 0.2.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 +1 -1
- package/README.md +120 -49
- package/dist/babel.cjs +7 -59
- package/dist/babel.cjs.map +1 -1
- package/dist/babel.d.cts +10 -10
- package/dist/babel.d.ts +10 -10
- package/dist/babel.js +1 -1
- package/dist/{chunk-AAPCI2HO.js → chunk-AQYKTX6L.js} +13 -9
- package/dist/chunk-AQYKTX6L.js.map +1 -0
- package/dist/chunk-X6NMJJ2M.cjs +68 -0
- package/dist/chunk-X6NMJJ2M.cjs.map +1 -0
- package/dist/index.cjs +95 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -41
- package/dist/index.d.ts +37 -41
- package/dist/index.js +98 -53
- package/dist/index.js.map +1 -1
- package/dist/vite.cjs +5 -64
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.d.cts +7 -7
- package/dist/vite.d.ts +7 -7
- package/dist/vite.js +4 -2
- package/dist/vite.js.map +1 -1
- package/package.json +24 -8
- package/dist/chunk-AAPCI2HO.js.map +0 -1
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 ghost-vk and contributors
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
# semantic-inspector
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
**Shift+click** copies a PNG screenshot of just that element. Built for pasting
|
|
7
|
-
precise UI context into an AI chat in seconds.
|
|
3
|
+
[](https://www.npmjs.com/package/semantic-inspector)
|
|
4
|
+
[](https://github.com/ghost-vk/semantic-inspector/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE)
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
A dev-only React inspector for vibe-coding. Hit a hotkey to enter inspect mode: hovering highlights
|
|
8
|
+
the element under the cursor and shows its component name + `file:line:col`. **Click** copies that
|
|
9
|
+
text identifier to the clipboard; **Shift+click** copies a PNG screenshot of just that element.
|
|
10
|
+
Built for pasting precise UI context into an AI chat in seconds.
|
|
11
|
+
|
|
12
|
+
Stack: Vite + `@vitejs/plugin-react` + React 18/19. Designed to add **no production runtime cost
|
|
13
|
+
when you gate and lazy-load it** (see [Mount it](#2-mount-it-behind-your-own-dev-flag-ideally-lazy)) —
|
|
14
|
+
`modern-screenshot` is loaded lazily and the source-stamping plugin runs only on the dev server.
|
|
15
|
+
|
|
16
|
+
## Demo
|
|
17
|
+
|
|
18
|
+
<video src="https://github.com/ghost-vk/semantic-inspector/raw/main/docs/demo.mp4" controls muted loop width="600"></video>
|
|
19
|
+
|
|
20
|
+
_Player not loading? [Watch the demo.](https://github.com/ghost-vk/semantic-inspector/raw/main/docs/demo.mp4)_
|
|
11
21
|
|
|
12
22
|
## Install
|
|
13
23
|
|
|
@@ -15,31 +25,49 @@ production — you gate where it mounts.
|
|
|
15
25
|
npm i -D semantic-inspector
|
|
16
26
|
```
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
Peer dependencies:
|
|
29
|
+
|
|
30
|
+
- `react` / `react-dom` (`>=18`) — required.
|
|
31
|
+
- `vite` (`>=5`) — optional, only for `semantic-inspector/vite`.
|
|
32
|
+
- `@babel/core` (`>=7.25`) — optional, only for `semantic-inspector/vite` or
|
|
33
|
+
`semantic-inspector/babel`. Most Vite + React projects already have it; if not:
|
|
34
|
+
```sh
|
|
35
|
+
npm i -D @babel/core
|
|
36
|
+
```
|
|
37
|
+
Pure-runtime consumers (`<SemanticInspector/>` only) don't need it.
|
|
20
38
|
|
|
21
39
|
## How it works
|
|
22
40
|
|
|
23
|
-
Source locations come from a **build-time stamp**, not React internals. A Babel
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
Source locations come from a **build-time stamp**, not React internals. A Babel pass adds
|
|
42
|
+
`data-loc="<path>:<line>:<col>"` and `data-comp="<Component>"` to JSX host elements (`div`,
|
|
43
|
+
`section`, …). The runtime reads those DOM attributes, so it stays robust across React versions. If
|
|
44
|
+
a node isn't stamped (prod build, foreign node), it degrades gracefully: fiber `displayName` →
|
|
45
|
+
filename → tag name.
|
|
46
|
+
|
|
47
|
+
```mermaid
|
|
48
|
+
flowchart LR
|
|
49
|
+
A[".tsx source"] -->|"Babel: stampLocVite / stampLocBabel<br/>(dev only)"| B["DOM with data-loc / data-comp"]
|
|
50
|
+
B -->|"hover → elementFromPoint"| C["resolveTarget<br/>closest([data-loc])"]
|
|
51
|
+
C --> D["Overlay highlight + tip"]
|
|
52
|
+
C -->|"click"| E["clipboard: text"]
|
|
53
|
+
C -->|"Shift+click"| F["clipboard: PNG (modern-screenshot)"]
|
|
54
|
+
```
|
|
28
55
|
|
|
29
56
|
## Three entry points
|
|
30
57
|
|
|
31
|
-
| Import
|
|
32
|
-
|
|
|
33
|
-
| `semantic-inspector`
|
|
34
|
-
| `semantic-inspector/vite`
|
|
35
|
-
| `semantic-inspector/babel`
|
|
58
|
+
| Import | What it is |
|
|
59
|
+
| -------------------------- | ------------------------------------------------------------------ |
|
|
60
|
+
| `semantic-inspector` | `<SemanticInspector/>` + `useInspector()` — overlay/hotkey/clipboard runtime. |
|
|
61
|
+
| `semantic-inspector/vite` | `stampLocVite()` — Vite plugin that stamps `data-loc` / `data-comp`. |
|
|
62
|
+
| `semantic-inspector/babel` | `{ stampLocBabel }` — raw Babel plugin, for the Babel variant of `@vitejs/plugin-react`. |
|
|
36
63
|
|
|
37
64
|
## Usage
|
|
38
65
|
|
|
39
66
|
### 1. Stamp source locations (Vite plugin)
|
|
40
67
|
|
|
41
|
-
`@vitejs/plugin-react` **v6** transpiles via oxc (no Babel hook), so stamp with a
|
|
42
|
-
|
|
68
|
+
`@vitejs/plugin-react` **v6** transpiles via oxc (no Babel hook), so stamp with a separate `pre`
|
|
69
|
+
plugin. **This is the recommended path.** The plugin runs only on the dev server (`apply: 'serve'`),
|
|
70
|
+
so `data-loc` / `data-comp` never reach a production build.
|
|
43
71
|
|
|
44
72
|
```ts
|
|
45
73
|
import react from '@vitejs/plugin-react';
|
|
@@ -52,15 +80,24 @@ export default defineConfig({
|
|
|
52
80
|
});
|
|
53
81
|
```
|
|
54
82
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
83
|
+
On the **Babel variant** of plugin-react you can skip the separate pre-pass by adding the plugin to
|
|
84
|
+
plugin-react's Babel options instead. Use **one** approach, not both. Note this forces plugin-react
|
|
85
|
+
onto Babel for all files (slower than the oxc + pre-pass above), so prefer option 1 unless you're
|
|
86
|
+
already on the Babel variant. Gate it to development so stamps stay out of production:
|
|
58
87
|
|
|
59
88
|
```ts
|
|
60
89
|
import react from '@vitejs/plugin-react';
|
|
61
|
-
import
|
|
62
|
-
|
|
63
|
-
|
|
90
|
+
import { stampLocBabel } from 'semantic-inspector/babel';
|
|
91
|
+
|
|
92
|
+
export default defineConfig(({ command }) => ({
|
|
93
|
+
plugins: [
|
|
94
|
+
react({
|
|
95
|
+
babel: {
|
|
96
|
+
plugins: command === 'serve' ? [[stampLocBabel, { rootDir: process.cwd() }]] : []
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
]
|
|
100
|
+
}));
|
|
64
101
|
```
|
|
65
102
|
|
|
66
103
|
### 2. Mount it (behind your own dev flag, ideally lazy)
|
|
@@ -81,33 +118,67 @@ const SemanticInspector = lazy(() =>
|
|
|
81
118
|
}
|
|
82
119
|
```
|
|
83
120
|
|
|
84
|
-
##
|
|
121
|
+
## API
|
|
85
122
|
|
|
86
|
-
|
|
87
|
-
| ------------ | ------------------------ | ---------------------------------------- |
|
|
88
|
-
| `hotkey` | `'Alt+Shift+S'` | toggle inspect mode (Esc exits) |
|
|
89
|
-
| `formatText` | `` `${comp} — ${loc}` `` | format of the text copied on click |
|
|
90
|
-
| `onCopy` | — | callback after a copy (telemetry/toasts) |
|
|
91
|
-
| `onError` | — | callback on clipboard/screenshot failure |
|
|
123
|
+
### `<SemanticInspector>` props
|
|
92
124
|
|
|
93
|
-
|
|
125
|
+
| prop | default | purpose |
|
|
126
|
+
| ------------ | ------------------------ | ----------------------------------------- |
|
|
127
|
+
| `hotkey` | `'Alt+Shift+S'` | toggle inspect mode (Esc always exits) |
|
|
128
|
+
| `formatText` | `` `${comp} — ${loc}` `` | format of the text copied on click; receives `{ comp, loc }` (`loc` may be `null`) |
|
|
129
|
+
| `onCopy` | — | called after a successful copy |
|
|
130
|
+
| `onError` | — | called on a clipboard/screenshot failure |
|
|
94
131
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
CORS and some exotic CSS may not render.
|
|
99
|
-
- On a prod React build without `data-loc`, the name falls back to the fiber
|
|
100
|
-
(minified) or tag — degraded mode. Full mode needs the build-time stamp.
|
|
132
|
+
`useInspector(props)` is also exported for building a custom overlay; it returns
|
|
133
|
+
`{ active, target }`. Note: used raw (not via `<SemanticInspector>`), it has no default `onError`,
|
|
134
|
+
so failures only surface via `console.warn` unless you pass one.
|
|
101
135
|
|
|
102
|
-
|
|
136
|
+
#### Callback payloads
|
|
103
137
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
138
|
+
- `onCopy('text', payload)` — `payload` is the copied string.
|
|
139
|
+
- `onCopy('screenshot', payload)` — `payload` is the **component name** (the PNG goes to the
|
|
140
|
+
clipboard, not to the callback).
|
|
141
|
+
- `onError(kind, err)` — `err` is the underlying error (`unknown`).
|
|
142
|
+
|
|
143
|
+
### Plugin options (`stampLocVite` / `stampLocBabel`)
|
|
144
|
+
|
|
145
|
+
| option | default | applies to | purpose |
|
|
146
|
+
| ---------- | ---------------- | ------------ | ---------------------------------------------- |
|
|
147
|
+
| `rootDir` | `process.cwd()` | both | base for the relative path written into `data-loc` |
|
|
148
|
+
| `include` | `/\.[jt]sx$/` | `/vite` only | which module ids get stamped |
|
|
149
|
+
| `attrLoc` | `'data-loc'` | both | attribute name for `path:line:col` |
|
|
150
|
+
| `attrComp` | `'data-comp'` | both | attribute name for the component name |
|
|
151
|
+
|
|
152
|
+
Files outside `rootDir` degrade to their basename, so an absolute filesystem path never leaks into
|
|
153
|
+
the stamped DOM.
|
|
154
|
+
|
|
155
|
+
## Hotkey format
|
|
156
|
+
|
|
157
|
+
`Modifier+...+Key`. Modifiers: `Alt`, `Shift`, `Ctrl` (or `Control`), `Meta` (or `Cmd`). The final
|
|
158
|
+
token is the key. Matching is case-insensitive and also matches the physical `event.code`, so
|
|
159
|
+
layout-shifted glyphs work (e.g. `Ctrl+Shift+/` matches even though Shift produces `?`). Digit and
|
|
160
|
+
punctuation keys are supported (`Alt+1`, `Ctrl+/`). `Esc` always exits inspect mode.
|
|
161
|
+
|
|
162
|
+
## Troubleshooting
|
|
163
|
+
|
|
164
|
+
| Symptom | Cause | Fix |
|
|
165
|
+
| --- | --- | --- |
|
|
166
|
+
| Nothing copies, no error | `navigator.clipboard` needs a secure context | Use `localhost` or `https://`, not `http://192.168.x.x` |
|
|
167
|
+
| Screenshot is blank/partial | CORS-tainted canvas / unsupported CSS | Serve images with CORS headers; some CSS (cross-origin `<img>`, exotic filters) won't rasterize |
|
|
168
|
+
| `loc` shows `no source` / name is minified | Node wasn't stamped (prod build, or plugin not registered) | Register the stamper (Usage 1) and confirm it isn't gated out in dev |
|
|
169
|
+
| Hotkey does nothing | Focus is in an input, or a typo in the combo | Use a `Modifier+Key` combo (see above); try the default `Alt+Shift+S` |
|
|
170
|
+
|
|
171
|
+
## Browser support
|
|
172
|
+
|
|
173
|
+
Works in current Chromium, Edge, Firefox, and Safari. Image-clipboard (Shift+click screenshot)
|
|
174
|
+
requires a `ClipboardItem`-capable browser; text copy works everywhere with a secure context.
|
|
175
|
+
|
|
176
|
+
## Contributing
|
|
177
|
+
|
|
178
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md). In short: `npm ci`, then
|
|
179
|
+
`npm run lint && npm run typecheck && npm test && npm run build`. `dist/` is generated — build once
|
|
180
|
+
after cloning before `npm link`.
|
|
110
181
|
|
|
111
182
|
## License
|
|
112
183
|
|
|
113
|
-
MIT
|
|
184
|
+
[MIT](./LICENSE) © ghost-vk
|
package/dist/babel.cjs
CHANGED
|
@@ -1,64 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
function isHostElement(name) {
|
|
5
|
-
return name.type === "JSXIdentifier" && /^[a-z]/.test(name.name);
|
|
6
|
-
}
|
|
7
|
-
function hasAttr(el, attrName) {
|
|
8
|
-
return el.attributes.some(
|
|
9
|
-
(a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === attrName
|
|
10
|
-
);
|
|
11
|
-
}
|
|
12
|
-
function nearestComponentName(path) {
|
|
13
|
-
let p = path;
|
|
14
|
-
while (p) {
|
|
15
|
-
const node = p.node;
|
|
16
|
-
if (node.type === "FunctionDeclaration" && node.id && /^[A-Z]/.test(node.id.name)) {
|
|
17
|
-
return node.id.name;
|
|
18
|
-
}
|
|
19
|
-
if (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
20
|
-
const parent = p.parentPath?.node;
|
|
21
|
-
if (parent?.type === "VariableDeclarator" && parent.id.type === "Identifier" && /^[A-Z]/.test(parent.id.name)) {
|
|
22
|
-
return parent.id.name;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
p = p.parentPath;
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
function stampLocBabel(babel, opts = {}) {
|
|
30
|
-
const t = babel.types;
|
|
31
|
-
const attrLoc = opts.attrLoc ?? "data-loc";
|
|
32
|
-
const attrComp = opts.attrComp ?? "data-comp";
|
|
33
|
-
const rootDir = opts.rootDir ?? process.cwd();
|
|
34
|
-
const attr = (name, value) => t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(value));
|
|
35
|
-
const toRel = (file) => {
|
|
36
|
-
let root = rootDir;
|
|
37
|
-
while (root.endsWith("/")) root = root.slice(0, -1);
|
|
38
|
-
const rel = file.startsWith(root + "/") ? file.slice(root.length + 1) : file;
|
|
39
|
-
return rel.split("\\").join("/");
|
|
40
|
-
};
|
|
41
|
-
return {
|
|
42
|
-
name: "stamp-loc",
|
|
43
|
-
visitor: {
|
|
44
|
-
JSXOpeningElement(path, state) {
|
|
45
|
-
const node = path.node;
|
|
46
|
-
if (!isHostElement(node.name)) return;
|
|
47
|
-
const filename = state.file.opts.filename;
|
|
48
|
-
const loc = node.loc;
|
|
49
|
-
if (!filename || !loc) return;
|
|
50
|
-
if (!hasAttr(node, attrLoc)) {
|
|
51
|
-
node.attributes.push(attr(attrLoc, `${toRel(filename)}:${loc.start.line}`));
|
|
52
|
-
}
|
|
53
|
-
if (!hasAttr(node, attrComp)) {
|
|
54
|
-
const comp = nearestComponentName(path);
|
|
55
|
-
if (comp) node.attributes.push(attr(attrComp, comp));
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
3
|
+
var chunkX6NMJJ2M_cjs = require('./chunk-X6NMJJ2M.cjs');
|
|
61
4
|
|
|
62
|
-
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Object.defineProperty(exports, "stampLocBabel", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function () { return chunkX6NMJJ2M_cjs.stampLocBabel; }
|
|
10
|
+
});
|
|
63
11
|
//# sourceMappingURL=babel.cjs.map
|
|
64
12
|
//# sourceMappingURL=babel.cjs.map
|
package/dist/babel.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"babel.cjs"}
|
package/dist/babel.d.cts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { types, PluginObj } from '@babel/core';
|
|
1
|
+
import { ConfigAPI, types, PluginObj } from '@babel/core';
|
|
2
2
|
|
|
3
3
|
interface StampLocOptions {
|
|
4
|
-
/**
|
|
4
|
+
/** Path attribute name. Default 'data-loc'. */
|
|
5
5
|
attrLoc?: string;
|
|
6
|
-
/**
|
|
6
|
+
/** Component attribute name. Default 'data-comp'. */
|
|
7
7
|
attrComp?: string;
|
|
8
|
-
/**
|
|
8
|
+
/** Base for the relative path written into data-loc. Default process.cwd(). */
|
|
9
9
|
rootDir?: string;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Babel
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Babel plugin: stamps data-loc="<path>:<line>:<col>" and data-comp="<Component>" onto JSX
|
|
13
|
+
* host elements (div, section, ...). The runtime inspector reads these DOM attributes (not
|
|
14
|
+
* React internals), so it stays robust across React versions. Component tags (PascalCase) are
|
|
15
|
+
* skipped — they don't produce their own DOM node.
|
|
16
16
|
*/
|
|
17
|
-
declare function stampLocBabel(
|
|
17
|
+
declare function stampLocBabel(api: ConfigAPI & {
|
|
18
18
|
types: typeof types;
|
|
19
19
|
}, opts?: StampLocOptions): PluginObj;
|
|
20
20
|
|
|
21
|
-
export { type StampLocOptions, stampLocBabel
|
|
21
|
+
export { type StampLocOptions, stampLocBabel };
|
package/dist/babel.d.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { types, PluginObj } from '@babel/core';
|
|
1
|
+
import { ConfigAPI, types, PluginObj } from '@babel/core';
|
|
2
2
|
|
|
3
3
|
interface StampLocOptions {
|
|
4
|
-
/**
|
|
4
|
+
/** Path attribute name. Default 'data-loc'. */
|
|
5
5
|
attrLoc?: string;
|
|
6
|
-
/**
|
|
6
|
+
/** Component attribute name. Default 'data-comp'. */
|
|
7
7
|
attrComp?: string;
|
|
8
|
-
/**
|
|
8
|
+
/** Base for the relative path written into data-loc. Default process.cwd(). */
|
|
9
9
|
rootDir?: string;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
* Babel
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* Babel plugin: stamps data-loc="<path>:<line>:<col>" and data-comp="<Component>" onto JSX
|
|
13
|
+
* host elements (div, section, ...). The runtime inspector reads these DOM attributes (not
|
|
14
|
+
* React internals), so it stays robust across React versions. Component tags (PascalCase) are
|
|
15
|
+
* skipped — they don't produce their own DOM node.
|
|
16
16
|
*/
|
|
17
|
-
declare function stampLocBabel(
|
|
17
|
+
declare function stampLocBabel(api: ConfigAPI & {
|
|
18
18
|
types: typeof types;
|
|
19
19
|
}, opts?: StampLocOptions): PluginObj;
|
|
20
20
|
|
|
21
|
-
export { type StampLocOptions, stampLocBabel
|
|
21
|
+
export { type StampLocOptions, stampLocBabel };
|
package/dist/babel.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { relative, isAbsolute, sep } from 'path';
|
|
2
|
+
|
|
1
3
|
// src/stampLocBabel.ts
|
|
2
4
|
function isHostElement(name) {
|
|
3
5
|
return name.type === "JSXIdentifier" && /^[a-z]/.test(name.name);
|
|
@@ -24,17 +26,19 @@ function nearestComponentName(path) {
|
|
|
24
26
|
}
|
|
25
27
|
return null;
|
|
26
28
|
}
|
|
27
|
-
function stampLocBabel(
|
|
28
|
-
|
|
29
|
+
function stampLocBabel(api, opts = {}) {
|
|
30
|
+
api.assertVersion(7);
|
|
31
|
+
const t = api.types;
|
|
29
32
|
const attrLoc = opts.attrLoc ?? "data-loc";
|
|
30
33
|
const attrComp = opts.attrComp ?? "data-comp";
|
|
31
34
|
const rootDir = opts.rootDir ?? process.cwd();
|
|
32
35
|
const attr = (name, value) => t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(value));
|
|
33
36
|
const toRel = (file) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const rel = relative(rootDir, file);
|
|
38
|
+
if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
|
|
39
|
+
return file.split(/[\\/]/).pop() ?? "unknown";
|
|
40
|
+
}
|
|
41
|
+
return rel.split(sep).join("/");
|
|
38
42
|
};
|
|
39
43
|
return {
|
|
40
44
|
name: "stamp-loc",
|
|
@@ -46,7 +50,7 @@ function stampLocBabel(babel, opts = {}) {
|
|
|
46
50
|
const loc = node.loc;
|
|
47
51
|
if (!filename || !loc) return;
|
|
48
52
|
if (!hasAttr(node, attrLoc)) {
|
|
49
|
-
node.attributes.push(attr(attrLoc, `${toRel(filename)}:${loc.start.line}`));
|
|
53
|
+
node.attributes.push(attr(attrLoc, `${toRel(filename)}:${loc.start.line}:${loc.start.column + 1}`));
|
|
50
54
|
}
|
|
51
55
|
if (!hasAttr(node, attrComp)) {
|
|
52
56
|
const comp = nearestComponentName(path);
|
|
@@ -58,5 +62,5 @@ function stampLocBabel(babel, opts = {}) {
|
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
export { stampLocBabel };
|
|
61
|
-
//# sourceMappingURL=chunk-
|
|
62
|
-
//# sourceMappingURL=chunk-
|
|
65
|
+
//# sourceMappingURL=chunk-AQYKTX6L.js.map
|
|
66
|
+
//# sourceMappingURL=chunk-AQYKTX6L.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stampLocBabel.ts"],"names":[],"mappings":"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","file":"chunk-AQYKTX6L.js","sourcesContent":["import { isAbsolute, relative, sep } from 'node:path';\nimport type { types as BabelTypes, ConfigAPI, NodePath, PluginObj } from '@babel/core';\n\nexport interface StampLocOptions {\n /** Path attribute name. Default 'data-loc'. */\n attrLoc?: string;\n /** Component attribute name. Default 'data-comp'. */\n attrComp?: string;\n /** Base for the relative path written into data-loc. Default process.cwd(). */\n rootDir?: string;\n}\n\nfunction isHostElement(name: BabelTypes.JSXOpeningElement['name']): boolean {\n return name.type === 'JSXIdentifier' && /^[a-z]/.test(name.name);\n}\n\nfunction hasAttr(el: BabelTypes.JSXOpeningElement, attrName: string): boolean {\n return el.attributes.some(\n (a) => a.type === 'JSXAttribute' && a.name.type === 'JSXIdentifier' && a.name.name === attrName\n );\n}\n\n// Nearest PascalCase function-component ancestor:\n// function Foo() {} | const Foo = () => {} | const Foo = function () {}\nfunction nearestComponentName(path: NodePath): string | null {\n let p: NodePath | null = path;\n while (p) {\n const node = p.node;\n if (node.type === 'FunctionDeclaration' && node.id && /^[A-Z]/.test(node.id.name)) {\n return node.id.name;\n }\n if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {\n const parent = p.parentPath?.node;\n if (parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier' && /^[A-Z]/.test(parent.id.name)) {\n return parent.id.name;\n }\n }\n p = p.parentPath;\n }\n return null;\n}\n\n/**\n * Babel plugin: stamps data-loc=\"<path>:<line>:<col>\" and data-comp=\"<Component>\" onto JSX\n * host elements (div, section, ...). The runtime inspector reads these DOM attributes (not\n * React internals), so it stays robust across React versions. Component tags (PascalCase) are\n * skipped — they don't produce their own DOM node.\n */\nexport function stampLocBabel(api: ConfigAPI & { types: typeof BabelTypes }, opts: StampLocOptions = {}): PluginObj {\n api.assertVersion(7);\n const t = api.types;\n const attrLoc = opts.attrLoc ?? 'data-loc';\n const attrComp = opts.attrComp ?? 'data-comp';\n const rootDir = opts.rootDir ?? process.cwd();\n\n const attr = (name: string, value: string) => t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(value));\n\n // Relative POSIX path from rootDir. Files outside rootDir degrade to their basename so an\n // absolute filesystem path can never leak into the stamped DOM.\n const toRel = (file: string): string => {\n const rel = relative(rootDir, file);\n if (!rel || rel.startsWith('..') || isAbsolute(rel)) {\n return file.split(/[\\\\/]/).pop() ?? 'unknown';\n }\n return rel.split(sep).join('/');\n };\n\n return {\n name: 'stamp-loc',\n visitor: {\n JSXOpeningElement(path, state) {\n const node = path.node;\n if (!isHostElement(node.name)) return;\n\n const filename = state.file.opts.filename;\n const loc = node.loc;\n if (!filename || !loc) return;\n\n if (!hasAttr(node, attrLoc)) {\n node.attributes.push(attr(attrLoc, `${toRel(filename)}:${loc.start.line}:${loc.start.column + 1}`));\n }\n if (!hasAttr(node, attrComp)) {\n const comp = nearestComponentName(path);\n if (comp) node.attributes.push(attr(attrComp, comp));\n }\n }\n }\n };\n}\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var path = require('path');
|
|
4
|
+
|
|
5
|
+
// src/stampLocBabel.ts
|
|
6
|
+
function isHostElement(name) {
|
|
7
|
+
return name.type === "JSXIdentifier" && /^[a-z]/.test(name.name);
|
|
8
|
+
}
|
|
9
|
+
function hasAttr(el, attrName) {
|
|
10
|
+
return el.attributes.some(
|
|
11
|
+
(a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === attrName
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
function nearestComponentName(path) {
|
|
15
|
+
let p = path;
|
|
16
|
+
while (p) {
|
|
17
|
+
const node = p.node;
|
|
18
|
+
if (node.type === "FunctionDeclaration" && node.id && /^[A-Z]/.test(node.id.name)) {
|
|
19
|
+
return node.id.name;
|
|
20
|
+
}
|
|
21
|
+
if (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") {
|
|
22
|
+
const parent = p.parentPath?.node;
|
|
23
|
+
if (parent?.type === "VariableDeclarator" && parent.id.type === "Identifier" && /^[A-Z]/.test(parent.id.name)) {
|
|
24
|
+
return parent.id.name;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
p = p.parentPath;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function stampLocBabel(api, opts = {}) {
|
|
32
|
+
api.assertVersion(7);
|
|
33
|
+
const t = api.types;
|
|
34
|
+
const attrLoc = opts.attrLoc ?? "data-loc";
|
|
35
|
+
const attrComp = opts.attrComp ?? "data-comp";
|
|
36
|
+
const rootDir = opts.rootDir ?? process.cwd();
|
|
37
|
+
const attr = (name, value) => t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(value));
|
|
38
|
+
const toRel = (file) => {
|
|
39
|
+
const rel = path.relative(rootDir, file);
|
|
40
|
+
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
41
|
+
return file.split(/[\\/]/).pop() ?? "unknown";
|
|
42
|
+
}
|
|
43
|
+
return rel.split(path.sep).join("/");
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
name: "stamp-loc",
|
|
47
|
+
visitor: {
|
|
48
|
+
JSXOpeningElement(path, state) {
|
|
49
|
+
const node = path.node;
|
|
50
|
+
if (!isHostElement(node.name)) return;
|
|
51
|
+
const filename = state.file.opts.filename;
|
|
52
|
+
const loc = node.loc;
|
|
53
|
+
if (!filename || !loc) return;
|
|
54
|
+
if (!hasAttr(node, attrLoc)) {
|
|
55
|
+
node.attributes.push(attr(attrLoc, `${toRel(filename)}:${loc.start.line}:${loc.start.column + 1}`));
|
|
56
|
+
}
|
|
57
|
+
if (!hasAttr(node, attrComp)) {
|
|
58
|
+
const comp = nearestComponentName(path);
|
|
59
|
+
if (comp) node.attributes.push(attr(attrComp, comp));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
exports.stampLocBabel = stampLocBabel;
|
|
67
|
+
//# sourceMappingURL=chunk-X6NMJJ2M.cjs.map
|
|
68
|
+
//# sourceMappingURL=chunk-X6NMJJ2M.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stampLocBabel.ts"],"names":["relative","isAbsolute","sep"],"mappings":"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","file":"chunk-X6NMJJ2M.cjs","sourcesContent":["import { isAbsolute, relative, sep } from 'node:path';\nimport type { types as BabelTypes, ConfigAPI, NodePath, PluginObj } from '@babel/core';\n\nexport interface StampLocOptions {\n /** Path attribute name. Default 'data-loc'. */\n attrLoc?: string;\n /** Component attribute name. Default 'data-comp'. */\n attrComp?: string;\n /** Base for the relative path written into data-loc. Default process.cwd(). */\n rootDir?: string;\n}\n\nfunction isHostElement(name: BabelTypes.JSXOpeningElement['name']): boolean {\n return name.type === 'JSXIdentifier' && /^[a-z]/.test(name.name);\n}\n\nfunction hasAttr(el: BabelTypes.JSXOpeningElement, attrName: string): boolean {\n return el.attributes.some(\n (a) => a.type === 'JSXAttribute' && a.name.type === 'JSXIdentifier' && a.name.name === attrName\n );\n}\n\n// Nearest PascalCase function-component ancestor:\n// function Foo() {} | const Foo = () => {} | const Foo = function () {}\nfunction nearestComponentName(path: NodePath): string | null {\n let p: NodePath | null = path;\n while (p) {\n const node = p.node;\n if (node.type === 'FunctionDeclaration' && node.id && /^[A-Z]/.test(node.id.name)) {\n return node.id.name;\n }\n if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {\n const parent = p.parentPath?.node;\n if (parent?.type === 'VariableDeclarator' && parent.id.type === 'Identifier' && /^[A-Z]/.test(parent.id.name)) {\n return parent.id.name;\n }\n }\n p = p.parentPath;\n }\n return null;\n}\n\n/**\n * Babel plugin: stamps data-loc=\"<path>:<line>:<col>\" and data-comp=\"<Component>\" onto JSX\n * host elements (div, section, ...). The runtime inspector reads these DOM attributes (not\n * React internals), so it stays robust across React versions. Component tags (PascalCase) are\n * skipped — they don't produce their own DOM node.\n */\nexport function stampLocBabel(api: ConfigAPI & { types: typeof BabelTypes }, opts: StampLocOptions = {}): PluginObj {\n api.assertVersion(7);\n const t = api.types;\n const attrLoc = opts.attrLoc ?? 'data-loc';\n const attrComp = opts.attrComp ?? 'data-comp';\n const rootDir = opts.rootDir ?? process.cwd();\n\n const attr = (name: string, value: string) => t.jsxAttribute(t.jsxIdentifier(name), t.stringLiteral(value));\n\n // Relative POSIX path from rootDir. Files outside rootDir degrade to their basename so an\n // absolute filesystem path can never leak into the stamped DOM.\n const toRel = (file: string): string => {\n const rel = relative(rootDir, file);\n if (!rel || rel.startsWith('..') || isAbsolute(rel)) {\n return file.split(/[\\\\/]/).pop() ?? 'unknown';\n }\n return rel.split(sep).join('/');\n };\n\n return {\n name: 'stamp-loc',\n visitor: {\n JSXOpeningElement(path, state) {\n const node = path.node;\n if (!isHostElement(node.name)) return;\n\n const filename = state.file.opts.filename;\n const loc = node.loc;\n if (!filename || !loc) return;\n\n if (!hasAttr(node, attrLoc)) {\n node.attributes.push(attr(attrLoc, `${toRel(filename)}:${loc.start.line}:${loc.start.column + 1}`));\n }\n if (!hasAttr(node, attrComp)) {\n const comp = nearestComponentName(path);\n if (comp) node.attributes.push(attr(attrComp, comp));\n }\n }\n }\n };\n}\n"]}
|