@zigrivers/surface-adapter-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 +22 -0
- package/README.md +15 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +370 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ken Allred
|
|
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.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Surface React Adapter
|
|
2
|
+
|
|
3
|
+
React adapter for Surface audits.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @zigrivers/surface-adapter-react
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
See the repository README for usage and release guidance: https://github.com/zigrivers/surface#readme
|
|
12
|
+
|
|
13
|
+
## License
|
|
14
|
+
|
|
15
|
+
MIT.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FrameworkAdapter, SourceFileRef, ComponentMap } from '@zigrivers/surface-core/interfaces';
|
|
2
|
+
import { Result, SurfaceError } from '@zigrivers/surface-core';
|
|
3
|
+
|
|
4
|
+
declare const REACT_ADAPTER_ID = "react";
|
|
5
|
+
interface ReactFrameworkAdapter extends FrameworkAdapter {
|
|
6
|
+
readonly id: typeof REACT_ADAPTER_ID;
|
|
7
|
+
introspect(source: SourceFileRef): Promise<Result<ComponentMap, SurfaceError>>;
|
|
8
|
+
}
|
|
9
|
+
declare function createReactAdapter(): ReactFrameworkAdapter;
|
|
10
|
+
|
|
11
|
+
export { REACT_ADAPTER_ID, type ReactFrameworkAdapter, createReactAdapter };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { basename } from "path";
|
|
3
|
+
import { parse as parseBabel } from "@babel/parser";
|
|
4
|
+
import { createSurfaceError } from "@zigrivers/surface-core";
|
|
5
|
+
var REACT_ADAPTER_ID = "react";
|
|
6
|
+
var NULL_REPLACEMENT_CHARACTER = "\uFFFD";
|
|
7
|
+
var SUPPORTED_EXTENSIONS = [".jsx", ".tsx", ".js", ".ts"];
|
|
8
|
+
var COMPONENT_ATTRIBUTES = ["data-component", "data-surface-component"];
|
|
9
|
+
var SELECTOR_ATTRIBUTES = ["data-testid", "role", "aria-label"];
|
|
10
|
+
var WRAPPER_CALLS = ["memo", "forwardRef", "React.memo", "React.forwardRef"];
|
|
11
|
+
function createReactAdapter() {
|
|
12
|
+
return {
|
|
13
|
+
id: REACT_ADAPTER_ID,
|
|
14
|
+
supports: (file) => SUPPORTED_EXTENSIONS.some((extension) => file.toLowerCase().endsWith(extension)),
|
|
15
|
+
introspect(source) {
|
|
16
|
+
if (!isSourceFileRef(source)) {
|
|
17
|
+
return Promise.resolve(
|
|
18
|
+
err(
|
|
19
|
+
createSurfaceError("step_failed", "SourceFileRef requires string path and contents.")
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
return Promise.resolve(ok({ entries: introspectReactSource(source) }));
|
|
25
|
+
} catch (cause) {
|
|
26
|
+
return Promise.resolve(
|
|
27
|
+
err(
|
|
28
|
+
createSurfaceError("step_failed", "Failed to introspect React source.", {
|
|
29
|
+
cause
|
|
30
|
+
})
|
|
31
|
+
)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function ok(value) {
|
|
38
|
+
return { ok: true, value };
|
|
39
|
+
}
|
|
40
|
+
function err(error) {
|
|
41
|
+
return { ok: false, error };
|
|
42
|
+
}
|
|
43
|
+
function isSourceFileRef(source) {
|
|
44
|
+
if (typeof source !== "object" || source === null) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const candidate = source;
|
|
48
|
+
return typeof candidate.path === "string" && typeof candidate.contents === "string";
|
|
49
|
+
}
|
|
50
|
+
function introspectReactSource(source) {
|
|
51
|
+
const ast = parseBabel(source.contents, {
|
|
52
|
+
errorRecovery: false,
|
|
53
|
+
plugins: parserPluginsForPath(source.path),
|
|
54
|
+
sourceType: "unambiguous"
|
|
55
|
+
});
|
|
56
|
+
const entries = /* @__PURE__ */ new Map();
|
|
57
|
+
walkAst(ast, (node) => {
|
|
58
|
+
const declaration = componentDeclarationFor(node, source.path);
|
|
59
|
+
if (declaration !== void 0) {
|
|
60
|
+
mergeEntry(entries, source.path, declaration.component, declaration.selectors);
|
|
61
|
+
for (const reference of declaration.references) {
|
|
62
|
+
mergeEntry(entries, source.path, reference.component, [reference.selector]);
|
|
63
|
+
}
|
|
64
|
+
return "skip";
|
|
65
|
+
}
|
|
66
|
+
for (const reference of markerReferencesFor(node)) {
|
|
67
|
+
mergeEntry(entries, source.path, reference.component, [reference.selector]);
|
|
68
|
+
}
|
|
69
|
+
return void 0;
|
|
70
|
+
});
|
|
71
|
+
return [...entries.values()].map((entry) => ({
|
|
72
|
+
...entry,
|
|
73
|
+
selectors: [...new Set(entry.selectors)].sort(compareStableStrings)
|
|
74
|
+
})).sort((left, right) => compareStableStrings(left.component, right.component));
|
|
75
|
+
}
|
|
76
|
+
function parserPluginsForPath(path) {
|
|
77
|
+
const isTypeScript = /\.[cm]?tsx?$/u.test(path);
|
|
78
|
+
const supportsJsx = /\.[cm]?[jt]sx$/u.test(path) || /\.[cm]?js$/u.test(path);
|
|
79
|
+
return [
|
|
80
|
+
...isTypeScript ? ["typescript"] : ["flow"],
|
|
81
|
+
...supportsJsx ? ["jsx"] : [],
|
|
82
|
+
"classProperties",
|
|
83
|
+
"classPrivateProperties",
|
|
84
|
+
"classPrivateMethods"
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
function mergeEntry(entries, file, component, selectors) {
|
|
88
|
+
const key = `${file}\0${component}`;
|
|
89
|
+
const existing = entries.get(key);
|
|
90
|
+
entries.set(
|
|
91
|
+
key,
|
|
92
|
+
existing === void 0 ? { component, file, selectors: [...selectors] } : { ...existing, selectors: [...existing.selectors, ...selectors] }
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
function componentDeclarationFor(node, path) {
|
|
96
|
+
if (node.type === "FunctionDeclaration") {
|
|
97
|
+
const name = identifierName(node.id);
|
|
98
|
+
const details = collectComponentDetails(node);
|
|
99
|
+
return name !== void 0 && isComponentName(name) && details.hasJsx ? { component: name, ...details } : void 0;
|
|
100
|
+
}
|
|
101
|
+
if (node.type === "VariableDeclarator") {
|
|
102
|
+
const name = identifierName(node.id);
|
|
103
|
+
const initializer = unwrapReactWrapper(node.init);
|
|
104
|
+
const details = initializer === void 0 ? void 0 : collectComponentDetails(initializer);
|
|
105
|
+
return name !== void 0 && isComponentName(name) && isFunctionLike(initializer) && details?.hasJsx === true ? { component: name, ...details } : void 0;
|
|
106
|
+
}
|
|
107
|
+
if (node.type === "ClassDeclaration") {
|
|
108
|
+
const name = identifierName(node.id);
|
|
109
|
+
const details = collectComponentDetails(node);
|
|
110
|
+
return name !== void 0 && isComponentName(name) && extendsReactBase(node) && classHasJsxRender(node) && details.hasJsx ? { component: name, ...details } : void 0;
|
|
111
|
+
}
|
|
112
|
+
if (node.type === "ExportDefaultDeclaration") {
|
|
113
|
+
const declaration = asAstNode(node.declaration);
|
|
114
|
+
if (declaration?.type === "FunctionDeclaration") {
|
|
115
|
+
const details2 = collectComponentDetails(declaration);
|
|
116
|
+
return details2.hasJsx ? { component: identifierName(declaration.id) ?? componentNameFromPath(path), ...details2 } : void 0;
|
|
117
|
+
}
|
|
118
|
+
if (declaration?.type === "ClassDeclaration") {
|
|
119
|
+
const details2 = collectComponentDetails(declaration);
|
|
120
|
+
return extendsReactBase(declaration) && classHasJsxRender(declaration) && details2.hasJsx ? { component: identifierName(declaration.id) ?? componentNameFromPath(path), ...details2 } : void 0;
|
|
121
|
+
}
|
|
122
|
+
const unwrapped = unwrapReactWrapper(declaration);
|
|
123
|
+
const details = unwrapped === void 0 ? void 0 : collectComponentDetails(unwrapped);
|
|
124
|
+
const name = unwrapped === void 0 ? void 0 : componentNameFromExpression(unwrapped, path);
|
|
125
|
+
return isFunctionLike(unwrapped) && name !== void 0 && details?.hasJsx === true ? { component: name, ...details } : void 0;
|
|
126
|
+
}
|
|
127
|
+
return void 0;
|
|
128
|
+
}
|
|
129
|
+
function collectComponentDetails(node) {
|
|
130
|
+
let hasJsx = false;
|
|
131
|
+
const references = [];
|
|
132
|
+
const selectors = [];
|
|
133
|
+
walkAst(node, (child) => {
|
|
134
|
+
if (child.type === "JSXElement" || child.type === "JSXFragment") {
|
|
135
|
+
hasJsx = true;
|
|
136
|
+
}
|
|
137
|
+
if (child !== node && isNestedComponentBoundary(child)) {
|
|
138
|
+
return "skip";
|
|
139
|
+
}
|
|
140
|
+
if (child.type === "JSXOpeningElement") {
|
|
141
|
+
references.push(...markerReferencesFor(child));
|
|
142
|
+
selectors.push(...selectorsForJsxOpeningElement(child));
|
|
143
|
+
}
|
|
144
|
+
return void 0;
|
|
145
|
+
});
|
|
146
|
+
return { hasJsx, references, selectors };
|
|
147
|
+
}
|
|
148
|
+
function isNestedComponentBoundary(node) {
|
|
149
|
+
if (node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return node.type === "VariableDeclarator" && isFunctionLike(unwrapReactWrapper(node.init));
|
|
153
|
+
}
|
|
154
|
+
function compareStableStrings(left, right) {
|
|
155
|
+
if (left < right) {
|
|
156
|
+
return -1;
|
|
157
|
+
}
|
|
158
|
+
return left > right ? 1 : 0;
|
|
159
|
+
}
|
|
160
|
+
function markerReferencesFor(node) {
|
|
161
|
+
if (node.type !== "JSXOpeningElement") {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
return COMPONENT_ATTRIBUTES.flatMap((attribute) => {
|
|
165
|
+
const rawValue = jsxStringAttribute(node, attribute);
|
|
166
|
+
const component = sanitizeComponentName(rawValue);
|
|
167
|
+
return component === void 0 || !isComponentName(component) ? [] : [{ component, selector: `[${attribute}="${escapeCssString(rawValue ?? "")}"]` }];
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
function selectorsForJsxOpeningElement(node) {
|
|
171
|
+
const elementName = jsxElementName(node.name);
|
|
172
|
+
if (elementName === void 0) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const selectors = [];
|
|
176
|
+
for (const attribute of COMPONENT_ATTRIBUTES) {
|
|
177
|
+
const value = jsxStringAttribute(node, attribute);
|
|
178
|
+
if (value !== void 0 && value.trim().length > 0) {
|
|
179
|
+
selectors.push(`[${attribute}="${escapeCssString(value)}"]`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const id = jsxStringAttribute(node, "id");
|
|
183
|
+
if (id !== void 0 && id.trim().length > 0) {
|
|
184
|
+
selectors.push(`[id="${escapeCssString(id)}"]`);
|
|
185
|
+
}
|
|
186
|
+
for (const attribute of SELECTOR_ATTRIBUTES) {
|
|
187
|
+
const value = jsxStringAttribute(node, attribute);
|
|
188
|
+
if (value !== void 0 && value.trim().length > 0) {
|
|
189
|
+
selectors.push(`[${attribute}="${escapeCssString(value)}"]`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!startsWithLowercase(elementName)) {
|
|
193
|
+
selectors.push(`react:${elementName}`);
|
|
194
|
+
}
|
|
195
|
+
if (selectors.length > 0) {
|
|
196
|
+
return selectors;
|
|
197
|
+
}
|
|
198
|
+
return [elementName];
|
|
199
|
+
}
|
|
200
|
+
function classHasJsxRender(node) {
|
|
201
|
+
const body = asAstNode(node.body);
|
|
202
|
+
const members = asAstNodeArray(body?.body);
|
|
203
|
+
return members.some((member) => {
|
|
204
|
+
const keyName = propertyName(member.key);
|
|
205
|
+
return keyName === "render" && collectComponentDetails(member).hasJsx;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function extendsReactBase(node) {
|
|
209
|
+
const superClass = calleeName(node.superClass);
|
|
210
|
+
return superClass === "Component" || superClass === "PureComponent" || superClass === "React.Component" || superClass === "React.PureComponent";
|
|
211
|
+
}
|
|
212
|
+
function unwrapReactWrapper(node) {
|
|
213
|
+
let current = asAstNode(node);
|
|
214
|
+
while (current?.type === "CallExpression" && isReactWrapperCall(current)) {
|
|
215
|
+
current = asAstNode(asAstNodeArray(current.arguments)[0]);
|
|
216
|
+
}
|
|
217
|
+
return current;
|
|
218
|
+
}
|
|
219
|
+
function isReactWrapperCall(node) {
|
|
220
|
+
const callee = calleeName(node.callee);
|
|
221
|
+
return callee !== void 0 && WRAPPER_CALLS.includes(callee);
|
|
222
|
+
}
|
|
223
|
+
function isFunctionLike(node) {
|
|
224
|
+
const candidate = asAstNode(node);
|
|
225
|
+
return candidate?.type === "ArrowFunctionExpression" || candidate?.type === "FunctionExpression" || candidate?.type === "FunctionDeclaration";
|
|
226
|
+
}
|
|
227
|
+
function jsxStringAttribute(node, attributeName) {
|
|
228
|
+
const attributes = asAstNodeArray(node.attributes);
|
|
229
|
+
for (const attribute of attributes) {
|
|
230
|
+
if (attribute.type !== "JSXAttribute" || jsxElementName(attribute.name) !== attributeName) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const value = attribute.value;
|
|
234
|
+
if (value === null) {
|
|
235
|
+
return "";
|
|
236
|
+
}
|
|
237
|
+
if (isAstNode(value) && value.type === "StringLiteral" && typeof value.value === "string") {
|
|
238
|
+
return value.value;
|
|
239
|
+
}
|
|
240
|
+
const expression = isAstNode(value) ? asAstNode(value.expression) : void 0;
|
|
241
|
+
if (expression?.type === "StringLiteral" && typeof expression.value === "string") {
|
|
242
|
+
return expression.value;
|
|
243
|
+
}
|
|
244
|
+
if (expression?.type === "TemplateLiteral") {
|
|
245
|
+
const quasis = asAstNodeArray(expression.quasis);
|
|
246
|
+
const expressions = Array.isArray(expression.expressions) ? expression.expressions : [];
|
|
247
|
+
const cooked = objectValue(quasis[0]?.value, "cooked");
|
|
248
|
+
if (quasis.length === 1 && expressions.length === 0 && typeof cooked === "string") {
|
|
249
|
+
return cooked;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return void 0;
|
|
254
|
+
}
|
|
255
|
+
function walkAst(root, visit) {
|
|
256
|
+
const pending = [root];
|
|
257
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
258
|
+
while (pending.length > 0) {
|
|
259
|
+
const current = pending.pop();
|
|
260
|
+
if (typeof current !== "object" || current === null || seen.has(current)) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
seen.add(current);
|
|
264
|
+
if (isAstNode(current) && visit(current) === "skip") {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
for (const value of Object.values(current)) {
|
|
268
|
+
if (Array.isArray(value)) {
|
|
269
|
+
for (let index = value.length - 1; index >= 0; index -= 1) {
|
|
270
|
+
pending.push(value[index]);
|
|
271
|
+
}
|
|
272
|
+
} else if (typeof value === "object" && value !== null) {
|
|
273
|
+
pending.push(value);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function isAstNode(value) {
|
|
279
|
+
return typeof value === "object" && value !== null && typeof value.type === "string";
|
|
280
|
+
}
|
|
281
|
+
function asAstNode(value) {
|
|
282
|
+
return isAstNode(value) ? value : void 0;
|
|
283
|
+
}
|
|
284
|
+
function asAstNodeArray(value) {
|
|
285
|
+
return Array.isArray(value) ? value.filter(isAstNode) : [];
|
|
286
|
+
}
|
|
287
|
+
function objectValue(value, key) {
|
|
288
|
+
return typeof value === "object" && value !== null ? value[key] : void 0;
|
|
289
|
+
}
|
|
290
|
+
function identifierName(node) {
|
|
291
|
+
const candidate = asAstNode(node);
|
|
292
|
+
return candidate?.type === "Identifier" && typeof candidate.name === "string" ? candidate.name : void 0;
|
|
293
|
+
}
|
|
294
|
+
function jsxElementName(node) {
|
|
295
|
+
const candidate = asAstNode(node);
|
|
296
|
+
if (candidate?.type === "JSXIdentifier" && typeof candidate.name === "string") {
|
|
297
|
+
return candidate.name;
|
|
298
|
+
}
|
|
299
|
+
if (candidate?.type === "JSXMemberExpression") {
|
|
300
|
+
const object = jsxElementName(candidate.object);
|
|
301
|
+
const property = jsxElementName(candidate.property);
|
|
302
|
+
return object !== void 0 && property !== void 0 ? `${object}.${property}` : void 0;
|
|
303
|
+
}
|
|
304
|
+
return void 0;
|
|
305
|
+
}
|
|
306
|
+
function calleeName(node) {
|
|
307
|
+
const candidate = asAstNode(node);
|
|
308
|
+
if (candidate?.type === "Identifier" && typeof candidate.name === "string") {
|
|
309
|
+
return candidate.name;
|
|
310
|
+
}
|
|
311
|
+
if (candidate?.type === "MemberExpression") {
|
|
312
|
+
const object = calleeName(candidate.object);
|
|
313
|
+
const property = candidate.computed === true ? stringLiteralValue(candidate.property) : propertyName(candidate.property);
|
|
314
|
+
return object !== void 0 && property !== void 0 ? `${object}.${property}` : void 0;
|
|
315
|
+
}
|
|
316
|
+
return void 0;
|
|
317
|
+
}
|
|
318
|
+
function propertyName(node) {
|
|
319
|
+
const candidate = asAstNode(node);
|
|
320
|
+
if ((candidate?.type === "Identifier" || candidate?.type === "JSXIdentifier") && typeof candidate.name === "string") {
|
|
321
|
+
return candidate.name;
|
|
322
|
+
}
|
|
323
|
+
return stringLiteralValue(candidate);
|
|
324
|
+
}
|
|
325
|
+
function stringLiteralValue(node) {
|
|
326
|
+
const candidate = asAstNode(node);
|
|
327
|
+
return candidate?.type === "StringLiteral" && typeof candidate.value === "string" ? candidate.value : void 0;
|
|
328
|
+
}
|
|
329
|
+
function componentNameFromPath(path) {
|
|
330
|
+
const file = basename(path).replace(/\.[cm]?[jt]sx?$/u, "");
|
|
331
|
+
const normalized = file.split(/[^A-Za-z0-9]+/u).filter((part) => part.length > 0).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("");
|
|
332
|
+
if (normalized.length === 0) {
|
|
333
|
+
return "DefaultExport";
|
|
334
|
+
}
|
|
335
|
+
return isComponentName(normalized) ? normalized : `Default${normalized}`;
|
|
336
|
+
}
|
|
337
|
+
function componentNameFromExpression(node, path) {
|
|
338
|
+
const name = identifierName(node.id);
|
|
339
|
+
return name !== void 0 && isComponentName(name) ? name : componentNameFromPath(path);
|
|
340
|
+
}
|
|
341
|
+
function isComponentName(name) {
|
|
342
|
+
return /^[A-Z][A-Za-z0-9_$]*$/u.test(name);
|
|
343
|
+
}
|
|
344
|
+
function startsWithLowercase(name) {
|
|
345
|
+
return /^[a-z]/u.test(name);
|
|
346
|
+
}
|
|
347
|
+
function sanitizeComponentName(value) {
|
|
348
|
+
const component = value === void 0 ? void 0 : normalizeNullCharacters(value).trim();
|
|
349
|
+
return component === void 0 || component.length === 0 ? void 0 : component;
|
|
350
|
+
}
|
|
351
|
+
function normalizeNullCharacters(value) {
|
|
352
|
+
return value.replaceAll("\0", NULL_REPLACEMENT_CHARACTER);
|
|
353
|
+
}
|
|
354
|
+
function escapeCssString(value) {
|
|
355
|
+
return Array.from(normalizeNullCharacters(value), (character) => {
|
|
356
|
+
const codePoint = character.codePointAt(0);
|
|
357
|
+
if (codePoint >= 1 && codePoint <= 31 || codePoint === 127) {
|
|
358
|
+
return `\\${codePoint.toString(16)} `;
|
|
359
|
+
}
|
|
360
|
+
if (character === '"' || character === "\\") {
|
|
361
|
+
return `\\${character}`;
|
|
362
|
+
}
|
|
363
|
+
return character;
|
|
364
|
+
}).join("");
|
|
365
|
+
}
|
|
366
|
+
export {
|
|
367
|
+
REACT_ADAPTER_ID,
|
|
368
|
+
createReactAdapter
|
|
369
|
+
};
|
|
370
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { basename } from \"node:path\";\n\nimport { parse as parseBabel, type ParserPlugin } from \"@babel/parser\";\nimport type {\n ComponentMap,\n ComponentMapEntry,\n FrameworkAdapter,\n SourceFileRef,\n} from \"@zigrivers/surface-core/interfaces\";\nimport { createSurfaceError, type Result, type SurfaceError } from \"@zigrivers/surface-core\";\n\nexport const REACT_ADAPTER_ID = \"react\";\n\nconst NULL_REPLACEMENT_CHARACTER = \"\\uFFFD\";\nconst SUPPORTED_EXTENSIONS = [\".jsx\", \".tsx\", \".js\", \".ts\"] as const;\nconst COMPONENT_ATTRIBUTES = [\"data-component\", \"data-surface-component\"] as const;\nconst SELECTOR_ATTRIBUTES = [\"data-testid\", \"role\", \"aria-label\"] as const;\nconst WRAPPER_CALLS = [\"memo\", \"forwardRef\", \"React.memo\", \"React.forwardRef\"] as const;\n\ntype AstNode = { readonly type: string; readonly [key: string]: unknown };\ntype ComponentReference = { readonly component: string; readonly selector: string };\ntype ComponentDetails = {\n readonly hasJsx: boolean;\n readonly references: readonly ComponentReference[];\n readonly selectors: readonly string[];\n};\n\nexport interface ReactFrameworkAdapter extends FrameworkAdapter {\n readonly id: typeof REACT_ADAPTER_ID;\n introspect(source: SourceFileRef): Promise<Result<ComponentMap, SurfaceError>>;\n}\n\nexport function createReactAdapter(): ReactFrameworkAdapter {\n return {\n id: REACT_ADAPTER_ID,\n supports: (file: string) =>\n SUPPORTED_EXTENSIONS.some((extension) => file.toLowerCase().endsWith(extension)),\n introspect(source: SourceFileRef) {\n if (!isSourceFileRef(source)) {\n return Promise.resolve(\n err(\n createSurfaceError(\"step_failed\", \"SourceFileRef requires string path and contents.\"),\n ),\n );\n }\n\n try {\n return Promise.resolve(ok({ entries: introspectReactSource(source) }));\n } catch (cause) {\n return Promise.resolve(\n err(\n createSurfaceError(\"step_failed\", \"Failed to introspect React source.\", {\n cause,\n }),\n ),\n );\n }\n },\n };\n}\n\nfunction ok<T>(value: T): Result<T, SurfaceError> {\n return { ok: true, value };\n}\n\nfunction err(error: SurfaceError): Result<never, SurfaceError> {\n return { ok: false, error };\n}\n\nfunction isSourceFileRef(source: unknown): source is SourceFileRef {\n if (typeof source !== \"object\" || source === null) {\n return false;\n }\n\n const candidate = source as { readonly path?: unknown; readonly contents?: unknown };\n\n return typeof candidate.path === \"string\" && typeof candidate.contents === \"string\";\n}\n\nfunction introspectReactSource(source: SourceFileRef): ComponentMapEntry[] {\n const ast = parseBabel(source.contents, {\n errorRecovery: false,\n plugins: parserPluginsForPath(source.path),\n sourceType: \"unambiguous\",\n });\n const entries = new Map<string, ComponentMapEntry>();\n\n walkAst(ast, (node) => {\n const declaration = componentDeclarationFor(node, source.path);\n\n if (declaration !== undefined) {\n mergeEntry(entries, source.path, declaration.component, declaration.selectors);\n for (const reference of declaration.references) {\n mergeEntry(entries, source.path, reference.component, [reference.selector]);\n }\n return \"skip\";\n }\n\n for (const reference of markerReferencesFor(node)) {\n mergeEntry(entries, source.path, reference.component, [reference.selector]);\n }\n\n return undefined;\n });\n\n return [...entries.values()]\n .map((entry) => ({\n ...entry,\n selectors: [...new Set(entry.selectors)].sort(compareStableStrings),\n }))\n .sort((left, right) => compareStableStrings(left.component, right.component));\n}\n\nfunction parserPluginsForPath(path: string): ParserPlugin[] {\n const isTypeScript = /\\.[cm]?tsx?$/u.test(path);\n const supportsJsx = /\\.[cm]?[jt]sx$/u.test(path) || /\\.[cm]?js$/u.test(path);\n\n return [\n ...(isTypeScript ? ([\"typescript\"] as const) : ([\"flow\"] as const)),\n ...(supportsJsx ? ([\"jsx\"] as const) : []),\n \"classProperties\",\n \"classPrivateProperties\",\n \"classPrivateMethods\",\n ];\n}\n\nfunction mergeEntry(\n entries: Map<string, ComponentMapEntry>,\n file: string,\n component: string,\n selectors: readonly string[],\n): void {\n const key = `${file}\\0${component}`;\n const existing = entries.get(key);\n\n entries.set(\n key,\n existing === undefined\n ? { component, file, selectors: [...selectors] }\n : { ...existing, selectors: [...existing.selectors, ...selectors] },\n );\n}\n\nfunction componentDeclarationFor(\n node: AstNode,\n path: string,\n): ({ readonly component: string } & ComponentDetails) | undefined {\n if (node.type === \"FunctionDeclaration\") {\n const name = identifierName(node.id);\n const details = collectComponentDetails(node);\n\n return name !== undefined && isComponentName(name) && details.hasJsx\n ? { component: name, ...details }\n : undefined;\n }\n\n if (node.type === \"VariableDeclarator\") {\n const name = identifierName(node.id);\n const initializer = unwrapReactWrapper(node.init);\n const details = initializer === undefined ? undefined : collectComponentDetails(initializer);\n\n return name !== undefined &&\n isComponentName(name) &&\n isFunctionLike(initializer) &&\n details?.hasJsx === true\n ? { component: name, ...details }\n : undefined;\n }\n\n if (node.type === \"ClassDeclaration\") {\n const name = identifierName(node.id);\n const details = collectComponentDetails(node);\n\n return name !== undefined &&\n isComponentName(name) &&\n extendsReactBase(node) &&\n classHasJsxRender(node) &&\n details.hasJsx\n ? { component: name, ...details }\n : undefined;\n }\n\n if (node.type === \"ExportDefaultDeclaration\") {\n const declaration = asAstNode(node.declaration);\n\n if (declaration?.type === \"FunctionDeclaration\") {\n const details = collectComponentDetails(declaration);\n\n return details.hasJsx\n ? { component: identifierName(declaration.id) ?? componentNameFromPath(path), ...details }\n : undefined;\n }\n\n if (declaration?.type === \"ClassDeclaration\") {\n const details = collectComponentDetails(declaration);\n\n return extendsReactBase(declaration) && classHasJsxRender(declaration) && details.hasJsx\n ? { component: identifierName(declaration.id) ?? componentNameFromPath(path), ...details }\n : undefined;\n }\n\n const unwrapped = unwrapReactWrapper(declaration);\n const details = unwrapped === undefined ? undefined : collectComponentDetails(unwrapped);\n\n const name = unwrapped === undefined ? undefined : componentNameFromExpression(unwrapped, path);\n\n return isFunctionLike(unwrapped) && name !== undefined && details?.hasJsx === true\n ? { component: name, ...details }\n : undefined;\n }\n\n return undefined;\n}\n\nfunction collectComponentDetails(node: AstNode): ComponentDetails {\n let hasJsx = false;\n const references: ComponentReference[] = [];\n const selectors: string[] = [];\n\n walkAst(node, (child) => {\n if (child.type === \"JSXElement\" || child.type === \"JSXFragment\") {\n hasJsx = true;\n }\n\n if (child !== node && isNestedComponentBoundary(child)) {\n return \"skip\";\n }\n\n if (child.type === \"JSXOpeningElement\") {\n references.push(...markerReferencesFor(child));\n\n selectors.push(...selectorsForJsxOpeningElement(child));\n }\n\n return undefined;\n });\n\n return { hasJsx, references, selectors };\n}\n\nfunction isNestedComponentBoundary(node: AstNode): boolean {\n if (node.type === \"FunctionDeclaration\" || node.type === \"ClassDeclaration\") {\n return true;\n }\n\n return node.type === \"VariableDeclarator\" && isFunctionLike(unwrapReactWrapper(node.init));\n}\n\nfunction compareStableStrings(left: string, right: string): number {\n if (left < right) {\n return -1;\n }\n\n return left > right ? 1 : 0;\n}\n\nfunction markerReferencesFor(node: AstNode): ComponentReference[] {\n if (node.type !== \"JSXOpeningElement\") {\n return [];\n }\n\n return COMPONENT_ATTRIBUTES.flatMap((attribute) => {\n const rawValue = jsxStringAttribute(node, attribute);\n const component = sanitizeComponentName(rawValue);\n\n return component === undefined || !isComponentName(component)\n ? []\n : [{ component, selector: `[${attribute}=\"${escapeCssString(rawValue ?? \"\")}\"]` }];\n });\n}\n\nfunction selectorsForJsxOpeningElement(node: AstNode): string[] {\n const elementName = jsxElementName(node.name);\n\n if (elementName === undefined) {\n return [];\n }\n\n const selectors: string[] = [];\n\n for (const attribute of COMPONENT_ATTRIBUTES) {\n const value = jsxStringAttribute(node, attribute);\n\n if (value !== undefined && value.trim().length > 0) {\n selectors.push(`[${attribute}=\"${escapeCssString(value)}\"]`);\n }\n }\n\n const id = jsxStringAttribute(node, \"id\");\n\n if (id !== undefined && id.trim().length > 0) {\n selectors.push(`[id=\"${escapeCssString(id)}\"]`);\n }\n\n for (const attribute of SELECTOR_ATTRIBUTES) {\n const value = jsxStringAttribute(node, attribute);\n\n if (value !== undefined && value.trim().length > 0) {\n selectors.push(`[${attribute}=\"${escapeCssString(value)}\"]`);\n }\n }\n\n if (!startsWithLowercase(elementName)) {\n selectors.push(`react:${elementName}`);\n }\n\n if (selectors.length > 0) {\n return selectors;\n }\n\n return [elementName];\n}\n\nfunction classHasJsxRender(node: AstNode): boolean {\n const body = asAstNode(node.body);\n const members = asAstNodeArray(body?.body);\n\n return members.some((member) => {\n const keyName = propertyName(member.key);\n\n return keyName === \"render\" && collectComponentDetails(member).hasJsx;\n });\n}\n\nfunction extendsReactBase(node: AstNode): boolean {\n const superClass = calleeName(node.superClass);\n\n return (\n superClass === \"Component\" ||\n superClass === \"PureComponent\" ||\n superClass === \"React.Component\" ||\n superClass === \"React.PureComponent\"\n );\n}\n\nfunction unwrapReactWrapper(node: unknown): AstNode | undefined {\n let current = asAstNode(node);\n\n while (current?.type === \"CallExpression\" && isReactWrapperCall(current)) {\n current = asAstNode(asAstNodeArray(current.arguments)[0]);\n }\n\n return current;\n}\n\nfunction isReactWrapperCall(node: AstNode): boolean {\n const callee = calleeName(node.callee);\n\n return callee !== undefined && WRAPPER_CALLS.includes(callee as (typeof WRAPPER_CALLS)[number]);\n}\n\nfunction isFunctionLike(node: unknown): node is AstNode {\n const candidate = asAstNode(node);\n\n return (\n candidate?.type === \"ArrowFunctionExpression\" ||\n candidate?.type === \"FunctionExpression\" ||\n candidate?.type === \"FunctionDeclaration\"\n );\n}\n\nfunction jsxStringAttribute(node: AstNode, attributeName: string): string | undefined {\n const attributes = asAstNodeArray(node.attributes);\n\n for (const attribute of attributes) {\n if (attribute.type !== \"JSXAttribute\" || jsxElementName(attribute.name) !== attributeName) {\n continue;\n }\n\n const value = attribute.value;\n\n if (value === null) {\n return \"\";\n }\n\n if (isAstNode(value) && value.type === \"StringLiteral\" && typeof value.value === \"string\") {\n return value.value;\n }\n\n const expression = isAstNode(value) ? asAstNode(value.expression) : undefined;\n\n if (expression?.type === \"StringLiteral\" && typeof expression.value === \"string\") {\n return expression.value;\n }\n\n if (expression?.type === \"TemplateLiteral\") {\n const quasis = asAstNodeArray(expression.quasis);\n const expressions = Array.isArray(expression.expressions) ? expression.expressions : [];\n const cooked = objectValue(quasis[0]?.value, \"cooked\");\n\n if (quasis.length === 1 && expressions.length === 0 && typeof cooked === \"string\") {\n return cooked;\n }\n }\n }\n\n return undefined;\n}\n\nfunction walkAst(root: unknown, visit: (node: AstNode) => \"skip\" | undefined | void): void {\n const pending: unknown[] = [root];\n const seen = new WeakSet<object>();\n\n while (pending.length > 0) {\n const current = pending.pop();\n\n if (typeof current !== \"object\" || current === null || seen.has(current)) {\n continue;\n }\n\n seen.add(current);\n\n if (isAstNode(current) && visit(current) === \"skip\") {\n continue;\n }\n\n for (const value of Object.values(current)) {\n if (Array.isArray(value)) {\n for (let index = value.length - 1; index >= 0; index -= 1) {\n pending.push(value[index]);\n }\n } else if (typeof value === \"object\" && value !== null) {\n pending.push(value);\n }\n }\n }\n}\n\nfunction isAstNode(value: unknown): value is AstNode {\n return typeof value === \"object\" && value !== null && typeof (value as AstNode).type === \"string\";\n}\n\nfunction asAstNode(value: unknown): AstNode | undefined {\n return isAstNode(value) ? value : undefined;\n}\n\nfunction asAstNodeArray(value: unknown): AstNode[] {\n return Array.isArray(value) ? value.filter(isAstNode) : [];\n}\n\nfunction objectValue(value: unknown, key: string): unknown {\n return typeof value === \"object\" && value !== null\n ? (value as Record<string, unknown>)[key]\n : undefined;\n}\n\nfunction identifierName(node: unknown): string | undefined {\n const candidate = asAstNode(node);\n\n return candidate?.type === \"Identifier\" && typeof candidate.name === \"string\"\n ? candidate.name\n : undefined;\n}\n\nfunction jsxElementName(node: unknown): string | undefined {\n const candidate = asAstNode(node);\n\n if (candidate?.type === \"JSXIdentifier\" && typeof candidate.name === \"string\") {\n return candidate.name;\n }\n\n if (candidate?.type === \"JSXMemberExpression\") {\n const object = jsxElementName(candidate.object);\n const property = jsxElementName(candidate.property);\n\n return object !== undefined && property !== undefined ? `${object}.${property}` : undefined;\n }\n\n return undefined;\n}\n\nfunction calleeName(node: unknown): string | undefined {\n const candidate = asAstNode(node);\n\n if (candidate?.type === \"Identifier\" && typeof candidate.name === \"string\") {\n return candidate.name;\n }\n\n if (candidate?.type === \"MemberExpression\") {\n const object = calleeName(candidate.object);\n const property =\n candidate.computed === true\n ? stringLiteralValue(candidate.property)\n : propertyName(candidate.property);\n\n return object !== undefined && property !== undefined ? `${object}.${property}` : undefined;\n }\n\n return undefined;\n}\n\nfunction propertyName(node: unknown): string | undefined {\n const candidate = asAstNode(node);\n\n if (\n (candidate?.type === \"Identifier\" || candidate?.type === \"JSXIdentifier\") &&\n typeof candidate.name === \"string\"\n ) {\n return candidate.name;\n }\n\n return stringLiteralValue(candidate);\n}\n\nfunction stringLiteralValue(node: unknown): string | undefined {\n const candidate = asAstNode(node);\n\n return candidate?.type === \"StringLiteral\" && typeof candidate.value === \"string\"\n ? candidate.value\n : undefined;\n}\n\nfunction componentNameFromPath(path: string): string {\n const file = basename(path).replace(/\\.[cm]?[jt]sx?$/u, \"\");\n const normalized = file\n .split(/[^A-Za-z0-9]+/u)\n .filter((part) => part.length > 0)\n .map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)\n .join(\"\");\n\n if (normalized.length === 0) {\n return \"DefaultExport\";\n }\n\n return isComponentName(normalized) ? normalized : `Default${normalized}`;\n}\n\nfunction componentNameFromExpression(node: AstNode, path: string): string {\n const name = identifierName(node.id);\n\n return name !== undefined && isComponentName(name) ? name : componentNameFromPath(path);\n}\n\nfunction isComponentName(name: string): boolean {\n return /^[A-Z][A-Za-z0-9_$]*$/u.test(name);\n}\n\nfunction startsWithLowercase(name: string): boolean {\n return /^[a-z]/u.test(name);\n}\n\nfunction sanitizeComponentName(value: string | undefined): string | undefined {\n const component = value === undefined ? undefined : normalizeNullCharacters(value).trim();\n\n return component === undefined || component.length === 0 ? undefined : component;\n}\n\nfunction normalizeNullCharacters(value: string): string {\n return value.replaceAll(\"\\0\", NULL_REPLACEMENT_CHARACTER);\n}\n\nfunction escapeCssString(value: string): string {\n return Array.from(normalizeNullCharacters(value), (character) => {\n const codePoint = character.codePointAt(0)!;\n\n if ((codePoint >= 1 && codePoint <= 0x1f) || codePoint === 0x7f) {\n return `\\\\${codePoint.toString(16)} `;\n }\n\n if (character === '\"' || character === \"\\\\\") {\n return `\\\\${character}`;\n }\n\n return character;\n }).join(\"\");\n}\n"],"mappings":"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","names":["details"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zigrivers/surface-adapter-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@babel/parser": "^7.29.7",
|
|
20
|
+
"@zigrivers/surface-core": "0.1.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.13.0",
|
|
24
|
+
"eslint": "^9.39.4",
|
|
25
|
+
"tsup": "^8.5.1",
|
|
26
|
+
"typescript": "^5.9.3",
|
|
27
|
+
"vitest": "^4.0.14"
|
|
28
|
+
},
|
|
29
|
+
"description": "React adapter for Surface audits",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"homepage": "https://github.com/zigrivers/surface#readme",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/zigrivers/surface.git",
|
|
35
|
+
"directory": "packages/adapters/react"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/zigrivers/surface/issues"
|
|
39
|
+
},
|
|
40
|
+
"keywords": [
|
|
41
|
+
"surface",
|
|
42
|
+
"adapter",
|
|
43
|
+
"react"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public",
|
|
47
|
+
"provenance": true
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"build": "tsup",
|
|
51
|
+
"build:smoke": "node scripts/build-smoke.mjs",
|
|
52
|
+
"clean": "node scripts/clean.mjs",
|
|
53
|
+
"lint": "eslint src",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest",
|
|
56
|
+
"typecheck": "tsc --noEmit"
|
|
57
|
+
}
|
|
58
|
+
}
|