esupgrade 2025.2.0 → 2025.2.1

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esupgrade",
3
- "version": "2025.2.0",
3
+ "version": "2025.2.1",
4
4
  "description": "Auto-upgrade your JavaScript syntax",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -51,26 +51,83 @@ export function concatToTemplateLiteral(j, root) {
51
51
  const parts = []
52
52
  const expressions = []
53
53
 
54
- const flatten = (node) => {
55
- if (j.BinaryExpression.check(node) && node.operator === "+") {
56
- flatten(node.left)
57
- flatten(node.right)
58
- } else if (
54
+ // Helper to check if a node is a string literal
55
+ const isStringLiteral = (node) => {
56
+ return (
59
57
  j.StringLiteral.check(node) ||
60
58
  (j.Literal.check(node) && typeof node.value === "string")
61
- ) {
62
- // Add string literal value
63
- if (parts.length === 0 || expressions.length >= parts.length) {
64
- parts.push(node.value)
65
- } else {
66
- parts[parts.length - 1] += node.value
67
- }
59
+ )
60
+ }
61
+
62
+ // Helper to check if a node contains any string literal
63
+ const containsStringLiteral = (node) => {
64
+ if (isStringLiteral(node)) return true
65
+ if (j.BinaryExpression.check(node) && node.operator === "+") {
66
+ return containsStringLiteral(node.left) || containsStringLiteral(node.right)
67
+ }
68
+ return false
69
+ }
70
+
71
+ const addStringPart = (value) => {
72
+ if (parts.length === 0 || expressions.length >= parts.length) {
73
+ parts.push(value)
68
74
  } else {
69
- // Add expression
70
- if (parts.length === 0) {
71
- parts.push("")
75
+ parts[parts.length - 1] += value
76
+ }
77
+ }
78
+
79
+ const addExpression = (expr) => {
80
+ if (parts.length === 0) {
81
+ parts.push("")
82
+ }
83
+ expressions.push(expr)
84
+ }
85
+
86
+ const flatten = (node, stringContext = false) => {
87
+ // Note: node is always a BinaryExpression when called, as non-BinaryExpression
88
+ // nodes are handled inline before recursing into flatten
89
+ if (j.BinaryExpression.check(node) && node.operator === "+") {
90
+ // Check if this entire binary expression contains any string literal
91
+ const hasString = containsStringLiteral(node)
92
+
93
+ if (!hasString && !stringContext) {
94
+ // This is pure numeric addition (no strings anywhere), keep as expression
95
+ addExpression(node)
96
+ } else {
97
+ // This binary expression is part of string concatenation
98
+ // Check each operand
99
+ const leftHasString = containsStringLiteral(node.left)
100
+
101
+ // Process left side
102
+ if (j.BinaryExpression.check(node.left) && node.left.operator === "+") {
103
+ // Left is also a + expression - recurse
104
+ flatten(node.left, stringContext)
105
+ } else if (isStringLiteral(node.left)) {
106
+ // Left is a string literal
107
+ addStringPart(node.left.value)
108
+ } else {
109
+ // Left is some other expression
110
+ addExpression(node.left)
111
+ }
112
+
113
+ // Process right side - it's in string context if left had a string
114
+ const rightInStringContext = stringContext || leftHasString
115
+ if (j.BinaryExpression.check(node.right) && node.right.operator === "+") {
116
+ // If right is a + expression with no strings and we're in string context, keep it as a unit
117
+ if (!containsStringLiteral(node.right) && rightInStringContext) {
118
+ addExpression(node.right)
119
+ } else {
120
+ // Right has strings or we need to flatten it
121
+ flatten(node.right, rightInStringContext)
122
+ }
123
+ } else if (isStringLiteral(node.right)) {
124
+ // Right is a string literal
125
+ addStringPart(node.right.value)
126
+ } else {
127
+ // Right is some other expression
128
+ addExpression(node.right)
129
+ }
72
130
  }
73
- expressions.push(node)
74
131
  }
75
132
  }
76
133
 
@@ -480,6 +480,83 @@ var x = 1;`
480
480
  assert.strictEqual(result.modified, true)
481
481
  assert.match(result.code, /`/)
482
482
  })
483
+
484
+ test("numeric addition followed by string concatenation", () => {
485
+ const input = `cal_box.style.left = findPosX(cal_link) + 17 + 'px';`
486
+
487
+ const result = transform(input)
488
+
489
+ assert.strictEqual(result.modified, true)
490
+ // Should treat (findPosX(cal_link) + 17) as a single numeric expression
491
+ assert.match(result.code, /`\$\{findPosX\(cal_link\) \+ 17\}px`/)
492
+ })
493
+
494
+ test("multiple numeric additions followed by string concatenation", () => {
495
+ const input = `const result = a + b + c + 'd';`
496
+
497
+ const result = transform(input)
498
+
499
+ assert.strictEqual(result.modified, true)
500
+ // Should treat (a + b + c) as a single numeric expression
501
+ assert.match(result.code, /`\$\{a \+ b \+ c\}d`/)
502
+ })
503
+
504
+ test("string concatenation followed by numeric addition", () => {
505
+ const input = `const result = 'Value: ' + x + y;`
506
+
507
+ const result = transform(input)
508
+
509
+ assert.strictEqual(result.modified, true)
510
+ // After first string, all subsequent + are string concatenation
511
+ assert.match(result.code, /`Value: \$\{x\}\$\{y\}`/)
512
+ })
513
+
514
+ test("numeric addition in middle of string concatenations", () => {
515
+ const input = `const result = 'start' + (a + b) + 'end';`
516
+
517
+ const result = transform(input)
518
+
519
+ assert.strictEqual(result.modified, true)
520
+ // Parenthesized numeric expression should be preserved
521
+ // jscodeshift may add parentheses around binary expressions in template literals
522
+ assert.match(result.code, /`start\$\{(\()?a \+ b(\))?\}end`/)
523
+ })
524
+
525
+ test("consecutive string literals should be merged", () => {
526
+ const input = `const msg = 'Hello' + ' ' + 'world';`
527
+
528
+ const result = transform(input)
529
+
530
+ assert.strictEqual(result.modified, true)
531
+ assert.match(result.code, /`Hello world`/)
532
+ })
533
+
534
+ test("string literal followed by non-binary expression", () => {
535
+ const input = `const msg = 'Value: ' + getValue();`
536
+
537
+ const result = transform(input)
538
+
539
+ assert.strictEqual(result.modified, true)
540
+ assert.match(result.code, /`Value: \$\{getValue\(\)\}`/)
541
+ })
542
+
543
+ test("expression followed by string literal", () => {
544
+ const input = `const msg = getValue() + ' is the value';`
545
+
546
+ const result = transform(input)
547
+
548
+ assert.strictEqual(result.modified, true)
549
+ assert.match(result.code, /`\$\{getValue\(\)\} is the value`/)
550
+ })
551
+
552
+ test("non-binary expression in the middle", () => {
553
+ const input = `const msg = 'start' + getValue() + 'end';`
554
+
555
+ const result = transform(input)
556
+
557
+ assert.strictEqual(result.modified, true)
558
+ assert.match(result.code, /`start\$\{getValue\(\)\}end`/)
559
+ })
483
560
  })
484
561
 
485
562
  describe("object spread", () => {