vue3-migration 1.3.0 → 1.3.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/package.json
CHANGED
|
@@ -34,8 +34,9 @@ def extract_all_identifiers(source: str) -> list[str]:
|
|
|
34
34
|
else:
|
|
35
35
|
ids.add(part.split("=")[0].strip())
|
|
36
36
|
|
|
37
|
-
# Return keys: return { foo, bar, baz: val }
|
|
38
|
-
|
|
37
|
+
# Return keys: return { foo, bar, baz: val } — use LAST match to skip nested returns
|
|
38
|
+
ret_matches = list(re.finditer(r"\breturn\s*\{", source))
|
|
39
|
+
ret = ret_matches[-1] if ret_matches else None
|
|
39
40
|
if ret:
|
|
40
41
|
block = extract_brace_block(source, ret.end() - 1)
|
|
41
42
|
ids.update(re.findall(r"\b(\w+)\s*[,}\n:]", block))
|
|
@@ -59,8 +60,9 @@ def extract_return_keys(source: str) -> list[str]:
|
|
|
59
60
|
"null", "undefined", "new", "value",
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
# Case 1: direct return { ... }
|
|
63
|
-
|
|
63
|
+
# Case 1: direct return { ... } — use LAST match to skip nested returns
|
|
64
|
+
matches = list(re.finditer(r"\breturn\s*\{", source))
|
|
65
|
+
ret = matches[-1] if matches else None
|
|
64
66
|
if ret:
|
|
65
67
|
block = extract_brace_block(source, ret.end() - 1)
|
|
66
68
|
keys = re.findall(r"\b(\w+)\b", block)
|
|
@@ -40,24 +40,8 @@ _THIS_DOLLAR_PATTERNS: list[tuple[str, str, str, str]] = [
|
|
|
40
40
|
"this.$refs is not available in composables",
|
|
41
41
|
"Use template refs with ref() instead",
|
|
42
42
|
),
|
|
43
|
-
(
|
|
44
|
-
|
|
45
|
-
"this.$nextTick",
|
|
46
|
-
"this.$nextTick should use the imported nextTick",
|
|
47
|
-
"Import nextTick from 'vue' and call it directly",
|
|
48
|
-
),
|
|
49
|
-
(
|
|
50
|
-
r"this\.\$set\b",
|
|
51
|
-
"this.$set",
|
|
52
|
-
"this.$set is removed in Vue 3",
|
|
53
|
-
"Assign directly — Vue 3 reactivity tracks new properties",
|
|
54
|
-
),
|
|
55
|
-
(
|
|
56
|
-
r"this\.\$delete\b",
|
|
57
|
-
"this.$delete",
|
|
58
|
-
"this.$delete is removed in Vue 3",
|
|
59
|
-
"Use delete operator directly — Vue 3 reactivity tracks deletions",
|
|
60
|
-
),
|
|
43
|
+
# $nextTick, $set, $delete are auto-migrated by rewrite_this_dollar_refs()
|
|
44
|
+
# in this_rewriter.py — no warning needed.
|
|
61
45
|
(
|
|
62
46
|
r"this\.\$on\b",
|
|
63
47
|
"this.$on",
|
|
@@ -305,7 +289,7 @@ def inject_inline_warnings(
|
|
|
305
289
|
Optionally prepends a confidence header comment at the top.
|
|
306
290
|
"""
|
|
307
291
|
if confidence is not None:
|
|
308
|
-
header = f"//
|
|
292
|
+
header = f"// Transformation confidence: {confidence.value} ({warning_count} warnings — see migration report)\n"
|
|
309
293
|
source = header + source
|
|
310
294
|
|
|
311
295
|
unplaced: list[MigrationWarning] = []
|
|
@@ -420,6 +404,26 @@ _MIXIN_OPTION_PATTERNS: list[tuple[str, str, str, str]] = [
|
|
|
420
404
|
]
|
|
421
405
|
|
|
422
406
|
|
|
407
|
+
def _brace_depth_at(source: str, pos: int) -> int:
|
|
408
|
+
"""Count net brace depth from start of *source* to *pos*, skipping non-code."""
|
|
409
|
+
from .js_parser import skip_non_code
|
|
410
|
+
|
|
411
|
+
depth = 0
|
|
412
|
+
i = 0
|
|
413
|
+
while i < pos:
|
|
414
|
+
new_i, skipped = skip_non_code(source, i)
|
|
415
|
+
if skipped:
|
|
416
|
+
i = new_i
|
|
417
|
+
continue
|
|
418
|
+
ch = source[i]
|
|
419
|
+
if ch == '{':
|
|
420
|
+
depth += 1
|
|
421
|
+
elif ch == '}':
|
|
422
|
+
depth -= 1
|
|
423
|
+
i += 1
|
|
424
|
+
return depth
|
|
425
|
+
|
|
426
|
+
|
|
423
427
|
def detect_mixin_options(
|
|
424
428
|
mixin_source: str, mixin_stem: str
|
|
425
429
|
) -> list[MigrationWarning]:
|
|
@@ -447,6 +451,10 @@ def detect_mixin_options(
|
|
|
447
451
|
pos += 1
|
|
448
452
|
if in_non_code:
|
|
449
453
|
continue
|
|
454
|
+
# Mixin options sit at brace depth 1 (inside `export default {}`).
|
|
455
|
+
# Deeper matches (e.g. `filters` inside data()) are not options.
|
|
456
|
+
if _brace_depth_at(mixin_source, m.start()) != 1:
|
|
457
|
+
continue
|
|
450
458
|
warnings.append(MigrationWarning(
|
|
451
459
|
mixin_stem=mixin_stem,
|
|
452
460
|
category=f"mixin-option:{option}",
|
|
@@ -425,16 +425,19 @@ def build_warning_summary(
|
|
|
425
425
|
conf = confidence_map[entry.mixin_stem]
|
|
426
426
|
icon = _CONF_ICON.get(conf, "\u2753")
|
|
427
427
|
|
|
428
|
-
a(f"### {icon} {entry.mixin_stem} \u2014 {conf.value}
|
|
428
|
+
a(f"### {icon} {entry.mixin_stem} \u2014 Transformation confidence: {conf.value}\n")
|
|
429
429
|
|
|
430
430
|
if not entry.warnings:
|
|
431
431
|
a("No manual changes needed.\n")
|
|
432
432
|
continue
|
|
433
433
|
|
|
434
|
+
item_count = len(entry.warnings)
|
|
435
|
+
a(f"> {item_count} item{'s' if item_count != 1 else ''} need{'s' if item_count == 1 else ''} attention\n")
|
|
436
|
+
|
|
434
437
|
for warning in entry.warnings:
|
|
435
438
|
sev_icon = _SEVERITY_ICON.get(warning.severity, "\u2753")
|
|
436
|
-
a(f"-
|
|
437
|
-
a(f"
|
|
438
|
-
|
|
439
|
+
a(f"- {sev_icon} **{warning.category}** ({warning.severity}): {warning.message}")
|
|
440
|
+
a(f"")
|
|
441
|
+
a(f" **\u2192 {warning.action_required}**\n")
|
|
439
442
|
|
|
440
443
|
return "\n".join(lines)
|
|
@@ -18,7 +18,7 @@ from ..core.mixin_analyzer import (
|
|
|
18
18
|
extract_lifecycle_hooks, extract_mixin_members,
|
|
19
19
|
find_external_this_refs,
|
|
20
20
|
)
|
|
21
|
-
from ..core.warning_collector import collect_mixin_warnings
|
|
21
|
+
from ..core.warning_collector import collect_mixin_warnings, detect_name_collisions
|
|
22
22
|
from ..models import (
|
|
23
23
|
ComposableCoverage, FileChange, MigrationConfig, MigrationPlan, MigrationStatus,
|
|
24
24
|
MixinEntry, MixinMembers,
|
|
@@ -336,6 +336,20 @@ def plan_component_injections(
|
|
|
336
336
|
if not ready_entries:
|
|
337
337
|
continue
|
|
338
338
|
|
|
339
|
+
# Detect member name collisions across composables for this component
|
|
340
|
+
if len(ready_entries) > 1:
|
|
341
|
+
composable_members_map = {}
|
|
342
|
+
for entry in ready_entries:
|
|
343
|
+
if entry.composable and entry.classification:
|
|
344
|
+
composable_members_map[entry.composable.fn_name] = list(
|
|
345
|
+
entry.classification.injectable
|
|
346
|
+
)
|
|
347
|
+
collision_warnings = detect_name_collisions(composable_members_map)
|
|
348
|
+
if collision_warnings:
|
|
349
|
+
for w in collision_warnings:
|
|
350
|
+
w.mixin_stem = "cross-composable"
|
|
351
|
+
ready_entries[0].warnings.extend(collision_warnings)
|
|
352
|
+
|
|
339
353
|
content = comp_source
|
|
340
354
|
changes_desc = []
|
|
341
355
|
|
|
@@ -447,6 +461,11 @@ def run_scoped(
|
|
|
447
461
|
|
|
448
462
|
Exactly one of component_path or mixin_stem must be provided.
|
|
449
463
|
No file I/O. Returns a MigrationPlan the CLI can show as a diff and write.
|
|
464
|
+
|
|
465
|
+
Note: When component_path is provided, composable patches only aggregate
|
|
466
|
+
requirements from that component's entries. Shared composables may receive
|
|
467
|
+
incomplete patches. Use mixin_stem scope or full-project run for complete
|
|
468
|
+
composable coverage.
|
|
450
469
|
"""
|
|
451
470
|
if component_path is None and mixin_stem is None:
|
|
452
471
|
raise ValueError("Provide either component_path or mixin_stem")
|