@auspex-ai/next 0.0.0-stage → 1.4.2
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 +202 -0
- package/README.md +57 -2
- package/dist/config.cjs +700 -0
- package/dist/config.d.cts +54 -0
- package/dist/config.d.mts +54 -0
- package/dist/config.mjs +691 -0
- package/dist/icon-name-loader.cjs +1156 -0
- package/dist/index.cjs +169 -0
- package/dist/index.d.cts +41 -0
- package/dist/index.d.mts +41 -0
- package/dist/index.mjs +146 -0
- package/dist/register-names-cli.mjs +390 -0
- package/dist/rolldown-runtime-VH7oDXx4.cjs +28 -0
- package/dist/svg-name-loader.cjs +18 -0
- package/dist/transform-Dc5diC_P.cjs +441 -0
- package/package.json +85 -3
|
@@ -0,0 +1,1156 @@
|
|
|
1
|
+
const require_transform = require("./transform-Dc5diC_P.cjs");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
node_path = require_transform.__toESM(node_path, 1);
|
|
4
|
+
let magic_string = require("magic-string");
|
|
5
|
+
//#region ../build-names/src/element-name/label.ts
|
|
6
|
+
/** 式の置き換え */
|
|
7
|
+
const HOLE = "[*]";
|
|
8
|
+
/** 子の中のテキストを文書順に集める(入れ子の要素の子を含む)。式は HOLE にする */
|
|
9
|
+
function collect(children, out) {
|
|
10
|
+
for (const c of children) switch (c.type) {
|
|
11
|
+
case "JSXText":
|
|
12
|
+
out.push(c.value);
|
|
13
|
+
break;
|
|
14
|
+
case "JSXElement":
|
|
15
|
+
case "JSXFragment":
|
|
16
|
+
collect(c.children ?? [], out);
|
|
17
|
+
break;
|
|
18
|
+
case "JSXExpressionContainer":
|
|
19
|
+
out.push(fromExpression(c.expression));
|
|
20
|
+
break;
|
|
21
|
+
case "JSXSpreadChild": out.push(HOLE);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** 式 1 つ分のテキスト。文字列リテラルと式の無いテンプレートリテラルだけが静的な文言 */
|
|
25
|
+
function fromExpression(e) {
|
|
26
|
+
if (!e || e.type === "JSXEmptyExpression") return "";
|
|
27
|
+
if (e.type === "StringLiteral") return e.value;
|
|
28
|
+
if (e.type === "TemplateLiteral") {
|
|
29
|
+
let s = "";
|
|
30
|
+
for (const [i, q] of e.quasis.entries()) {
|
|
31
|
+
s += q.value.cooked ?? q.value.raw;
|
|
32
|
+
if (i < e.expressions.length) s += HOLE;
|
|
33
|
+
}
|
|
34
|
+
return s;
|
|
35
|
+
}
|
|
36
|
+
return HOLE;
|
|
37
|
+
}
|
|
38
|
+
function normalize(raw) {
|
|
39
|
+
return raw.replace(/\s+/g, " ").trim().replace(/\[\*\](?: ?\[\*\])+/g, HOLE).normalize("NFC");
|
|
40
|
+
}
|
|
41
|
+
/** [*]・空白・句読点を除いて何も残らないか */
|
|
42
|
+
function isEmptyText(s) {
|
|
43
|
+
return s.split(HOLE).join("").replace(/[\s\p{P}]/gu, "") === "";
|
|
44
|
+
}
|
|
45
|
+
/** 静的な文字列リテラルの属性値(式は取らない) */
|
|
46
|
+
function staticAttr(o, name) {
|
|
47
|
+
for (const a of o.attributes) {
|
|
48
|
+
if (a.type !== "JSXAttribute" || a.name.type !== "JSXIdentifier" || a.name.name !== name) continue;
|
|
49
|
+
return a.value?.type === "StringLiteral" ? a.value.value : null;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/** クリックされうる JSX 要素のラベル。取れない・使えない(実行時の値だけ・句読点だけ・長すぎる)ときは null */
|
|
54
|
+
function labelOf(el) {
|
|
55
|
+
const parts = [];
|
|
56
|
+
collect(el.children ?? [], parts);
|
|
57
|
+
let text = normalize(parts.join(""));
|
|
58
|
+
if (isEmptyText(text)) {
|
|
59
|
+
text = "";
|
|
60
|
+
for (const attr of ["aria-label", "title"]) {
|
|
61
|
+
const v = staticAttr(el.openingElement, attr);
|
|
62
|
+
if (v === null) continue;
|
|
63
|
+
const n = normalize(v);
|
|
64
|
+
if (!isEmptyText(n)) {
|
|
65
|
+
text = n;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (text === "" || [...text].length > 80) return null;
|
|
71
|
+
return text;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region ../build-names/src/element-name/extract.ts
|
|
75
|
+
const SKIP_KEYS$1 = /* @__PURE__ */ new Set([
|
|
76
|
+
"loc",
|
|
77
|
+
"start",
|
|
78
|
+
"end",
|
|
79
|
+
"extra",
|
|
80
|
+
"range",
|
|
81
|
+
"leadingComments",
|
|
82
|
+
"trailingComments",
|
|
83
|
+
"innerComments"
|
|
84
|
+
]);
|
|
85
|
+
function children(n) {
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const k of Object.keys(n)) {
|
|
88
|
+
if (SKIP_KEYS$1.has(k)) continue;
|
|
89
|
+
const v = n[k];
|
|
90
|
+
if (Array.isArray(v)) {
|
|
91
|
+
for (const c of v) if (c && typeof c.type === "string") out.push(c);
|
|
92
|
+
} else if (v && typeof v.type === "string") out.push(v);
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
const GENERIC_HANDLER = /^((on|handle)(Click|Press|Tap|Change|Select|Submit|PointerDown|MouseDown|TouchStart|KeyDown|DoubleClick|Action)?|callback|cb|fn|handler|action|formAction|onClickHandler|clickHandler)$/;
|
|
97
|
+
const GENERIC_COMPONENT = /^(App|Common|Shared|My|Main|Primary|Secondary|Submit)?(Button|IconButton|Link|NavLink|Anchor|Clickable|Pressable|Item|MenuItem|ListItem|Root|Trigger|Wrapper|Card|Tab|Option|Chip|App|Component|Default|Page|Layout|Home)$|^(Base|Styled|Custom|Ui|UI|Primitive)[A-Z]\w*$|(Page|Layout|Template)$/;
|
|
98
|
+
const GENERIC_MEMBER_PROP = /* @__PURE__ */ new Set([
|
|
99
|
+
"mutate",
|
|
100
|
+
"mutateAsync",
|
|
101
|
+
"trigger",
|
|
102
|
+
"execute",
|
|
103
|
+
"run",
|
|
104
|
+
"call",
|
|
105
|
+
"open",
|
|
106
|
+
"close",
|
|
107
|
+
"toggle",
|
|
108
|
+
"show",
|
|
109
|
+
"hide",
|
|
110
|
+
"reset",
|
|
111
|
+
"refetch",
|
|
112
|
+
"submit",
|
|
113
|
+
"onOpen",
|
|
114
|
+
"onClose",
|
|
115
|
+
"onToggle"
|
|
116
|
+
]);
|
|
117
|
+
/** 呼んでも要素の意味にならない呼び出し */
|
|
118
|
+
const NOISE_CALLS = /* @__PURE__ */ new Set([
|
|
119
|
+
"preventDefault",
|
|
120
|
+
"stopPropagation",
|
|
121
|
+
"stopImmediatePropagation",
|
|
122
|
+
"log",
|
|
123
|
+
"warn",
|
|
124
|
+
"error",
|
|
125
|
+
"info",
|
|
126
|
+
"debug",
|
|
127
|
+
"alert",
|
|
128
|
+
"confirm",
|
|
129
|
+
"prompt",
|
|
130
|
+
"setTimeout",
|
|
131
|
+
"clearTimeout",
|
|
132
|
+
"requestAnimationFrame",
|
|
133
|
+
"startTransition",
|
|
134
|
+
"then",
|
|
135
|
+
"catch",
|
|
136
|
+
"finally",
|
|
137
|
+
"resolve",
|
|
138
|
+
"all",
|
|
139
|
+
"focus",
|
|
140
|
+
"blur",
|
|
141
|
+
"scrollIntoView",
|
|
142
|
+
"success",
|
|
143
|
+
"loading",
|
|
144
|
+
"dismiss",
|
|
145
|
+
"toast",
|
|
146
|
+
"Number",
|
|
147
|
+
"String",
|
|
148
|
+
"Boolean",
|
|
149
|
+
"parseInt",
|
|
150
|
+
"structuredClone"
|
|
151
|
+
]);
|
|
152
|
+
/** メンバー呼び出しのときだけ雑音(配列・文字列・ストレージのメソッド) */
|
|
153
|
+
const NOISE_MEMBER = /* @__PURE__ */ new Set([
|
|
154
|
+
"stringify",
|
|
155
|
+
"parse",
|
|
156
|
+
"push",
|
|
157
|
+
"filter",
|
|
158
|
+
"map",
|
|
159
|
+
"includes",
|
|
160
|
+
"find",
|
|
161
|
+
"slice",
|
|
162
|
+
"trim",
|
|
163
|
+
"join",
|
|
164
|
+
"replace",
|
|
165
|
+
"getItem",
|
|
166
|
+
"setItem",
|
|
167
|
+
"removeItem",
|
|
168
|
+
"getTime",
|
|
169
|
+
"now",
|
|
170
|
+
"max",
|
|
171
|
+
"min",
|
|
172
|
+
"floor",
|
|
173
|
+
"round",
|
|
174
|
+
"random",
|
|
175
|
+
"toString",
|
|
176
|
+
"toISOString",
|
|
177
|
+
"current",
|
|
178
|
+
"forEach",
|
|
179
|
+
"concat",
|
|
180
|
+
"split",
|
|
181
|
+
"set",
|
|
182
|
+
"get",
|
|
183
|
+
"delete",
|
|
184
|
+
"has",
|
|
185
|
+
"add"
|
|
186
|
+
]);
|
|
187
|
+
const NOISE_OBJECTS = /* @__PURE__ */ new Set([
|
|
188
|
+
"e",
|
|
189
|
+
"ev",
|
|
190
|
+
"evt",
|
|
191
|
+
"event",
|
|
192
|
+
"console",
|
|
193
|
+
"toast",
|
|
194
|
+
"Math",
|
|
195
|
+
"Date",
|
|
196
|
+
"JSON",
|
|
197
|
+
"Object",
|
|
198
|
+
"Array",
|
|
199
|
+
"localStorage",
|
|
200
|
+
"sessionStorage"
|
|
201
|
+
]);
|
|
202
|
+
/** 既存の計測呼び出し(第 1 引数などのイベント名を名前にする) */
|
|
203
|
+
const TRACK_CALLEES = /* @__PURE__ */ new Set([
|
|
204
|
+
"track",
|
|
205
|
+
"trackEvent",
|
|
206
|
+
"sendGAEvent",
|
|
207
|
+
"sendGTMEvent",
|
|
208
|
+
"logEvent",
|
|
209
|
+
"gtag",
|
|
210
|
+
"capture",
|
|
211
|
+
"logAnalyticsEvent",
|
|
212
|
+
"analyticsTrack"
|
|
213
|
+
]);
|
|
214
|
+
const TRACK_OBJECTS = /* @__PURE__ */ new Set([
|
|
215
|
+
"posthog",
|
|
216
|
+
"mixpanel",
|
|
217
|
+
"amplitude",
|
|
218
|
+
"analytics",
|
|
219
|
+
"ga"
|
|
220
|
+
]);
|
|
221
|
+
const CLICKISH_COMPONENT = /Button|Link|Tab|MenuItem|Trigger|Checkbox|Radio|Switch|Toggle|SelectItem|Option|Chip|Action$/;
|
|
222
|
+
const HANDLER_PROPS = [
|
|
223
|
+
"onClick",
|
|
224
|
+
"onPress",
|
|
225
|
+
"onPointerDown",
|
|
226
|
+
"onMouseDown",
|
|
227
|
+
"onTouchStart",
|
|
228
|
+
"onDoubleClick",
|
|
229
|
+
"onKeyDown",
|
|
230
|
+
"onChange",
|
|
231
|
+
"onCheckedChange",
|
|
232
|
+
"onValueChange",
|
|
233
|
+
"onSelect",
|
|
234
|
+
"onToggle"
|
|
235
|
+
];
|
|
236
|
+
const GROUP_HANDLER_PROPS = [
|
|
237
|
+
"onValueChange",
|
|
238
|
+
"onChange",
|
|
239
|
+
"onCheckedChange"
|
|
240
|
+
];
|
|
241
|
+
const VALUE_ELEMENTS = /^(input|option|SelectItem|TabsTrigger|RadioGroupItem|ToggleGroupItem|DropdownMenuRadioItem|Radio|Tab)$/;
|
|
242
|
+
const NATIVE_CLICKABLE = /* @__PURE__ */ new Set([
|
|
243
|
+
"button",
|
|
244
|
+
"a",
|
|
245
|
+
"input",
|
|
246
|
+
"select",
|
|
247
|
+
"label",
|
|
248
|
+
"summary",
|
|
249
|
+
"area",
|
|
250
|
+
"option"
|
|
251
|
+
]);
|
|
252
|
+
const CLICK_ROLES = /* @__PURE__ */ new Set([
|
|
253
|
+
"button",
|
|
254
|
+
"link",
|
|
255
|
+
"tab",
|
|
256
|
+
"menuitem",
|
|
257
|
+
"option",
|
|
258
|
+
"switch",
|
|
259
|
+
"checkbox",
|
|
260
|
+
"radio",
|
|
261
|
+
"menuitemradio",
|
|
262
|
+
"menuitemcheckbox"
|
|
263
|
+
]);
|
|
264
|
+
/** 配列の名前として意味を持たない変数名 */
|
|
265
|
+
const GENERIC_ARRAY = /^(items|list|data|rows|options|entries|children|props)$/;
|
|
266
|
+
/**
|
|
267
|
+
* 候補の名前として使える形か(受信口の形の規則 `isElementName` と同じ)。ここでは候補を選ぶためだけに使い、
|
|
268
|
+
* 書き込んだ値の検査は受信口に一本化する(Design Doc #43 の 3 節)
|
|
269
|
+
*/
|
|
270
|
+
const ID = /^[A-Za-z0-9_.:/[\]*#|-]{1,64}$/;
|
|
271
|
+
function usable(n) {
|
|
272
|
+
return !!n && ID.test(n) && !/\d{3,}/.test(n.replace(/\[\*\]/g, ""));
|
|
273
|
+
}
|
|
274
|
+
const WRAPPERS = /* @__PURE__ */ new Set([
|
|
275
|
+
"TSAsExpression",
|
|
276
|
+
"TSSatisfiesExpression",
|
|
277
|
+
"TSNonNullExpression",
|
|
278
|
+
"ParenthesizedExpression",
|
|
279
|
+
"JSXExpressionContainer",
|
|
280
|
+
"AwaitExpression",
|
|
281
|
+
"TSTypeAssertion"
|
|
282
|
+
]);
|
|
283
|
+
function unwrap(e) {
|
|
284
|
+
let n = e;
|
|
285
|
+
while (n && WRAPPERS.has(n.type)) n = n.type === "AwaitExpression" ? n.argument : n.expression;
|
|
286
|
+
return n;
|
|
287
|
+
}
|
|
288
|
+
function staticString(e) {
|
|
289
|
+
const n = unwrap(e);
|
|
290
|
+
if (!n) return null;
|
|
291
|
+
if (n.type === "StringLiteral") return n.value;
|
|
292
|
+
if (n.type === "TemplateLiteral" && n.expressions.length === 0) return n.quasis[0].value.cooked ?? null;
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
/** 引数の静的な値: 文字列・数値・真偽・null と、x ± 数値 の ±N */
|
|
296
|
+
function literalArg(e) {
|
|
297
|
+
const str = staticString(e);
|
|
298
|
+
if (str !== null) return str;
|
|
299
|
+
const n = unwrap(e);
|
|
300
|
+
if (!n) return null;
|
|
301
|
+
if (n.type === "NumericLiteral") return String(n.value);
|
|
302
|
+
if (n.type === "UnaryExpression" && n.operator === "-" && n.argument.type === "NumericLiteral") return `-${n.argument.value}`;
|
|
303
|
+
if (n.type === "BooleanLiteral") return String(n.value);
|
|
304
|
+
if (n.type === "NullLiteral") return "null";
|
|
305
|
+
if (n.type === "BinaryExpression" && (n.operator === "+" || n.operator === "-") && n.right.type === "NumericLiteral") return `${n.operator}${n.right.value}`;
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
function keyName(p) {
|
|
309
|
+
if (p.computed) return null;
|
|
310
|
+
if (p.key.type === "Identifier") return p.key.name;
|
|
311
|
+
if (p.key.type === "StringLiteral") return p.key.value;
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
/** パスとして: 式の部分は [*]。静的な部分が無ければ null */
|
|
315
|
+
function pathTemplate(e) {
|
|
316
|
+
const n = unwrap(e);
|
|
317
|
+
if (!n) return null;
|
|
318
|
+
const s = staticString(n);
|
|
319
|
+
if (s !== null) return s;
|
|
320
|
+
if (n.type === "TemplateLiteral") {
|
|
321
|
+
let out = "";
|
|
322
|
+
n.quasis.forEach((q, i) => {
|
|
323
|
+
out += q.value.cooked;
|
|
324
|
+
if (i < n.expressions.length) out += "[*]";
|
|
325
|
+
});
|
|
326
|
+
return out;
|
|
327
|
+
}
|
|
328
|
+
if (n.type === "BinaryExpression" && n.operator === "+") {
|
|
329
|
+
const l = pathTemplate(n.left);
|
|
330
|
+
if (l === null) return null;
|
|
331
|
+
return l + (pathTemplate(n.right) ?? "[*]");
|
|
332
|
+
}
|
|
333
|
+
if (n.type === "ObjectExpression") {
|
|
334
|
+
const p = n.properties.find((p) => p.type === "ObjectProperty" && keyName(p) === "pathname");
|
|
335
|
+
return p ? pathTemplate(p.value) : null;
|
|
336
|
+
}
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
/** パスを名前にする: クエリ・ハッシュを落とし、外部 URL はホストだけ、mailto・tel は種類だけ */
|
|
340
|
+
function normalizePath(p) {
|
|
341
|
+
if (p === null) return null;
|
|
342
|
+
const s = p.trim();
|
|
343
|
+
if (/^mailto:/i.test(s)) return "mailto";
|
|
344
|
+
if (/^tel:/i.test(s)) return "tel";
|
|
345
|
+
if (/^https?:\/\//i.test(s)) {
|
|
346
|
+
const host = s.replace(/^https?:\/\//i, "").split(/[/?#]/)[0] ?? "";
|
|
347
|
+
return host.includes("[*]") || !host ? null : `ext:${host}`;
|
|
348
|
+
}
|
|
349
|
+
if (s.startsWith("#")) return s.length > 1 ? s : null;
|
|
350
|
+
if (!s.startsWith("/")) return null;
|
|
351
|
+
return (s.split(/[?#]/)[0] ?? "").replace(/\[\*\][^/]*/g, "[*]").replace(/\/+$/, "") || "/";
|
|
352
|
+
}
|
|
353
|
+
function calleeParts(c) {
|
|
354
|
+
const n = unwrap(c);
|
|
355
|
+
if (n.type === "Identifier") return {
|
|
356
|
+
obj: null,
|
|
357
|
+
prop: n.name
|
|
358
|
+
};
|
|
359
|
+
if (n.type === "MemberExpression" || n.type === "OptionalMemberExpression") {
|
|
360
|
+
const prop = !n.computed && n.property.type === "Identifier" ? n.property.name : null;
|
|
361
|
+
const o = unwrap(n.object);
|
|
362
|
+
return {
|
|
363
|
+
obj: o.type === "Identifier" ? o.name : o.type === "MemberExpression" && !o.computed && o.property.type === "Identifier" ? o.property.name : o.type === "ThisExpression" ? "this" : null,
|
|
364
|
+
prop
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
obj: null,
|
|
369
|
+
prop: null
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const PRIORITY = [
|
|
373
|
+
"track",
|
|
374
|
+
"call",
|
|
375
|
+
"setterArg",
|
|
376
|
+
"api",
|
|
377
|
+
"nav",
|
|
378
|
+
"mcall",
|
|
379
|
+
"setter"
|
|
380
|
+
];
|
|
381
|
+
/** 後始末(pause・stopSpeaking)が先に来て本体が後に来る書き方が多いので、同じ種類の中では最後のものを採る */
|
|
382
|
+
const LAST = /* @__PURE__ */ new Set([
|
|
383
|
+
"call",
|
|
384
|
+
"setterArg",
|
|
385
|
+
"mcall"
|
|
386
|
+
]);
|
|
387
|
+
var Ctx = class {
|
|
388
|
+
parent = /* @__PURE__ */ new WeakMap();
|
|
389
|
+
constructor(program) {
|
|
390
|
+
const visit = (n) => {
|
|
391
|
+
for (const c of children(n)) {
|
|
392
|
+
this.parent.set(c, n);
|
|
393
|
+
visit(c);
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
visit(program);
|
|
397
|
+
}
|
|
398
|
+
ancestors(n) {
|
|
399
|
+
const out = [];
|
|
400
|
+
for (let p = this.parent.get(n); p; p = this.parent.get(p)) out.push(p);
|
|
401
|
+
return out;
|
|
402
|
+
}
|
|
403
|
+
/** 識別子の束縛(同じファイル内: 変数宣言・関数宣言・分割代入・引数)を内側のスコープから探す */
|
|
404
|
+
binding(name, from) {
|
|
405
|
+
for (const a of [from, ...this.ancestors(from)]) {
|
|
406
|
+
if (/Function/.test(a.type)) for (const p of a.params) {
|
|
407
|
+
if (p.type === "Identifier" && p.name === name) return {
|
|
408
|
+
init: null,
|
|
409
|
+
kind: "param"
|
|
410
|
+
};
|
|
411
|
+
if (p.type === "ObjectPattern") {
|
|
412
|
+
for (const pr of p.properties) if (pr.type === "ObjectProperty" && unwrap(pr.value)?.type === "Identifier" && pr.value.name === name) return {
|
|
413
|
+
init: null,
|
|
414
|
+
kind: "param"
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const body = a.type === "BlockStatement" || a.type === "Program" ? a.body : null;
|
|
419
|
+
if (!body) continue;
|
|
420
|
+
for (const st of body) {
|
|
421
|
+
const decl = st.type === "ExportNamedDeclaration" || st.type === "ExportDefaultDeclaration" ? st.declaration : st;
|
|
422
|
+
if (decl?.type === "VariableDeclaration") for (const d of decl.declarations) {
|
|
423
|
+
if (d.id.type === "Identifier" && d.id.name === name) return {
|
|
424
|
+
init: d.init,
|
|
425
|
+
kind: "var"
|
|
426
|
+
};
|
|
427
|
+
if (d.id.type === "ObjectPattern") {
|
|
428
|
+
for (const pr of d.id.properties) if (pr.type === "ObjectProperty" && pr.value.type === "Identifier" && pr.value.name === name) return {
|
|
429
|
+
init: d.init,
|
|
430
|
+
kind: "destructure"
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (d.id.type === "ArrayPattern" && d.id.elements.some((el) => el?.type === "Identifier" && el.name === name)) return {
|
|
434
|
+
init: d.init,
|
|
435
|
+
kind: "destructure",
|
|
436
|
+
pattern: d.id
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
if (decl?.type === "FunctionDeclaration" && decl.id?.name === name) return {
|
|
440
|
+
init: decl,
|
|
441
|
+
kind: "fn"
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
function trackName(call) {
|
|
449
|
+
const { obj, prop } = calleeParts(call.callee);
|
|
450
|
+
const args = call.arguments;
|
|
451
|
+
const fromObj = (a) => {
|
|
452
|
+
const o = unwrap(a);
|
|
453
|
+
if (o?.type !== "ObjectExpression") return null;
|
|
454
|
+
const p = o.properties.find((p) => p.type === "ObjectProperty" && [
|
|
455
|
+
"event",
|
|
456
|
+
"name",
|
|
457
|
+
"action"
|
|
458
|
+
].includes(keyName(p) ?? ""));
|
|
459
|
+
return p ? staticString(p.value) : null;
|
|
460
|
+
};
|
|
461
|
+
if (obj === "dataLayer" && prop === "push") return fromObj(args[0]);
|
|
462
|
+
if (!(prop && (TRACK_CALLEES.has(prop) || obj && TRACK_OBJECTS.has(obj) && [
|
|
463
|
+
"track",
|
|
464
|
+
"capture",
|
|
465
|
+
"logEvent"
|
|
466
|
+
].includes(prop)))) return null;
|
|
467
|
+
if (prop === "gtag" || prop === "sendGAEvent" && staticString(args[0]) === "event") return staticString(args[1]);
|
|
468
|
+
if (prop === "logEvent" && args[0] && staticString(args[0]) === null) return staticString(args[1]);
|
|
469
|
+
return staticString(args[0]) ?? fromObj(args[0]);
|
|
470
|
+
}
|
|
471
|
+
/** 画面遷移: 遷移先のパス(名前にならなければ null)。遷移でなければ undefined */
|
|
472
|
+
function navPath(call) {
|
|
473
|
+
const { obj, prop } = calleeParts(call.callee);
|
|
474
|
+
const a0 = call.arguments[0];
|
|
475
|
+
if (obj && /router|navigation|nav/i.test(obj) && (prop === "push" || prop === "replace") || prop === "redirect" || prop === "navigate" || prop === "permanentRedirect" || obj === "location" && (prop === "assign" || prop === "replace") || obj === "window" && prop === "open") return normalizePath(pathTemplate(a0));
|
|
476
|
+
if (obj && /router|history/i.test(obj) && prop === "back" || prop === "back") return "back";
|
|
477
|
+
if (obj && /router/i.test(obj) && prop === "refresh") return "refresh";
|
|
478
|
+
}
|
|
479
|
+
/** API の呼び出し: API のパス(名前にならなければ null)。API でなければ undefined */
|
|
480
|
+
function apiPath(call) {
|
|
481
|
+
const { obj, prop } = calleeParts(call.callee);
|
|
482
|
+
if (prop === "fetch" || prop === "axios" || obj && /^(axios|api|client|http|ky|\$fetch)$/.test(obj) && /^(get|post|put|patch|delete|request)$/.test(prop ?? "")) {
|
|
483
|
+
const p = normalizePath(pathTemplate(call.arguments[0]));
|
|
484
|
+
return p?.startsWith("/") ? p : null;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function analyzeCall(call, ctx, out) {
|
|
488
|
+
const tracked = trackName(call);
|
|
489
|
+
if (tracked) {
|
|
490
|
+
out.push({
|
|
491
|
+
kind: "track",
|
|
492
|
+
name: tracked
|
|
493
|
+
});
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const nav = navPath(call);
|
|
497
|
+
if (nav !== void 0) {
|
|
498
|
+
if (nav) out.push({
|
|
499
|
+
kind: "nav",
|
|
500
|
+
name: nav
|
|
501
|
+
});
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const api = apiPath(call);
|
|
505
|
+
if (api !== void 0) {
|
|
506
|
+
if (api) out.push({
|
|
507
|
+
kind: "api",
|
|
508
|
+
name: api
|
|
509
|
+
});
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
const { obj, prop } = calleeParts(call.callee);
|
|
513
|
+
if (prop && [
|
|
514
|
+
"startTransition",
|
|
515
|
+
"setTimeout",
|
|
516
|
+
"requestAnimationFrame",
|
|
517
|
+
"then",
|
|
518
|
+
"finally",
|
|
519
|
+
"catch"
|
|
520
|
+
].includes(prop)) {
|
|
521
|
+
const cu = unwrap(call.callee);
|
|
522
|
+
if (cu.type === "MemberExpression") walkExpr(cu.object, ctx, out);
|
|
523
|
+
for (const a of call.arguments) if (/Function/.test(a.type)) walkFn(a, ctx, out);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (prop === "dispatch") {
|
|
527
|
+
const a = unwrap(call.arguments[0]);
|
|
528
|
+
if (a?.type === "CallExpression") {
|
|
529
|
+
const c = calleeParts(a.callee).prop;
|
|
530
|
+
if (c) out.push({
|
|
531
|
+
kind: "call",
|
|
532
|
+
name: c
|
|
533
|
+
});
|
|
534
|
+
} else if (a?.type === "ObjectExpression") {
|
|
535
|
+
const p = a.properties.find((p) => keyName(p) === "type");
|
|
536
|
+
const s = p && staticString(p.value);
|
|
537
|
+
if (s) out.push({
|
|
538
|
+
kind: "call",
|
|
539
|
+
name: s
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (!prop || NOISE_CALLS.has(prop) || obj && NOISE_MEMBER.has(prop)) {
|
|
545
|
+
for (const a of call.arguments) walkExpr(a, ctx, out);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
if (obj && NOISE_OBJECTS.has(obj)) return;
|
|
549
|
+
if (/^set[A-Z]/.test(prop)) {
|
|
550
|
+
const arg = literalArg(call.arguments[0]);
|
|
551
|
+
const promote = arg !== null && arg !== "false" && arg !== "null";
|
|
552
|
+
out.push(arg && usable(`${prop}:${arg}`) ? {
|
|
553
|
+
kind: promote ? "setterArg" : "setter",
|
|
554
|
+
name: `${prop}:${arg}`
|
|
555
|
+
} : {
|
|
556
|
+
kind: "setter",
|
|
557
|
+
name: prop
|
|
558
|
+
});
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (GENERIC_MEMBER_PROP.has(prop) && !obj) {
|
|
562
|
+
const init = unwrap(ctx.binding(prop, call)?.init);
|
|
563
|
+
const key = init?.type === "CallExpression" ? normalizePath(pathTemplate(init.arguments[0])) : null;
|
|
564
|
+
if (key?.startsWith("/")) out.push({
|
|
565
|
+
kind: "api",
|
|
566
|
+
name: key
|
|
567
|
+
});
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (GENERIC_MEMBER_PROP.has(prop)) {
|
|
571
|
+
if (obj && ![
|
|
572
|
+
"props",
|
|
573
|
+
"this",
|
|
574
|
+
"state",
|
|
575
|
+
"store",
|
|
576
|
+
"ref",
|
|
577
|
+
"current"
|
|
578
|
+
].includes(obj)) out.push({
|
|
579
|
+
kind: "call",
|
|
580
|
+
name: obj
|
|
581
|
+
});
|
|
582
|
+
else if (prop.startsWith("on") && !GENERIC_HANDLER.test(prop)) out.push({
|
|
583
|
+
kind: "call",
|
|
584
|
+
name: prop
|
|
585
|
+
});
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (obj === "clipboard" || obj === "navigator") {
|
|
589
|
+
out.push({
|
|
590
|
+
kind: "call",
|
|
591
|
+
name: `clipboard.${prop}`
|
|
592
|
+
});
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (obj === "window" && [
|
|
596
|
+
"print",
|
|
597
|
+
"scrollTo",
|
|
598
|
+
"close"
|
|
599
|
+
].includes(prop)) {
|
|
600
|
+
out.push({
|
|
601
|
+
kind: "call",
|
|
602
|
+
name: prop
|
|
603
|
+
});
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (GENERIC_HANDLER.test(prop)) return;
|
|
607
|
+
const arg = literalArg(call.arguments[0]);
|
|
608
|
+
out.push({
|
|
609
|
+
kind: obj ? "mcall" : "call",
|
|
610
|
+
name: arg && usable(`${prop}:${arg}`) ? `${prop}:${arg}` : prop
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
function walkExpr(e, ctx, out) {
|
|
614
|
+
const n = unwrap(e);
|
|
615
|
+
if (!n) return;
|
|
616
|
+
switch (n.type) {
|
|
617
|
+
case "CallExpression":
|
|
618
|
+
case "OptionalCallExpression":
|
|
619
|
+
analyzeCall(n, ctx, out);
|
|
620
|
+
return;
|
|
621
|
+
case "LogicalExpression":
|
|
622
|
+
walkExpr(n.left, ctx, out);
|
|
623
|
+
walkExpr(n.right, ctx, out);
|
|
624
|
+
return;
|
|
625
|
+
case "ConditionalExpression":
|
|
626
|
+
walkExpr(n.consequent, ctx, out);
|
|
627
|
+
walkExpr(n.alternate, ctx, out);
|
|
628
|
+
return;
|
|
629
|
+
case "SequenceExpression":
|
|
630
|
+
for (const x of n.expressions) walkExpr(x, ctx, out);
|
|
631
|
+
return;
|
|
632
|
+
case "UnaryExpression":
|
|
633
|
+
walkExpr(n.argument, ctx, out);
|
|
634
|
+
return;
|
|
635
|
+
case "AssignmentExpression": {
|
|
636
|
+
const { obj, prop } = calleeParts(unwrap(n.left));
|
|
637
|
+
if (prop === "href" && /location/.test(obj ?? "")) {
|
|
638
|
+
const p = normalizePath(pathTemplate(n.right));
|
|
639
|
+
if (p) out.push({
|
|
640
|
+
kind: "nav",
|
|
641
|
+
name: p
|
|
642
|
+
});
|
|
643
|
+
} else walkExpr(n.right, ctx, out);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function walkStmts(stmts, ctx, out) {
|
|
649
|
+
for (const st of stmts) switch (st.type) {
|
|
650
|
+
case "ExpressionStatement":
|
|
651
|
+
walkExpr(st.expression, ctx, out);
|
|
652
|
+
break;
|
|
653
|
+
case "ReturnStatement":
|
|
654
|
+
walkExpr(st.argument, ctx, out);
|
|
655
|
+
break;
|
|
656
|
+
case "IfStatement":
|
|
657
|
+
walkExpr(st.test, ctx, out);
|
|
658
|
+
walkStmts([st.consequent], ctx, out);
|
|
659
|
+
if (st.alternate) walkStmts([st.alternate], ctx, out);
|
|
660
|
+
break;
|
|
661
|
+
case "BlockStatement":
|
|
662
|
+
walkStmts(st.body, ctx, out);
|
|
663
|
+
break;
|
|
664
|
+
case "TryStatement":
|
|
665
|
+
walkStmts(st.block.body, ctx, out);
|
|
666
|
+
break;
|
|
667
|
+
case "VariableDeclaration": for (const d of st.declarations) walkExpr(d.init, ctx, out);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function walkFn(fn, ctx, out) {
|
|
671
|
+
if (fn.body.type === "BlockStatement") walkStmts(fn.body.body, ctx, out);
|
|
672
|
+
else walkExpr(fn.body, ctx, out);
|
|
673
|
+
}
|
|
674
|
+
function pick(cands) {
|
|
675
|
+
for (const k of PRIORITY) {
|
|
676
|
+
const xs = cands.filter((x) => x.kind === k && usable(x.name));
|
|
677
|
+
const found = LAST.has(k) ? xs[xs.length - 1] : xs[0];
|
|
678
|
+
if (found) return found;
|
|
679
|
+
}
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
function fromBody(fn, ctx) {
|
|
683
|
+
const out = [];
|
|
684
|
+
walkFn(fn, ctx, out);
|
|
685
|
+
const c = pick(out);
|
|
686
|
+
return c ? {
|
|
687
|
+
name: c.name,
|
|
688
|
+
source: c.kind
|
|
689
|
+
} : null;
|
|
690
|
+
}
|
|
691
|
+
/** ハンドラの式 → 名前 */
|
|
692
|
+
function handlerName(expr, ctx, depth = 0) {
|
|
693
|
+
const n = unwrap(expr);
|
|
694
|
+
if (!n || depth > 3) return null;
|
|
695
|
+
if (n.type === "Identifier") {
|
|
696
|
+
const b = ctx.binding(n.name, n);
|
|
697
|
+
const init = unwrap(b?.init);
|
|
698
|
+
if (b?.kind === "destructure" && b.pattern && init?.type === "CallExpression" && /^(useActionState|useFormState)$/.test(calleeParts(init.callee).prop ?? "")) {
|
|
699
|
+
const a0 = init.arguments[0];
|
|
700
|
+
return a0?.type === "Identifier" ? {
|
|
701
|
+
name: a0.name,
|
|
702
|
+
source: "action"
|
|
703
|
+
} : null;
|
|
704
|
+
}
|
|
705
|
+
if (!GENERIC_HANDLER.test(n.name)) return {
|
|
706
|
+
name: n.name,
|
|
707
|
+
source: "handler"
|
|
708
|
+
};
|
|
709
|
+
if (init && /Function/.test(init.type)) return fromBody(init, ctx);
|
|
710
|
+
if (init?.type === "CallExpression" && /^(useCallback|useMemo)$/.test(calleeParts(init.callee).prop ?? "") && /Function/.test(init.arguments[0]?.type)) return fromBody(init.arguments[0], ctx);
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
if (n.type === "MemberExpression" || n.type === "OptionalMemberExpression") {
|
|
714
|
+
const { obj, prop } = calleeParts(n);
|
|
715
|
+
if (!prop) return null;
|
|
716
|
+
if (GENERIC_MEMBER_PROP.has(prop)) return obj && ![
|
|
717
|
+
"props",
|
|
718
|
+
"this",
|
|
719
|
+
"state"
|
|
720
|
+
].includes(obj) ? {
|
|
721
|
+
name: obj,
|
|
722
|
+
source: "handler"
|
|
723
|
+
} : null;
|
|
724
|
+
if (/^set[A-Z]/.test(prop)) return {
|
|
725
|
+
name: prop,
|
|
726
|
+
source: "setter"
|
|
727
|
+
};
|
|
728
|
+
return GENERIC_HANDLER.test(prop) ? null : {
|
|
729
|
+
name: prop,
|
|
730
|
+
source: "handler"
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
if (/Function/.test(n.type)) return fromBody(n, ctx);
|
|
734
|
+
if (n.type === "CallExpression") {
|
|
735
|
+
const { obj, prop } = calleeParts(n.callee);
|
|
736
|
+
if (prop === "bind" && obj) return GENERIC_HANDLER.test(obj) ? null : {
|
|
737
|
+
name: obj,
|
|
738
|
+
source: "action"
|
|
739
|
+
};
|
|
740
|
+
if (prop && /^(useCallback|useMemo)$/.test(prop)) return handlerName(n.arguments[0], ctx, depth + 1);
|
|
741
|
+
const arg = staticString(n.arguments[0]);
|
|
742
|
+
if (prop && arg && usable(`${prop}:${arg}`)) return {
|
|
743
|
+
name: `${prop}:${arg}`,
|
|
744
|
+
source: "handler"
|
|
745
|
+
};
|
|
746
|
+
if (prop && !GENERIC_HANDLER.test(prop)) return (n.arguments[0] ? handlerName(n.arguments[0], ctx, depth + 1) : null) ?? {
|
|
747
|
+
name: prop,
|
|
748
|
+
source: "handler"
|
|
749
|
+
};
|
|
750
|
+
return n.arguments[0] ? handlerName(n.arguments[0], ctx, depth + 1) : null;
|
|
751
|
+
}
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
function tagName(o) {
|
|
755
|
+
const nm = o.name;
|
|
756
|
+
if (nm.type === "JSXIdentifier") return nm.name;
|
|
757
|
+
if (nm.type === "JSXMemberExpression") {
|
|
758
|
+
const parts = [];
|
|
759
|
+
let x = nm;
|
|
760
|
+
while (x.type === "JSXMemberExpression") {
|
|
761
|
+
parts.unshift(x.property.name);
|
|
762
|
+
x = x.object;
|
|
763
|
+
}
|
|
764
|
+
parts.unshift(x.name);
|
|
765
|
+
return parts.join(".");
|
|
766
|
+
}
|
|
767
|
+
return "";
|
|
768
|
+
}
|
|
769
|
+
function jsxAttr(o, name) {
|
|
770
|
+
return o.attributes.find((a) => a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && a.name.name === name);
|
|
771
|
+
}
|
|
772
|
+
function attrExpr(o, name) {
|
|
773
|
+
const a = jsxAttr(o, name);
|
|
774
|
+
return a ? a.value ?? {
|
|
775
|
+
type: "BooleanLiteral",
|
|
776
|
+
value: true
|
|
777
|
+
} : null;
|
|
778
|
+
}
|
|
779
|
+
function attrStatic(o, name) {
|
|
780
|
+
const e = attrExpr(o, name);
|
|
781
|
+
return e ? staticString(e) : null;
|
|
782
|
+
}
|
|
783
|
+
/** クリックされうる要素: ネイティブの要素、クリックの role、クリックのハンドラ、クリックらしいコンポーネント */
|
|
784
|
+
function isClickable(o) {
|
|
785
|
+
const tag = tagName(o);
|
|
786
|
+
const last = tag.split(".").pop() ?? "";
|
|
787
|
+
const has = (p) => !!jsxAttr(o, p);
|
|
788
|
+
const role = attrStatic(o, "role");
|
|
789
|
+
if (NATIVE_CLICKABLE.has(tag)) return true;
|
|
790
|
+
if (role && CLICK_ROLES.has(role)) return true;
|
|
791
|
+
if (HANDLER_PROPS.some((p) => p !== "onChange" && has(p))) return true;
|
|
792
|
+
if (/^[A-Z]/.test(last)) {
|
|
793
|
+
if (has("href") || has("onChange") || has("formAction") || has("value") && VALUE_ELEMENTS.test(last)) return true;
|
|
794
|
+
if (CLICKISH_COMPONENT.test(last)) return true;
|
|
795
|
+
}
|
|
796
|
+
return false;
|
|
797
|
+
}
|
|
798
|
+
/** 要素を含むコンポーネント(名前が大文字で始まる関数・クラス) */
|
|
799
|
+
function enclosingComponent(ctx, n) {
|
|
800
|
+
for (const a of ctx.ancestors(n)) {
|
|
801
|
+
if (a.type === "FunctionDeclaration" && a.id && /^[A-Z]/.test(a.id.name)) return {
|
|
802
|
+
name: a.id.name,
|
|
803
|
+
fn: a
|
|
804
|
+
};
|
|
805
|
+
if (/Function/.test(a.type)) {
|
|
806
|
+
let p = ctx.parent.get(a);
|
|
807
|
+
while (p && p.type === "CallExpression" && /^(forwardRef|memo)$/.test(calleeParts(p.callee).prop ?? "")) p = ctx.parent.get(p);
|
|
808
|
+
if (p?.type === "VariableDeclarator" && p.id.type === "Identifier" && /^[A-Z]/.test(p.id.name)) return {
|
|
809
|
+
name: p.id.name,
|
|
810
|
+
fn: a
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
if (a.type === "ClassDeclaration" && a.id) return {
|
|
814
|
+
name: a.id.name,
|
|
815
|
+
fn: a
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
return null;
|
|
819
|
+
}
|
|
820
|
+
/** 要素がコンポーネントの根か(return の直下 / 式本体。条件式・&& の枝を含む) */
|
|
821
|
+
function isRootOf(ctx, el, fn) {
|
|
822
|
+
let p = ctx.parent.get(el);
|
|
823
|
+
while (p && [
|
|
824
|
+
"ParenthesizedExpression",
|
|
825
|
+
"ConditionalExpression",
|
|
826
|
+
"LogicalExpression"
|
|
827
|
+
].includes(p.type)) p = ctx.parent.get(p);
|
|
828
|
+
if (p === fn) return true;
|
|
829
|
+
if (p?.type === "ReturnStatement") {
|
|
830
|
+
for (const a of ctx.ancestors(p)) if (/Function|ClassMethod/.test(a.type)) return a === fn || fn.type === "ClassDeclaration" && ctx.ancestors(a).includes(fn);
|
|
831
|
+
}
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
function enclosingJsx(ctx, el, pred) {
|
|
835
|
+
for (const a of ctx.ancestors(el)) if (a.type === "JSXElement" && pred(a.openingElement)) return a.openingElement;
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
/** items.map(...) の中の要素なら配列の変数名 */
|
|
839
|
+
function enclosingMapArray(ctx, el) {
|
|
840
|
+
for (const a of ctx.ancestors(el)) {
|
|
841
|
+
if (a.type === "CallExpression" && calleeParts(a.callee).prop === "map") {
|
|
842
|
+
const o = unwrap(unwrap(a.callee).object);
|
|
843
|
+
if (o?.type === "Identifier") return o.name;
|
|
844
|
+
if (o?.type === "MemberExpression" && !o.computed) return o.property.name;
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
if (a.type === "FunctionDeclaration") return null;
|
|
848
|
+
}
|
|
849
|
+
return null;
|
|
850
|
+
}
|
|
851
|
+
function nameOf(ctx, el) {
|
|
852
|
+
const o = el.openingElement;
|
|
853
|
+
const tag = tagName(o);
|
|
854
|
+
const last = tag.split(".").pop() ?? "";
|
|
855
|
+
const ok = (name, source) => usable(name) ? {
|
|
856
|
+
name,
|
|
857
|
+
source
|
|
858
|
+
} : null;
|
|
859
|
+
const value = VALUE_ELEMENTS.test(last) ? attrStatic(o, "value") : null;
|
|
860
|
+
const withValue = (r) => value && usable(`${r.name}:${value}`) && !r.name.includes(":") ? {
|
|
861
|
+
name: `${r.name}:${value}`,
|
|
862
|
+
source: `${r.source}+value`
|
|
863
|
+
} : r;
|
|
864
|
+
for (const p of HANDLER_PROPS) {
|
|
865
|
+
const e = attrExpr(o, p);
|
|
866
|
+
if (!e) continue;
|
|
867
|
+
const r = handlerName(e, ctx);
|
|
868
|
+
if (r && usable(r.name)) return withValue(r);
|
|
869
|
+
}
|
|
870
|
+
const fa = attrExpr(o, "formAction");
|
|
871
|
+
if (fa) {
|
|
872
|
+
const r = handlerName(fa, ctx);
|
|
873
|
+
if (r && usable(r.name)) return {
|
|
874
|
+
name: r.name,
|
|
875
|
+
source: "action"
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
for (const p of [
|
|
879
|
+
"id",
|
|
880
|
+
"data-gtm-click",
|
|
881
|
+
"data-gtm",
|
|
882
|
+
"data-gtm-id",
|
|
883
|
+
"name"
|
|
884
|
+
]) {
|
|
885
|
+
if (p === "name" && tag !== "input" && tag !== "select" && tag !== "button") continue;
|
|
886
|
+
const r = ok(attrStatic(o, p), p === "name" ? "name-attr" : "id");
|
|
887
|
+
if (r) return r;
|
|
888
|
+
}
|
|
889
|
+
for (const p of ["href", "to"]) {
|
|
890
|
+
const e = attrExpr(o, p);
|
|
891
|
+
if (e) {
|
|
892
|
+
const r = ok(normalizePath(pathTemplate(e)), "path");
|
|
893
|
+
if (r) return r;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (value) {
|
|
897
|
+
const group = enclosingJsx(ctx, el, (g) => GROUP_HANDLER_PROPS.some((p) => jsxAttr(g, p)));
|
|
898
|
+
if (group) for (const p of GROUP_HANDLER_PROPS) {
|
|
899
|
+
const e = attrExpr(group, p);
|
|
900
|
+
if (!e) continue;
|
|
901
|
+
const r = handlerName(e, ctx);
|
|
902
|
+
if (r && usable(r.name)) return withValue(r);
|
|
903
|
+
}
|
|
904
|
+
if (usable(value)) return {
|
|
905
|
+
name: value,
|
|
906
|
+
source: "value"
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
const type = attrStatic(o, "type");
|
|
910
|
+
if (tag === "button" && (type === null || type === "submit") || tag === "input" && type === "submit" || /Submit/.test(last)) {
|
|
911
|
+
const form = enclosingJsx(ctx, el, (f) => tagName(f) === "form" || /Form$/.test(tagName(f)));
|
|
912
|
+
if (form) for (const p of ["action", "onSubmit"]) {
|
|
913
|
+
const e = attrExpr(form, p);
|
|
914
|
+
if (!e) continue;
|
|
915
|
+
const r = handlerName(e, ctx);
|
|
916
|
+
if (r && usable(r.name)) return {
|
|
917
|
+
name: r.name,
|
|
918
|
+
source: "form"
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
const arr = enclosingMapArray(ctx, el);
|
|
923
|
+
if (arr && !GENERIC_ARRAY.test(arr)) {
|
|
924
|
+
const r = ok(arr, "map-array");
|
|
925
|
+
if (r) return r;
|
|
926
|
+
}
|
|
927
|
+
if (/^[A-Z]/.test(last) && !tag.includes(".") && !GENERIC_COMPONENT.test(last) && !/^(Select|Tabs|TabsList|Dialog|Menu)/.test(last)) {
|
|
928
|
+
const r = ok(last, "component");
|
|
929
|
+
if (r) return r;
|
|
930
|
+
}
|
|
931
|
+
const enc = enclosingComponent(ctx, el);
|
|
932
|
+
if (enc && !GENERIC_COMPONENT.test(enc.name)) {
|
|
933
|
+
const r = ok(enc.name, isRootOf(ctx, el, enc.fn) ? "own-component" : "context");
|
|
934
|
+
if (r) return r;
|
|
935
|
+
}
|
|
936
|
+
if (arr) {
|
|
937
|
+
const r = ok(arr, "map-array");
|
|
938
|
+
if (r) return r;
|
|
939
|
+
}
|
|
940
|
+
return {
|
|
941
|
+
name: null,
|
|
942
|
+
source: "none"
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
/** プログラム中の JSX 要素ごとに名前を決める(既定ではクリックされうる要素だけを返す) */
|
|
946
|
+
function extractFromProgram(program, options = {}) {
|
|
947
|
+
const ctx = new Ctx(program);
|
|
948
|
+
const out = [];
|
|
949
|
+
const visit = (n) => {
|
|
950
|
+
if (n.type === "JSXElement") {
|
|
951
|
+
const o = n.openingElement;
|
|
952
|
+
const clickable = isClickable(o);
|
|
953
|
+
if (clickable || options.all) {
|
|
954
|
+
const { name, source } = nameOf(ctx, n);
|
|
955
|
+
let receiver = null;
|
|
956
|
+
const enc = enclosingComponent(ctx, n);
|
|
957
|
+
if (enc && /^[a-z]/.test(tagName(o)) && isRootOf(ctx, n, enc.fn)) {
|
|
958
|
+
const p0 = enc.fn.params?.[0];
|
|
959
|
+
receiver = p0?.type === "ObjectPattern" ? {
|
|
960
|
+
kind: "pattern",
|
|
961
|
+
pattern: p0
|
|
962
|
+
} : p0?.type === "Identifier" ? {
|
|
963
|
+
kind: "ident",
|
|
964
|
+
name: p0.name
|
|
965
|
+
} : { kind: "none" };
|
|
966
|
+
}
|
|
967
|
+
out.push({
|
|
968
|
+
element: n,
|
|
969
|
+
tag: tagName(o),
|
|
970
|
+
name,
|
|
971
|
+
source,
|
|
972
|
+
label: labelOf(n),
|
|
973
|
+
clickable,
|
|
974
|
+
receiver
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
for (const c of children(n)) visit(c);
|
|
979
|
+
};
|
|
980
|
+
visit(program);
|
|
981
|
+
return out;
|
|
982
|
+
}
|
|
983
|
+
//#endregion
|
|
984
|
+
//#region ../build-names/src/element-name/transform.ts
|
|
985
|
+
/** 書く属性。@auspex-ai/core の ELEMENT_REF_ATTR(読む側)と同じ値(test/element-names.test.ts で一致を確かめる) */
|
|
986
|
+
const REF_ATTR = "data-auspex-ref";
|
|
987
|
+
/** ラベル(NAL-124)の参照。@auspex-ai/core の LABEL_REF_ATTR(読む側)と同じ値(test/element-names.test.ts で一致を確かめる) */
|
|
988
|
+
const LABEL_REF_ATTR = "data-auspex-label-ref";
|
|
989
|
+
/** 手書きは常に消す。アイコンの属性(#50 段階 2)も同じ理由 */
|
|
990
|
+
const HAND_WRITTEN = /* @__PURE__ */ new Set([
|
|
991
|
+
REF_ATTR,
|
|
992
|
+
LABEL_REF_ATTR,
|
|
993
|
+
"data-auspex-name",
|
|
994
|
+
require_transform.ICON_REF_ATTR,
|
|
995
|
+
require_transform.LEGACY_ICON_ATTR
|
|
996
|
+
]);
|
|
997
|
+
/** 定義側で受け取った参照を入れる変数(この名前がソースにあれば変換済みとみなす) */
|
|
998
|
+
const RECEIVED = "__auspexRef";
|
|
999
|
+
/** 同じく、ラベルの参照を入れる変数 */
|
|
1000
|
+
const RECEIVED_LABEL = "__auspexLabelRef";
|
|
1001
|
+
const startOf = (n) => n.start ?? 0;
|
|
1002
|
+
const endOf = (n) => n.end ?? 0;
|
|
1003
|
+
/** JSX を含みうるファイルか(解析する前の絞り込み) */
|
|
1004
|
+
function mayHaveElements(source, file) {
|
|
1005
|
+
return !file.endsWith(".ts") && /<[A-Za-z]/.test(source) && !source.includes(RECEIVED);
|
|
1006
|
+
}
|
|
1007
|
+
/** 手書きの属性を消す対象になりうるソースか(mayHaveElements とは別の判定。.ts など JSX を含まないファイルも) */
|
|
1008
|
+
function mayHaveHandWritten(source) {
|
|
1009
|
+
return source.includes("data-auspex-ref") || source.includes("data-auspex-label-ref") || source.includes("data-auspex-name") || source.includes("data-auspex-icon");
|
|
1010
|
+
}
|
|
1011
|
+
function passedValue(receiver, attr, received) {
|
|
1012
|
+
if (receiver.kind === "pattern") return received;
|
|
1013
|
+
if (receiver.kind === "ident") return `${receiver.name}?.[${JSON.stringify(attr)}]`;
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
const SKIP_KEYS = /* @__PURE__ */ new Set([
|
|
1017
|
+
"loc",
|
|
1018
|
+
"start",
|
|
1019
|
+
"end",
|
|
1020
|
+
"extra",
|
|
1021
|
+
"range",
|
|
1022
|
+
"leadingComments",
|
|
1023
|
+
"trailingComments",
|
|
1024
|
+
"innerComments"
|
|
1025
|
+
]);
|
|
1026
|
+
/** オブジェクトリテラルのプロパティのキーが、リテラルで書かれた手書きの属性名か */
|
|
1027
|
+
function isHandWrittenKey(p) {
|
|
1028
|
+
if (p.type === "SpreadElement") return false;
|
|
1029
|
+
const k = p.key;
|
|
1030
|
+
if (k.type === "StringLiteral") return HAND_WRITTEN.has(k.value);
|
|
1031
|
+
return !p.computed && k.type === "Identifier" && HAND_WRITTEN.has(k.name);
|
|
1032
|
+
}
|
|
1033
|
+
/** JSX の属性と、オブジェクトリテラルのリテラルのキー(スプレッド・createElement・jsx() の props を含む)から手書きの属性を消す */
|
|
1034
|
+
function removeHandWritten(node, s) {
|
|
1035
|
+
if (node.type === "JSXOpeningElement") {
|
|
1036
|
+
for (const a of node.attributes) if (a.type === "JSXAttribute" && a.name.type === "JSXIdentifier" && HAND_WRITTEN.has(a.name.name)) s.remove(startOf(a), endOf(a));
|
|
1037
|
+
}
|
|
1038
|
+
if (node.type === "ObjectExpression") {
|
|
1039
|
+
const props = node.properties;
|
|
1040
|
+
for (let i = 0; i < props.length; i++) {
|
|
1041
|
+
const p = props[i];
|
|
1042
|
+
if (!p || !isHandWrittenKey(p)) continue;
|
|
1043
|
+
let j = i;
|
|
1044
|
+
while (j + 1 < props.length && props[j + 1] && isHandWrittenKey(props[j + 1])) j++;
|
|
1045
|
+
const first = props[i];
|
|
1046
|
+
const last = props[j];
|
|
1047
|
+
const next = props[j + 1];
|
|
1048
|
+
const prev = props[i - 1];
|
|
1049
|
+
if (next) s.remove(startOf(first), startOf(next));
|
|
1050
|
+
else if (prev) s.remove(endOf(prev), endOf(last));
|
|
1051
|
+
else s.remove(startOf(first), endOf(last));
|
|
1052
|
+
i = j;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
for (const key of Object.keys(node)) {
|
|
1056
|
+
if (SKIP_KEYS.has(key)) continue;
|
|
1057
|
+
const v = node[key];
|
|
1058
|
+
for (const c of Array.isArray(v) ? v : [v]) if (c && typeof c.type === "string") removeHandWritten(c, s);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
/** 手書きの属性の削除だけを `s` に積む(strip のみのモード。JSX を含まないファイルも) */
|
|
1062
|
+
function stripHandWritten(program, s) {
|
|
1063
|
+
removeHandWritten(program, s);
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* 要素の参照の書き込みを `s` に積む(手書きの属性の削除は stripHandWritten で別に行う)。書いた名前とラベルを返す。
|
|
1067
|
+
* `refOf` は名前(kind = 'element')・ラベル(kind = 'label')から参照を計算する(鍵はここに渡らない)。
|
|
1068
|
+
* ラベルの参照(data-auspex-label-ref)も要素の参照と同じ仕組みで書く
|
|
1069
|
+
*/
|
|
1070
|
+
function applyElementNames(program, s, refOf) {
|
|
1071
|
+
const written = /* @__PURE__ */ new Set();
|
|
1072
|
+
const writtenLabels = /* @__PURE__ */ new Set();
|
|
1073
|
+
const patched = /* @__PURE__ */ new Set();
|
|
1074
|
+
for (const { element, tag, name, label, receiver } of extractFromProgram(program)) {
|
|
1075
|
+
const dom = /^[a-z]/.test(tag);
|
|
1076
|
+
const attrs = [];
|
|
1077
|
+
const write = (attr, received, own) => {
|
|
1078
|
+
const passed = receiver ? passedValue(receiver, attr, received) : null;
|
|
1079
|
+
const fallback = own ?? (dom ? "\"\"" : null);
|
|
1080
|
+
const value = passed && fallback ? `${passed} ?? ${fallback}` : passed ?? fallback;
|
|
1081
|
+
return value === null ? null : { value };
|
|
1082
|
+
};
|
|
1083
|
+
const refValue = write(REF_ATTR, RECEIVED, name === null ? null : JSON.stringify(refOf(name, "element")));
|
|
1084
|
+
const labelValue = write(LABEL_REF_ATTR, RECEIVED_LABEL, label === null ? null : JSON.stringify(refOf(label, "label")));
|
|
1085
|
+
if (refValue === null && labelValue === null) continue;
|
|
1086
|
+
if (refValue && name !== null) written.add(name);
|
|
1087
|
+
if (labelValue && label !== null) writtenLabels.add(label);
|
|
1088
|
+
if (receiver?.kind === "pattern" && !patched.has(receiver.pattern)) {
|
|
1089
|
+
s.appendRight(startOf(receiver.pattern) + 1, ` ${JSON.stringify(REF_ATTR)}: ${RECEIVED}, ${JSON.stringify(LABEL_REF_ATTR)}: ${RECEIVED_LABEL},`);
|
|
1090
|
+
patched.add(receiver.pattern);
|
|
1091
|
+
}
|
|
1092
|
+
if (refValue) attrs.push(` ${REF_ATTR}={${refValue.value}}`);
|
|
1093
|
+
if (labelValue) attrs.push(` ${LABEL_REF_ATTR}={${labelValue.value}}`);
|
|
1094
|
+
s.appendLeft(endOf(element.openingElement.name), attrs.join(""));
|
|
1095
|
+
}
|
|
1096
|
+
return {
|
|
1097
|
+
names: [...written],
|
|
1098
|
+
labels: [...writtenLabels]
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
//#endregion
|
|
1102
|
+
//#region ../build-names/src/transform.ts
|
|
1103
|
+
/** 変換が不要なら null */
|
|
1104
|
+
function transformNames(source, file, options = {}) {
|
|
1105
|
+
const refOf = options.elementNames === "strip" ? void 0 : options.refOf;
|
|
1106
|
+
const icons = refOf !== void 0 && options.iconNames !== false && require_transform.mayHaveIcons(source);
|
|
1107
|
+
const elements = refOf !== void 0 && options.elementAttrs !== false && mayHaveElements(source, file);
|
|
1108
|
+
const strip = mayHaveHandWritten(source);
|
|
1109
|
+
if (!icons && !elements && !strip) return null;
|
|
1110
|
+
const program = require_transform.parseSource(source, file);
|
|
1111
|
+
if (!program) return null;
|
|
1112
|
+
const s = new magic_string.MagicString(source);
|
|
1113
|
+
if (strip) stripHandWritten(program, s);
|
|
1114
|
+
const { names, labels } = elements && refOf ? applyElementNames(program, s, (name, kind) => refOf(name, kind)) : {
|
|
1115
|
+
names: [],
|
|
1116
|
+
labels: []
|
|
1117
|
+
};
|
|
1118
|
+
const iconNames = icons && refOf ? require_transform.applyIconNames(program, source, file, s, (name) => refOf(name, "icon")) : [];
|
|
1119
|
+
if (!s.hasChanged()) return null;
|
|
1120
|
+
return {
|
|
1121
|
+
code: s.toString(),
|
|
1122
|
+
map: s.generateMap({
|
|
1123
|
+
hires: true,
|
|
1124
|
+
source: file,
|
|
1125
|
+
includeContent: true
|
|
1126
|
+
}),
|
|
1127
|
+
names,
|
|
1128
|
+
icons: iconNames,
|
|
1129
|
+
labels
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
//#endregion
|
|
1133
|
+
//#region src/icon-name/loader.ts
|
|
1134
|
+
function iconNameLoader(source, inputMap) {
|
|
1135
|
+
const file = this.resourcePath;
|
|
1136
|
+
const skip = /[\\/]node_modules[\\/]/.test(file) || /\.svg(\.|$)/i.test(file);
|
|
1137
|
+
const options = this.getOptions?.() ?? {};
|
|
1138
|
+
const nameOptions = { elementNames: "strip" };
|
|
1139
|
+
if (options.iconNames !== void 0) nameOptions.iconNames = options.iconNames;
|
|
1140
|
+
if (options.elementAttrs !== void 0) nameOptions.elementAttrs = options.elementAttrs;
|
|
1141
|
+
const refOf = skip ? null : require_transform.refOfFromEnv(options);
|
|
1142
|
+
if (refOf) {
|
|
1143
|
+
nameOptions.elementNames = "refs";
|
|
1144
|
+
nameOptions.refOf = refOf;
|
|
1145
|
+
}
|
|
1146
|
+
const out = skip ? null : transformNames(source, file, nameOptions);
|
|
1147
|
+
if (out && (out.names.length > 0 || out.icons.length > 0 || out.labels.length > 0) && options.fragmentsDir) require_transform.writeFragment(node_path.default.resolve(this.rootContext ?? process.cwd(), options.fragmentsDir), file, {
|
|
1148
|
+
names: out.names,
|
|
1149
|
+
icons: out.icons,
|
|
1150
|
+
...out.labels.length > 0 ? { labels: out.labels } : {}
|
|
1151
|
+
});
|
|
1152
|
+
if (!out) this.callback(null, source, inputMap);
|
|
1153
|
+
else this.callback(null, out.code, out.map);
|
|
1154
|
+
}
|
|
1155
|
+
//#endregion
|
|
1156
|
+
module.exports = iconNameLoader;
|