react-icons-sprite 1.3.0 → 1.4.0-rc1

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.
@@ -1,854 +0,0 @@
1
- import { a as DEFAULT_ICON_SOURCES, n as normalizePackAlias } from "./compute-icon-id-1Gop_fRM.mjs";
2
- import MagicString from "magic-string";
3
- //#region src/transform/edit-applier.ts
4
- const applyEdits = (code, edits) => {
5
- const magicString = new MagicString(code);
6
- for (const edit of edits) {
7
- if (edit.type === "replace") magicString.overwrite(edit.from, edit.to, edit.value);
8
- if (edit.type === "insert") magicString.appendLeft(edit.pos, edit.value);
9
- if (edit.type === "remove") magicString.remove(edit.from, edit.to);
10
- }
11
- return magicString;
12
- };
13
- const editStart = (edit) => {
14
- return edit.type === "insert" ? edit.pos : edit.from;
15
- };
16
- const applyEditsToString = (code, edits, presorted = false) => {
17
- if (!edits.length) return code;
18
- const orderedEdits = presorted ? edits : [...edits].sort((left, right) => {
19
- const startDelta = editStart(left) - editStart(right);
20
- if (startDelta !== 0) return startDelta;
21
- if (left.type === "insert" && right.type !== "insert") return 1;
22
- if (left.type !== "insert" && right.type === "insert") return -1;
23
- return 0;
24
- });
25
- let result = "";
26
- let cursor = 0;
27
- for (const edit of orderedEdits) {
28
- if (edit.type === "insert") {
29
- if (edit.pos <= cursor) {
30
- result += edit.value;
31
- continue;
32
- }
33
- result += code.slice(cursor, edit.pos) + edit.value;
34
- cursor = edit.pos;
35
- continue;
36
- }
37
- result += code.slice(cursor, edit.from);
38
- if (edit.type === "replace") result += edit.value;
39
- cursor = edit.to;
40
- }
41
- return result + code.slice(cursor);
42
- };
43
- //#endregion
44
- //#region src/transform/fast-filter.ts
45
- const fastFilter = (code) => {
46
- if (!code.includes("<")) return false;
47
- if (!code.includes("import")) return false;
48
- return true;
49
- };
50
- //#endregion
51
- //#region src/transform/transform-module.ts
52
- const ICON_SOURCE = "react-icons-sprite";
53
- const ICON_COMPONENT_NAME = "ReactIconsSpriteIcon";
54
- const FONTAWESOME_REACT_PACK = "@fortawesome/react-fontawesome";
55
- const HUGEICONS_REACT_PACK = "@hugeicons/react";
56
- const isFontAwesomeIconPack = (pack) => {
57
- return /^@fortawesome\/[\w-]+-svg-icons$/.test(pack);
58
- };
59
- const isHugeiconsIconPack = (pack) => {
60
- return /^@hugeicons\/core-free-icons(?:\/.*)?$/.test(pack);
61
- };
62
- const sourceMatches = (source, pack) => {
63
- source.lastIndex = 0;
64
- return source.test(pack);
65
- };
66
- const IMPORT_RE = /import\s+([^;]+?)\s+from\s+(['"])([^'"]+)\2\s*;?/g;
67
- const trimRange = (value, offset) => {
68
- let start = 0;
69
- let end = value.length;
70
- while (start < end && /\s/.test(value[start])) start += 1;
71
- while (end > start && /\s/.test(value[end - 1])) end -= 1;
72
- return start === end ? null : [offset + start, offset + end];
73
- };
74
- const parseNamedSpecifiers = (specifier, specifierOffset, imports) => {
75
- const start = specifier.indexOf("{");
76
- const end = specifier.lastIndexOf("}");
77
- if (start === -1 || end === -1 || end <= start) return;
78
- let segmentStart = start + 1;
79
- for (let index = start + 1; index <= end; index += 1) {
80
- if (index !== end && specifier[index] !== ",") continue;
81
- const segment = specifier.slice(segmentStart, index);
82
- const range = trimRange(segment, specifierOffset + segmentStart);
83
- segmentStart = index + 1;
84
- if (!range) continue;
85
- const text = specifier.slice(range[0] - specifierOffset, range[1] - specifierOffset);
86
- if (text.startsWith("type ")) continue;
87
- const aliasMatch = /^(.*?)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(text);
88
- const exportName = aliasMatch ? aliasMatch[1].trim() : text.trim();
89
- const local = aliasMatch ? aliasMatch[2] : exportName;
90
- if (exportName && local) imports.push({
91
- local,
92
- exportName,
93
- range
94
- });
95
- }
96
- };
97
- const parseImportSpecifiers = (specifier, specifierOffset) => {
98
- const specifiers = [];
99
- const braceStart = specifier.indexOf("{");
100
- const defaultText = (braceStart === -1 ? specifier : specifier.slice(0, braceStart)).replace(/,\s*$/, "");
101
- const defaultRange = trimRange(defaultText, specifierOffset);
102
- if (defaultRange && !specifier.slice(defaultRange[0] - specifierOffset, defaultRange[1] - specifierOffset).startsWith("type ")) {
103
- const local = specifier.slice(defaultRange[0] - specifierOffset, defaultRange[1] - specifierOffset);
104
- if (local && /^[A-Za-z_$][\w$]*$/.test(local)) specifiers.push({
105
- local,
106
- exportName: "default",
107
- range: defaultRange
108
- });
109
- }
110
- parseNamedSpecifiers(specifier, specifierOffset, specifiers);
111
- return specifiers;
112
- };
113
- const countImportSpecifiers = (specifier) => {
114
- let count = 0;
115
- const braceStart = specifier.indexOf("{");
116
- if ((braceStart === -1 ? specifier : specifier.slice(0, braceStart)).replace(/,\s*$/, "").trim()) count += 1;
117
- const braceEnd = specifier.lastIndexOf("}");
118
- if (braceStart !== -1 && braceEnd > braceStart) {
119
- const named = specifier.slice(braceStart + 1, braceEnd);
120
- for (const segment of named.split(",")) if (segment.trim()) count += 1;
121
- }
122
- return count;
123
- };
124
- const scanImportsDetailed = (code, sources) => {
125
- const imports = [];
126
- IMPORT_RE.lastIndex = 0;
127
- for (const match of code.matchAll(IMPORT_RE)) {
128
- const [statement, specifier, , pack] = match;
129
- if (!sources.some((source) => sourceMatches(source, pack))) continue;
130
- if (specifier.trim().startsWith("type ")) continue;
131
- const matchStart = match.index;
132
- const specifierOffset = matchStart + statement.indexOf(specifier);
133
- const specifiers = parseImportSpecifiers(specifier, specifierOffset);
134
- if (specifiers.length) imports.push({
135
- pack,
136
- declarationRange: [matchStart, matchStart + statement.length],
137
- specifierRange: [specifierOffset, specifierOffset + specifier.length],
138
- specifiers,
139
- specifierCount: countImportSpecifiers(specifier)
140
- });
141
- }
142
- return imports;
143
- };
144
- const buildScannedSymbolTable = (imports) => {
145
- const items = Object.create(null);
146
- let size = 0;
147
- for (const item of imports) {
148
- const iconIdPrefix = `ri-${normalizePackAlias(item.pack)}-`;
149
- for (const specifier of item.specifiers) {
150
- if (items[specifier.local] === void 0) size += 1;
151
- items[specifier.local] = {
152
- pack: item.pack,
153
- exportName: specifier.exportName,
154
- iconId: iconIdPrefix + specifier.exportName
155
- };
156
- }
157
- }
158
- return {
159
- items,
160
- size
161
- };
162
- };
163
- const scanSpriteIconImport = (code) => {
164
- IMPORT_RE.lastIndex = 0;
165
- for (const match of code.matchAll(IMPORT_RE)) {
166
- const [, specifier, , source] = match;
167
- if (source !== "react-icons-sprite") continue;
168
- const specifiers = [];
169
- parseNamedSpecifiers(specifier, match.index + match[0].indexOf(specifier), specifiers);
170
- for (const item of specifiers) if (item.exportName === "ReactIconsSpriteIcon") return {
171
- hasImport: true,
172
- localName: item.local
173
- };
174
- }
175
- return {
176
- hasImport: false,
177
- localName: ICON_COMPONENT_NAME
178
- };
179
- };
180
- const escapeRegExp = (value) => {
181
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
182
- };
183
- const isIdentifierStart = (char) => {
184
- return char !== void 0 && (char >= "A" && char <= "Z" || char >= "a" && char <= "z" || char === "_" || char === "$");
185
- };
186
- const isIdentifierPart = (char) => {
187
- return char !== void 0 && (isIdentifierStart(char) || char >= "0" && char <= "9");
188
- };
189
- const isWhitespace = (char) => {
190
- return char === " " || char === " " || char === "\n" || char === "\r" || char === "\f";
191
- };
192
- const isWhitespaceCode = (code) => {
193
- return code === 32 || code === 9 || code === 10 || code === 13 || code === 12;
194
- };
195
- const scanJsxIconEdits = (code, symbols, componentName, usedSymbols, register, hasAnyIconId) => {
196
- if (symbols.size === 0) return {
197
- edits: [],
198
- count: 0
199
- };
200
- const edits = [];
201
- let count = 0;
202
- for (let index = code.indexOf("<"); index !== -1; index = code.indexOf("<", index + 1)) {
203
- let cursor = index + 1;
204
- while (isWhitespace(code[cursor])) cursor += 1;
205
- const closing = code[cursor] === "/";
206
- if (closing) {
207
- cursor += 1;
208
- while (isWhitespace(code[cursor])) cursor += 1;
209
- }
210
- if (!isIdentifierStart(code[cursor])) continue;
211
- const localStart = cursor;
212
- cursor += 1;
213
- while (isIdentifierPart(code[cursor])) cursor += 1;
214
- const local = code.slice(localStart, cursor);
215
- const symbol = symbols.items[local];
216
- if (!symbol) continue;
217
- let hasIconId = false;
218
- if (hasAnyIconId && !closing) {
219
- const tagEnd = findJsxOpeningTagEnd(code, cursor);
220
- hasIconId = tagEnd !== -1 && hasIconIdAttribute(code, cursor, tagEnd);
221
- }
222
- if (closing || hasIconId) edits.push({
223
- type: "replace",
224
- from: localStart,
225
- to: cursor,
226
- value: componentName
227
- });
228
- else edits.push({
229
- type: "replace",
230
- from: localStart,
231
- to: cursor,
232
- value: `${componentName} iconId="${symbol.iconId}"`
233
- });
234
- count += 1;
235
- if (!closing) {
236
- usedSymbols.add(local);
237
- register(symbol.pack, symbol.exportName);
238
- }
239
- }
240
- return {
241
- edits,
242
- count
243
- };
244
- };
245
- const scanJsxReferencePropEdits = (code, symbols, componentName, usedSymbols, register, ignoredComponentLocals) => {
246
- if (symbols.size === 0) return {
247
- edits: [],
248
- count: 0
249
- };
250
- const edits = [];
251
- let count = 0;
252
- for (let index = code.indexOf("<"); index !== -1; index = code.indexOf("<", index + 1)) {
253
- let cursor = index + 1;
254
- while (isWhitespace(code[cursor])) cursor += 1;
255
- if (code[cursor] === "/") continue;
256
- if (!isIdentifierStart(code[cursor])) continue;
257
- const componentStart = cursor;
258
- cursor += 1;
259
- while (isIdentifierPart(code[cursor])) cursor += 1;
260
- const componentLocal = code.slice(componentStart, cursor);
261
- if (symbols.items[componentLocal] || ignoredComponentLocals.has(componentLocal)) continue;
262
- const tagEnd = findJsxOpeningTagEnd(code, cursor);
263
- if (tagEnd === -1) continue;
264
- let attributeCursor = cursor;
265
- while (attributeCursor < tagEnd) {
266
- while (isWhitespace(code[attributeCursor])) attributeCursor += 1;
267
- if (attributeCursor >= tagEnd || code[attributeCursor] === "/") break;
268
- if (code[attributeCursor] === "{") {
269
- const expressionEnd = findJsxExpressionEnd(code, attributeCursor);
270
- attributeCursor = expressionEnd === -1 ? tagEnd : expressionEnd + 1;
271
- continue;
272
- }
273
- if (!isIdentifierStart(code[attributeCursor])) {
274
- attributeCursor += 1;
275
- continue;
276
- }
277
- attributeCursor += 1;
278
- while (isJsxAttributeNamePart(code[attributeCursor])) attributeCursor += 1;
279
- while (isWhitespace(code[attributeCursor])) attributeCursor += 1;
280
- if (code[attributeCursor] !== "=") continue;
281
- attributeCursor += 1;
282
- while (isWhitespace(code[attributeCursor])) attributeCursor += 1;
283
- const valueStart = attributeCursor;
284
- if (code[valueStart] === "\"" || code[valueStart] === "'") {
285
- attributeCursor = skipQuotedString(code, valueStart);
286
- continue;
287
- }
288
- if (code[valueStart] !== "{") continue;
289
- const expressionEnd = findJsxExpressionEnd(code, valueStart);
290
- if (expressionEnd === -1 || expressionEnd > tagEnd) {
291
- attributeCursor = tagEnd;
292
- continue;
293
- }
294
- attributeCursor = expressionEnd + 1;
295
- const expression = code.slice(valueStart + 1, expressionEnd);
296
- const iconMatch = /^(\s*)([A-Za-z_$][\w$]*)(\s*)$/.exec(expression);
297
- if (!iconMatch) continue;
298
- const iconLocal = iconMatch[2];
299
- const symbol = symbols.items[iconLocal];
300
- if (!symbol || isFontAwesomeIconPack(symbol.pack) || isHugeiconsIconPack(symbol.pack)) continue;
301
- const iconLocalStart = valueStart + 1 + iconMatch[1].length;
302
- edits.push({
303
- type: "replace",
304
- from: iconLocalStart,
305
- to: iconLocalStart + iconLocal.length,
306
- value: `(props) => <${componentName} {...props} iconId="${symbol.iconId}" />`
307
- });
308
- count += 1;
309
- usedSymbols.add(iconLocal);
310
- register(symbol.pack, symbol.exportName);
311
- }
312
- }
313
- return {
314
- edits,
315
- count
316
- };
317
- };
318
- const isJsxAttributeNamePart = (char) => {
319
- return isIdentifierPart(char) || char === "-" || char === "." || char === ":";
320
- };
321
- const skipQuotedString = (code, start) => {
322
- const quote = code[start];
323
- let cursor = start + 1;
324
- while (cursor < code.length) {
325
- if (code[cursor] === "\\") {
326
- cursor += 2;
327
- continue;
328
- }
329
- if (code[cursor] === quote) return cursor + 1;
330
- cursor += 1;
331
- }
332
- return code.length;
333
- };
334
- const findJsxExpressionEnd = (code, start) => {
335
- let quote = null;
336
- let braceDepth = 0;
337
- for (let index = start; index < code.length; index += 1) {
338
- const char = code[index];
339
- if (quote) {
340
- if (char === "\\") {
341
- index += 1;
342
- continue;
343
- }
344
- if (char === quote) quote = null;
345
- continue;
346
- }
347
- if (char === "\"" || char === "'" || char === "`") {
348
- quote = char;
349
- continue;
350
- }
351
- if (char === "{") {
352
- braceDepth += 1;
353
- continue;
354
- }
355
- if (char === "}") {
356
- braceDepth -= 1;
357
- if (braceDepth === 0) return index;
358
- }
359
- }
360
- return -1;
361
- };
362
- const hasIconIdAttribute = (code, start, end) => {
363
- for (let index = start; index < end; index += 1) {
364
- if (code.charCodeAt(index) !== 105 || code.charCodeAt(index + 1) !== 99 || code.charCodeAt(index + 2) !== 111 || code.charCodeAt(index + 3) !== 110 || code.charCodeAt(index + 4) !== 73 || code.charCodeAt(index + 5) !== 100) continue;
365
- if (isIdentifierPart(code[index - 1]) || isIdentifierPart(code[index + 6])) continue;
366
- let cursor = index + 6;
367
- while (isWhitespace(code[cursor])) cursor += 1;
368
- if (code[cursor] === "=") return true;
369
- }
370
- return false;
371
- };
372
- const findJsxOpeningTagEnd = (code, start) => {
373
- let quote = null;
374
- let braceDepth = 0;
375
- for (let index = start; index < code.length; index += 1) {
376
- const char = code[index];
377
- if (quote) {
378
- if (char === quote && code[index - 1] !== "\\") quote = null;
379
- continue;
380
- }
381
- if (char === "\"" || char === "'" || char === "`") {
382
- quote = char;
383
- continue;
384
- }
385
- if (char === "{") {
386
- braceDepth += 1;
387
- continue;
388
- }
389
- if (char === "}") {
390
- braceDepth -= 1;
391
- continue;
392
- }
393
- if (char === ">" && braceDepth === 0) return index;
394
- }
395
- return -1;
396
- };
397
- const scanFontAwesomeComponents = (code) => {
398
- const locals = /* @__PURE__ */ new Set();
399
- IMPORT_RE.lastIndex = 0;
400
- for (const match of code.matchAll(IMPORT_RE)) {
401
- const [, specifier, , source] = match;
402
- if (source !== FONTAWESOME_REACT_PACK) continue;
403
- const specifiers = [];
404
- parseNamedSpecifiers(specifier, match.index + match[0].indexOf(specifier), specifiers);
405
- for (const item of specifiers) if (item.exportName === "FontAwesomeIcon") locals.add(item.local);
406
- }
407
- return locals;
408
- };
409
- const scanFontAwesomeUsages = (code, symbols, componentLocals) => {
410
- if (!componentLocals.size) return [];
411
- const names = [...componentLocals].map(escapeRegExp).join("|");
412
- const tagRe = new RegExp(`<\\s*(${names})\\b`, "g");
413
- const usages = [];
414
- for (const match of code.matchAll(tagRe)) {
415
- const componentLocal = match[1];
416
- const componentStart = match.index + match[0].lastIndexOf(componentLocal);
417
- const tagEnd = findJsxOpeningTagEnd(code, componentStart + componentLocal.length);
418
- if (tagEnd === -1) continue;
419
- const attributes = code.slice(componentStart + componentLocal.length, tagEnd);
420
- const iconMatch = /\sicon\s*=\s*\{\s*([A-Za-z_$][\w$]*)\s*\}/.exec(attributes);
421
- if (!iconMatch || iconMatch.index === void 0) continue;
422
- const symbol = symbols.items[iconMatch[1]];
423
- if (!symbol || !isFontAwesomeIconPack(symbol.pack)) continue;
424
- const attributeStart = componentStart + componentLocal.length + iconMatch.index;
425
- usages.push({
426
- componentLocal,
427
- componentRange: [componentStart, componentStart + componentLocal.length],
428
- iconAttributeRange: [attributeStart, attributeStart + iconMatch[0].length],
429
- hasIconId: /\biconId\s*=/.test(attributes),
430
- iconLocal: iconMatch[1],
431
- pack: symbol.pack,
432
- exportName: symbol.exportName,
433
- iconId: symbol.iconId
434
- });
435
- }
436
- return usages;
437
- };
438
- const scanHugeiconsComponents = (code) => {
439
- const locals = /* @__PURE__ */ new Set();
440
- IMPORT_RE.lastIndex = 0;
441
- for (const match of code.matchAll(IMPORT_RE)) {
442
- const [, specifier, , source] = match;
443
- if (source !== HUGEICONS_REACT_PACK) continue;
444
- const specifiers = [];
445
- parseNamedSpecifiers(specifier, match.index + match[0].indexOf(specifier), specifiers);
446
- for (const item of specifiers) if (item.exportName === "HugeiconsIcon") locals.add(item.local);
447
- }
448
- return locals;
449
- };
450
- const scanHugeiconsUsages = (code, symbols, componentLocals) => {
451
- if (!componentLocals.size) return [];
452
- const names = [...componentLocals].map(escapeRegExp).join("|");
453
- const tagRe = new RegExp(`<\\s*(${names})\\b`, "g");
454
- const usages = [];
455
- for (const match of code.matchAll(tagRe)) {
456
- const componentLocal = match[1];
457
- const componentStart = match.index + match[0].lastIndexOf(componentLocal);
458
- const tagEnd = findJsxOpeningTagEnd(code, componentStart + componentLocal.length);
459
- if (tagEnd === -1) continue;
460
- const attributes = code.slice(componentStart + componentLocal.length, tagEnd);
461
- const iconMatch = /\sicon\s*=\s*\{\s*([A-Za-z_$][\w$]*)\s*\}/.exec(attributes);
462
- if (!iconMatch || iconMatch.index === void 0) continue;
463
- const symbol = symbols.items[iconMatch[1]];
464
- if (!symbol || !isHugeiconsIconPack(symbol.pack)) continue;
465
- const attributeStart = componentStart + componentLocal.length + iconMatch.index;
466
- usages.push({
467
- componentLocal,
468
- componentRange: [componentStart, componentStart + componentLocal.length],
469
- iconAttributeRange: [attributeStart, attributeStart + iconMatch[0].length],
470
- iconLocal: iconMatch[1],
471
- pack: symbol.pack,
472
- exportName: symbol.exportName,
473
- iconId: symbol.iconId,
474
- hasIconId: /\biconId\s*=/.test(attributes)
475
- });
476
- }
477
- return usages;
478
- };
479
- const cleanupScannedImports = (code, imports, usedLocals) => {
480
- const edits = [];
481
- for (const item of imports) {
482
- const usedSpecifiers = item.specifiers.filter((specifier) => usedLocals.has(specifier.local));
483
- if (!usedSpecifiers.length) continue;
484
- if (usedSpecifiers.length === item.specifiers.length && item.specifierCount === item.specifiers.length) {
485
- edits.push({
486
- type: "remove",
487
- from: item.declarationRange[0],
488
- to: extendToLineEnd(code, item.declarationRange[1])
489
- });
490
- continue;
491
- }
492
- const edit = removeImportSpecifiers(code, item, usedLocals);
493
- if (edit) edits.push(edit);
494
- }
495
- return edits;
496
- };
497
- const removeImportSpecifiers = (code, item, usedLocals) => {
498
- const specifierText = code.slice(item.specifierRange[0], item.specifierRange[1]);
499
- const braceStart = specifierText.indexOf("{");
500
- const braceEnd = specifierText.lastIndexOf("}");
501
- const defaultSpecifier = item.specifiers.find((specifier) => specifier.exportName === "default");
502
- const defaultLocal = (braceStart === -1 ? specifierText : specifierText.slice(0, braceStart)).replace(/,\s*$/, "").trim();
503
- const parts = [];
504
- if (defaultLocal && (!defaultSpecifier || !usedLocals.has(defaultSpecifier.local))) parts.push(defaultLocal);
505
- if (braceStart !== -1 && braceEnd > braceStart) {
506
- const namedSpecifiers = collectKeptNamedSpecifiers(specifierText.slice(braceStart + 1, braceEnd), usedLocals);
507
- if (namedSpecifiers.length) parts.push(`{ ${namedSpecifiers.join(", ")} }`);
508
- }
509
- if (!parts.length) return {
510
- type: "remove",
511
- from: item.declarationRange[0],
512
- to: extendToLineEnd(code, item.declarationRange[1])
513
- };
514
- return {
515
- type: "replace",
516
- from: item.specifierRange[0],
517
- to: item.specifierRange[1],
518
- value: parts.join(", ")
519
- };
520
- };
521
- const collectKeptNamedSpecifiers = (namedText, usedLocals) => {
522
- const kept = [];
523
- let segmentStart = 0;
524
- for (let index = 0; index <= namedText.length; index += 1) {
525
- if (index !== namedText.length && namedText[index] !== ",") continue;
526
- const text = namedText.slice(segmentStart, index).trim();
527
- segmentStart = index + 1;
528
- if (!text) continue;
529
- if (text.startsWith("type ")) {
530
- kept.push(text);
531
- continue;
532
- }
533
- const aliasMatch = /^(.*?)\s+as\s+([A-Za-z_$][\w$]*)$/.exec(text);
534
- const local = aliasMatch ? aliasMatch[2] : text;
535
- if (!usedLocals.has(local)) kept.push(text);
536
- }
537
- return kept;
538
- };
539
- const cleanupScannedFontAwesomeComponentImports = (code, usedLocals) => {
540
- const edits = [];
541
- IMPORT_RE.lastIndex = 0;
542
- for (const match of code.matchAll(IMPORT_RE)) {
543
- const [statement, specifier, , source] = match;
544
- if (source !== FONTAWESOME_REACT_PACK) continue;
545
- const specifierOffset = match.index + statement.indexOf(specifier);
546
- const specifiers = parseImportSpecifiers(specifier, specifierOffset);
547
- const removableSpecifiers = specifiers.filter((item) => item.exportName === "FontAwesomeIcon" && usedLocals.has(item.local));
548
- if (!removableSpecifiers.length) continue;
549
- const edit = removeImportSpecifiers(code, {
550
- pack: source,
551
- declarationRange: [match.index, match.index + statement.length],
552
- specifierRange: [specifierOffset, specifierOffset + specifier.length],
553
- specifiers,
554
- specifierCount: countImportSpecifiers(specifier)
555
- }, new Set(removableSpecifiers.map((item) => item.local)));
556
- if (edit) edits.push(edit);
557
- }
558
- return edits;
559
- };
560
- const cleanupScannedHugeiconsComponentImports = (code, usedLocals) => {
561
- const edits = [];
562
- IMPORT_RE.lastIndex = 0;
563
- for (const match of code.matchAll(IMPORT_RE)) {
564
- const [statement, specifier, , source] = match;
565
- if (source !== HUGEICONS_REACT_PACK) continue;
566
- const specifierOffset = match.index + statement.indexOf(specifier);
567
- const specifiers = parseImportSpecifiers(specifier, specifierOffset);
568
- const removableSpecifiers = specifiers.filter((item) => item.exportName === "HugeiconsIcon" && usedLocals.has(item.local));
569
- if (!removableSpecifiers.length) continue;
570
- const edit = removeImportSpecifiers(code, {
571
- pack: source,
572
- declarationRange: [match.index, match.index + statement.length],
573
- specifierRange: [specifierOffset, specifierOffset + specifier.length],
574
- specifiers,
575
- specifierCount: countImportSpecifiers(specifier)
576
- }, new Set(removableSpecifiers.map((item) => item.local)));
577
- if (edit) edits.push(edit);
578
- }
579
- return edits;
580
- };
581
- const extendToLineEnd = (code, end) => {
582
- let to = end;
583
- while (to < code.length) {
584
- const char = code.charCodeAt(to);
585
- if (char !== 32 && char !== 9) break;
586
- to += 1;
587
- }
588
- if (code[to] === "\r" && code[to + 1] === "\n") return to + 2;
589
- if (code[to] === "\n") return to + 1;
590
- return to;
591
- };
592
- const consumeTrailingWhitespace = (code, start) => {
593
- let to = start;
594
- while (to < code.length && isWhitespaceCode(code.charCodeAt(to))) to += 1;
595
- return to;
596
- };
597
- const editRange = (edit) => {
598
- if (edit.type === "insert") return null;
599
- return [edit.from, edit.to];
600
- };
601
- const rangeContains = ([start, end], index) => {
602
- return index >= start && index < end;
603
- };
604
- const findPreviousJsxTextBoundary = (code, start) => {
605
- let index = start - 1;
606
- while (index >= 0) {
607
- const char = code[index];
608
- if (char === ">" || char === "<" || char === "{" || char === "}") return index;
609
- index -= 1;
610
- }
611
- return -1;
612
- };
613
- const findNextJsxTextBoundary = (code, start) => {
614
- let index = start;
615
- while (index < code.length) {
616
- const char = code[index];
617
- if (char === "<" || char === "{") return index;
618
- index += 1;
619
- }
620
- return -1;
621
- };
622
- const isLikelyJsxTagStart = (code, index) => {
623
- const next = code[index + 1];
624
- return next === "/" || next === ">" || isIdentifierStart(next);
625
- };
626
- const isLikelyJsxTextIdentifier = (code, start, end) => {
627
- const previousBoundary = findPreviousJsxTextBoundary(code, start);
628
- if (previousBoundary === -1 || code[previousBoundary] !== ">" && code[previousBoundary] !== "}") return false;
629
- const nextBoundary = findNextJsxTextBoundary(code, end);
630
- if (nextBoundary === -1) return false;
631
- if (code[nextBoundary] === "<" && !isLikelyJsxTagStart(code, nextBoundary)) return false;
632
- const text = code.slice(previousBoundary + 1, nextBoundary);
633
- return !/[=()[\]{};,+*/%|?]/.test(text);
634
- };
635
- const collectUsedLocalsOutsideRanges = (code, usedLocals, ignoredRanges) => {
636
- const ranges = [...ignoredRanges].sort((left, right) => left[0] - right[0]);
637
- const found = /* @__PURE__ */ new Set();
638
- let rangeIndex = 0;
639
- let quote = null;
640
- let template = false;
641
- let templateExpressionDepth = 0;
642
- let lineComment = false;
643
- let blockComment = false;
644
- for (let index = 0; index < code.length; index += 1) {
645
- while (rangeIndex < ranges.length && ranges[rangeIndex][1] <= index) rangeIndex += 1;
646
- const ignoredRange = ranges[rangeIndex];
647
- if (ignoredRange && rangeContains(ignoredRange, index)) {
648
- index = ignoredRange[1] - 1;
649
- continue;
650
- }
651
- const char = code[index];
652
- const next = code[index + 1];
653
- if (lineComment) {
654
- if (char === "\n" || char === "\r") lineComment = false;
655
- continue;
656
- }
657
- if (blockComment) {
658
- if (char === "*" && next === "/") {
659
- blockComment = false;
660
- index += 1;
661
- }
662
- continue;
663
- }
664
- if (quote) {
665
- if (char === "\\") {
666
- index += 1;
667
- continue;
668
- }
669
- if (char === quote) quote = null;
670
- continue;
671
- }
672
- if (template && templateExpressionDepth === 0) {
673
- if (char === "\\") {
674
- index += 1;
675
- continue;
676
- }
677
- if (char === "`") {
678
- template = false;
679
- continue;
680
- }
681
- if (char === "$" && next === "{") {
682
- templateExpressionDepth = 1;
683
- index += 1;
684
- }
685
- continue;
686
- }
687
- if (char === "/" && next === "/") {
688
- lineComment = true;
689
- index += 1;
690
- continue;
691
- }
692
- if (char === "/" && next === "*") {
693
- blockComment = true;
694
- index += 1;
695
- continue;
696
- }
697
- if (template && templateExpressionDepth > 0) {
698
- if (char === "{") templateExpressionDepth += 1;
699
- if (char === "}") {
700
- templateExpressionDepth -= 1;
701
- if (templateExpressionDepth === 0) continue;
702
- }
703
- }
704
- if (char === "\"" || char === "'") {
705
- quote = char;
706
- continue;
707
- }
708
- if (char === "`") {
709
- template = true;
710
- continue;
711
- }
712
- if (!isIdentifierStart(char)) continue;
713
- const start = index;
714
- index += 1;
715
- while (isIdentifierPart(code[index])) index += 1;
716
- const local = code.slice(start, index);
717
- if (usedLocals.has(local) && !isLikelyJsxTextIdentifier(code, start, index)) found.add(local);
718
- index -= 1;
719
- }
720
- return found;
721
- };
722
- const filterRemovableUsedLocals = (code, imports, usedLocals, usageEdits) => {
723
- const ignoredRanges = [...imports.map((item) => item.declarationRange), ...usageEdits.flatMap((edit) => {
724
- const range = editRange(edit);
725
- return range ? [range] : [];
726
- })];
727
- const liveLocals = collectUsedLocalsOutsideRanges(code, usedLocals, ignoredRanges);
728
- const removable = /* @__PURE__ */ new Set();
729
- for (const local of usedLocals) if (!liveLocals.has(local)) removable.add(local);
730
- return removable;
731
- };
732
- const transformModule = (code, id, register, sources = DEFAULT_ICON_SOURCES, options = {}) => {
733
- const { sourceMap = false } = options;
734
- if (!fastFilter(code)) return {
735
- code,
736
- map: null,
737
- anyReplacements: false
738
- };
739
- const scannedImports = scanImportsDetailed(code, sources);
740
- if (!scannedImports.length) return {
741
- code,
742
- map: null,
743
- anyReplacements: false
744
- };
745
- const hasPotentialFontAwesomeUsage = code.includes(FONTAWESOME_REACT_PACK) && scannedImports.some((item) => isFontAwesomeIconPack(item.pack));
746
- const hasPotentialHugeiconsUsage = code.includes(HUGEICONS_REACT_PACK) && scannedImports.some((item) => isHugeiconsIconPack(item.pack));
747
- const table = buildScannedSymbolTable(scannedImports);
748
- if (!table.size) return {
749
- code,
750
- map: null,
751
- anyReplacements: false
752
- };
753
- const spriteIconImport = code.includes("react-icons-sprite") ? scanSpriteIconImport(code) : {
754
- hasImport: false,
755
- localName: ICON_COMPONENT_NAME
756
- };
757
- const used = /* @__PURE__ */ new Set();
758
- const fontAwesomeComponents = hasPotentialFontAwesomeUsage ? scanFontAwesomeComponents(code) : /* @__PURE__ */ new Set();
759
- const hugeiconsComponents = hasPotentialHugeiconsUsage ? scanHugeiconsComponents(code) : /* @__PURE__ */ new Set();
760
- const usedFontAwesomeComponents = /* @__PURE__ */ new Set();
761
- const usedHugeiconsComponents = /* @__PURE__ */ new Set();
762
- const ignoredIconPropComponentLocals = /* @__PURE__ */ new Set([...fontAwesomeComponents, ...hugeiconsComponents]);
763
- const jsxScan = scanJsxIconEdits(code, table, spriteIconImport.localName, used, register, code.includes("iconId"));
764
- const referencePropScan = scanJsxReferencePropEdits(code, table, spriteIconImport.localName, used, register, ignoredIconPropComponentLocals);
765
- const fontAwesomeUsages = hasPotentialFontAwesomeUsage ? scanFontAwesomeUsages(code, table, fontAwesomeComponents) : [];
766
- const hugeiconsUsages = hasPotentialHugeiconsUsage ? scanHugeiconsUsages(code, table, hugeiconsComponents) : [];
767
- if (jsxScan.count === 0 && referencePropScan.count === 0 && !fontAwesomeUsages.length && !hugeiconsUsages.length) return {
768
- code,
769
- map: null,
770
- anyReplacements: false
771
- };
772
- const edits = [...jsxScan.edits, ...referencePropScan.edits];
773
- const registeredFontAwesomeIcons = /* @__PURE__ */ new Set();
774
- for (const usage of fontAwesomeUsages) {
775
- edits.push({
776
- type: "replace",
777
- from: usage.componentRange[0],
778
- to: usage.componentRange[1],
779
- value: spriteIconImport.localName
780
- });
781
- if (!usage.hasIconId) edits.push({
782
- type: "insert",
783
- pos: usage.componentRange[1],
784
- value: ` iconId="${usage.iconId}"`
785
- });
786
- edits.push({
787
- type: "remove",
788
- from: usage.iconAttributeRange[0],
789
- to: consumeTrailingWhitespace(code, usage.iconAttributeRange[1])
790
- });
791
- used.add(usage.iconLocal);
792
- usedFontAwesomeComponents.add(usage.componentLocal);
793
- const key = `${usage.pack}:${usage.exportName}`;
794
- if (!registeredFontAwesomeIcons.has(key)) {
795
- registeredFontAwesomeIcons.add(key);
796
- register(usage.pack, usage.exportName);
797
- }
798
- }
799
- const registeredHugeiconsIcons = /* @__PURE__ */ new Set();
800
- for (const usage of hugeiconsUsages) {
801
- edits.push({
802
- type: "replace",
803
- from: usage.componentRange[0],
804
- to: usage.componentRange[1],
805
- value: spriteIconImport.localName
806
- });
807
- if (!usage.hasIconId) edits.push({
808
- type: "insert",
809
- pos: usage.componentRange[1],
810
- value: ` iconId="${usage.iconId}"`
811
- });
812
- edits.push({
813
- type: "remove",
814
- from: usage.iconAttributeRange[0],
815
- to: consumeTrailingWhitespace(code, usage.iconAttributeRange[1])
816
- });
817
- used.add(usage.iconLocal);
818
- usedHugeiconsComponents.add(usage.componentLocal);
819
- const key = `${usage.pack}:${usage.exportName}`;
820
- if (!registeredHugeiconsIcons.has(key)) {
821
- registeredHugeiconsIcons.add(key);
822
- register(usage.pack, usage.exportName);
823
- }
824
- }
825
- const removableUsed = filterRemovableUsedLocals(code, scannedImports, used, edits);
826
- const cleanupEdits = cleanupScannedImports(code, scannedImports, removableUsed);
827
- const cleanupFontAwesomeEdits = usedFontAwesomeComponents.size ? cleanupScannedFontAwesomeComponentImports(code, usedFontAwesomeComponents) : [];
828
- const cleanupHugeiconsEdits = usedHugeiconsComponents.size ? cleanupScannedHugeiconsComponentImports(code, usedHugeiconsComponents) : [];
829
- const canUsePresortedEdits = referencePropScan.count === 0 && cleanupFontAwesomeEdits.length === 0 && cleanupHugeiconsEdits.length === 0;
830
- const allEdits = canUsePresortedEdits ? [...cleanupEdits, ...edits] : [
831
- ...edits,
832
- ...cleanupEdits,
833
- ...cleanupFontAwesomeEdits,
834
- ...cleanupHugeiconsEdits
835
- ];
836
- const importPrefix = `import { ${ICON_COMPONENT_NAME} } from "${ICON_SOURCE}";\n`;
837
- if (!sourceMap) return {
838
- code: `${spriteIconImport.hasImport ? "" : importPrefix}${applyEditsToString(code, allEdits, canUsePresortedEdits)}`,
839
- map: null,
840
- anyReplacements: true
841
- };
842
- const magicString = applyEdits(code, allEdits);
843
- if (!spriteIconImport.hasImport) magicString.prepend(importPrefix);
844
- return {
845
- code: magicString.toString(),
846
- map: sourceMap ? magicString.generateMap({
847
- source: id,
848
- hires: true
849
- }) : null,
850
- anyReplacements: true
851
- };
852
- };
853
- //#endregion
854
- export { transformModule as t };