@teleporthq/teleport-plugin-common 0.43.44 → 0.43.47

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.
Files changed (38) hide show
  1. package/__tests__/utils/parse-string-with-template-expressions.ts +82 -0
  2. package/__tests__/utils/route-utils.ts +118 -0
  3. package/__tests__/utils/template-expression-balance.ts +83 -0
  4. package/dist/cjs/node-handlers/node-to-jsx/utils.d.ts.map +1 -1
  5. package/dist/cjs/node-handlers/node-to-jsx/utils.js +30 -7
  6. package/dist/cjs/node-handlers/node-to-jsx/utils.js.map +1 -1
  7. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  8. package/dist/cjs/utils/ast-utils.d.ts.map +1 -1
  9. package/dist/cjs/utils/ast-utils.js +65 -16
  10. package/dist/cjs/utils/ast-utils.js.map +1 -1
  11. package/dist/cjs/utils/route-utils.d.ts +10 -0
  12. package/dist/cjs/utils/route-utils.d.ts.map +1 -1
  13. package/dist/cjs/utils/route-utils.js +9 -1
  14. package/dist/cjs/utils/route-utils.js.map +1 -1
  15. package/dist/cjs/utils/template-expression-balance.d.ts +58 -0
  16. package/dist/cjs/utils/template-expression-balance.d.ts.map +1 -0
  17. package/dist/cjs/utils/template-expression-balance.js +181 -0
  18. package/dist/cjs/utils/template-expression-balance.js.map +1 -0
  19. package/dist/esm/node-handlers/node-to-jsx/utils.d.ts.map +1 -1
  20. package/dist/esm/node-handlers/node-to-jsx/utils.js +31 -8
  21. package/dist/esm/node-handlers/node-to-jsx/utils.js.map +1 -1
  22. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  23. package/dist/esm/utils/ast-utils.d.ts.map +1 -1
  24. package/dist/esm/utils/ast-utils.js +66 -17
  25. package/dist/esm/utils/ast-utils.js.map +1 -1
  26. package/dist/esm/utils/route-utils.d.ts +10 -0
  27. package/dist/esm/utils/route-utils.d.ts.map +1 -1
  28. package/dist/esm/utils/route-utils.js +8 -0
  29. package/dist/esm/utils/route-utils.js.map +1 -1
  30. package/dist/esm/utils/template-expression-balance.d.ts +58 -0
  31. package/dist/esm/utils/template-expression-balance.d.ts.map +1 -0
  32. package/dist/esm/utils/template-expression-balance.js +174 -0
  33. package/dist/esm/utils/template-expression-balance.js.map +1 -0
  34. package/package.json +4 -4
  35. package/src/node-handlers/node-to-jsx/utils.ts +36 -10
  36. package/src/utils/ast-utils.ts +68 -17
  37. package/src/utils/route-utils.ts +9 -0
  38. package/src/utils/template-expression-balance.ts +196 -0
@@ -1 +1 @@
1
- {"version":3,"file":"route-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/route-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAE1D;;;GAGG;AACH,eAAO,MAAM,cAAc;;qBACM,MAAM,EAAE;mBAAa,MAAM;;MACxD,OAKH,CAAA;AAID;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,gCAAgC;WAElC,MAAM;;iBAEF,MAAM;mBACJ,MAAM;;wBACe,MAAM;;;eAG9B,aAAa,KACxB,OAiCF,CAAA"}
1
+ {"version":3,"file":"route-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/route-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAG1D;;;GAGG;AACH,eAAO,MAAM,cAAc;;qBACM,MAAM,EAAE;mBAAa,MAAM;;MACxD,OAKH,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAQ,qBAAqB,6BAAE,wBAAwB;;;CAAe,CAAA;AAI7E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,gCAAgC;WAElC,MAAM;;iBAEF,MAAM;mBACJ,MAAM;;wBACe,MAAM;;;eAG9B,aAAa,KACxB,OAiCF,CAAA"}
@@ -7,6 +7,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
7
7
  }
8
8
  return to.concat(ar || Array.prototype.slice.call(from));
9
9
  };
10
+ import { RoutePaths } from '@teleporthq/teleport-shared';
10
11
  /**
11
12
  * True when this UIDL component's output route contains a Next.js dynamic
12
13
  * segment (`[id]`, `[slug]`, ...) anywhere in its folder path or file name.
@@ -15,6 +16,13 @@ export var isDynamicRoute = function (uidl) {
15
16
  var _a = uidl.outputOptions || {}, _b = _a.folderPath, folderPath = _b === void 0 ? [] : _b, _c = _a.fileName, fileName = _c === void 0 ? '' : _c;
16
17
  return __spreadArray(__spreadArray([], folderPath, true), [fileName], false).some(function (segment) { return segment.startsWith('[') && segment.endsWith(']'); });
17
18
  };
19
+ /**
20
+ * Reading a route TEMPLATE (`/event-details/[id]`) rather than a URL. Defined in
21
+ * teleport-shared because the navlink resolver needs the same two functions and
22
+ * lives in a package that does not depend on this one; re-exported here so
23
+ * `RouteUtils` stays the single import for route questions in the plugins.
24
+ */
25
+ export var pathHasDynamicSegment = RoutePaths.pathHasDynamicSegment, parseDynamicPathSegments = RoutePaths.parseDynamicPathSegments;
18
26
  var MUTATION_NODE_TYPES = new Set(['data-create-item', 'data-update-item']);
19
27
  /**
20
28
  * True when this page both reads one specific database row via its own
@@ -1 +1 @@
1
- {"version":3,"file":"route-utils.js","sourceRoot":"","sources":["../../../src/utils/route-utils.ts"],"names":[],"mappings":";;;;;;;;;AAEA;;;GAGG;AACH,MAAM,CAAC,IAAM,cAAc,GAAG,UAAC,IAE9B;IACO,IAAA,KAAqC,IAAI,CAAC,aAAa,IAAI,EAAE,EAA3D,kBAAe,EAAf,UAAU,mBAAG,EAAE,KAAA,EAAE,gBAAa,EAAb,QAAQ,mBAAG,EAAE,KAA6B,CAAA;IACnE,OAAO,gCAAI,UAAU,UAAE,QAAQ,UAAE,IAAI,CACnC,UAAC,OAAO,IAAK,OAAA,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAhD,CAAgD,CAC9D,CAAA;AACH,CAAC,CAAA;AAED,IAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAC,CAAA;AAE7E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,IAAM,gCAAgC,GAAG,UAC9C,IAOC,EACD,SAAyB;;IAEzB,IAAM,SAAS,GAAG,MAAA,MAAA,IAAI,CAAC,aAAa,0CAAE,eAAe,0CAAE,SAAS,CAAA;IAChE,IAAI,CAAC,SAAS,IAAI,CAAC,CAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,SAAS,CAAA,EAAE;QACvC,OAAO,KAAK,CAAA;KACb;IAED,IAAM,MAAM,GAAG,CAAA,MAAA,IAAI,CAAC,aAAa,0CAAE,MAAM,MAAI,MAAA,IAAI,CAAC,aAAa,0CAAE,QAAQ,CAAA,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAA;IAE5F,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,UAAC,EAAE;;QAChD,IAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAA;QAC1B,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,KAAK,MAAM,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,EAAE;YACzE,OAAO,KAAK,CAAA;SACb;QAED,wEAAwE;QACxE,yEAAyE;QACzE,0EAA0E;QAC1E,+CAA+C;QAC/C,IAAM,aAAa,GAAG,MAAA,OAAO,CAAC,MAAM,0CAAE,MAA4B,CAAA;QAClE,IAAM,aAAa,GAAG,MAAA,OAAO,CAAC,MAAM,0CAAE,aAAkD,CAAA;QACxF,IAAM,kBAAkB,GACtB,OAAO,CAAC,KAAK,KAAK,MAAM;YACtB,CAAC,CAAC,CAAC,aAAa,IAAI,aAAa,KAAK,MAAM;YAC5C,CAAC,CAAC,CAAC,aAAa,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,EAAE,KAAK,MAAM,EAAf,CAAe,CAAC,CAAA;QAEhG,IAAI,CAAC,kBAAkB,EAAE;YACvB,OAAO,KAAK,CAAA;SACb;QAED,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAClB,UAAC,IAAI,YAAK,OAAA,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAA,MAAA,IAAI,CAAC,MAAM,0CAAE,SAAS,MAAK,SAAS,CAAA,EAAA,CACrF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAA"}
1
+ {"version":3,"file":"route-utils.js","sourceRoot":"","sources":["../../../src/utils/route-utils.ts"],"names":[],"mappings":";;;;;;;;;AACA,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAA;AAExD;;;GAGG;AACH,MAAM,CAAC,IAAM,cAAc,GAAG,UAAC,IAE9B;IACO,IAAA,KAAqC,IAAI,CAAC,aAAa,IAAI,EAAE,EAA3D,kBAAe,EAAf,UAAU,mBAAG,EAAE,KAAA,EAAE,gBAAa,EAAb,QAAQ,mBAAG,EAAE,KAA6B,CAAA;IACnE,OAAO,gCAAI,UAAU,UAAE,QAAQ,UAAE,IAAI,CACnC,UAAC,OAAO,IAAK,OAAA,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAhD,CAAgD,CAC9D,CAAA;AACH,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAS,IAAA,qBAAqB,GAA+B,UAAU,sBAAzC,EAAE,wBAAwB,GAAK,UAAU,yBAAf,CAAe;AAE7E,IAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,kBAAkB,EAAE,kBAAkB,CAAC,CAAC,CAAA;AAE7E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,IAAM,gCAAgC,GAAG,UAC9C,IAOC,EACD,SAAyB;;IAEzB,IAAM,SAAS,GAAG,MAAA,MAAA,IAAI,CAAC,aAAa,0CAAE,eAAe,0CAAE,SAAS,CAAA;IAChE,IAAI,CAAC,SAAS,IAAI,CAAC,CAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,SAAS,CAAA,EAAE;QACvC,OAAO,KAAK,CAAA;KACb;IAED,IAAM,MAAM,GAAG,CAAA,MAAA,IAAI,CAAC,aAAa,0CAAE,MAAM,MAAI,MAAA,IAAI,CAAC,aAAa,0CAAE,QAAQ,CAAA,IAAI,IAAI,CAAC,IAAI,IAAI,EAAE,CAAA;IAE5F,OAAO,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,UAAC,EAAE;;QAChD,IAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAA;QAC1B,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,KAAK,MAAM,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,EAAE;YACzE,OAAO,KAAK,CAAA;SACb;QAED,wEAAwE;QACxE,yEAAyE;QACzE,0EAA0E;QAC1E,+CAA+C;QAC/C,IAAM,aAAa,GAAG,MAAA,OAAO,CAAC,MAAM,0CAAE,MAA4B,CAAA;QAClE,IAAM,aAAa,GAAG,MAAA,OAAO,CAAC,MAAM,0CAAE,aAAkD,CAAA;QACxF,IAAM,kBAAkB,GACtB,OAAO,CAAC,KAAK,KAAK,MAAM;YACtB,CAAC,CAAC,CAAC,aAAa,IAAI,aAAa,KAAK,MAAM;YAC5C,CAAC,CAAC,CAAC,aAAa,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,UAAC,CAAC,IAAK,OAAA,CAAC,CAAC,EAAE,KAAK,MAAM,EAAf,CAAe,CAAC,CAAA;QAEhG,IAAI,CAAC,kBAAkB,EAAE;YACvB,OAAO,KAAK,CAAA;SACb;QAED,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAClB,UAAC,IAAI,YAAK,OAAA,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAA,MAAA,IAAI,CAAC,MAAM,0CAAE,SAAS,MAAK,SAAS,CAAA,EAAA,CACrF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC,CAAA"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Delimiter balancing for `{{ … }}` binding expressions.
3
+ *
4
+ * THE DEFECT THIS EXISTS FOR. `parseStringWithTemplateExpressions` converted a
5
+ * UIDL string into a template literal and then handed the result straight to
6
+ * Babel. When the binding inside was unbalanced the produced source did not
7
+ * parse, `parse()` threw, and — because the inline-style caller has no
8
+ * try/catch — the SyntaxError escaped all the way out and killed the ENTIRE
9
+ * project build:
10
+ *
11
+ * const x = `translateX(${-(cameraX || ''}px) }px)`
12
+ * ^ Unexpected token, expected ","
13
+ *
14
+ * Two separate faults produced that line. The interpolated expression
15
+ * `-(cameraX || ''` was missing its closing paren, and the "re-close the CSS
16
+ * function" step counted parentheses across the WHOLE string — including the
17
+ * ones inside `${…}` — decided one was missing, and appended `px)` to the end,
18
+ * which could never help because the imbalance was inside the interpolation.
19
+ *
20
+ * One bad binding in one style property must never cost a project its build.
21
+ * These helpers make the imbalance repairable where it actually is, and let the
22
+ * caller degrade a single value instead of aborting.
23
+ */
24
+ export interface ExpressionScan {
25
+ /** Openers still waiting for their closer, outermost first. */
26
+ unclosed: string[];
27
+ /** Closers that never had a matching opener. */
28
+ strayCloserCount: number;
29
+ /** The quote character still open at the end of the scan, if any. */
30
+ unterminatedQuote: string | null;
31
+ }
32
+ /**
33
+ * Single left-to-right pass over an expression, quote and escape aware.
34
+ * Characters inside a string literal never affect nesting.
35
+ */
36
+ export declare const scanExpression: (expression: string) => ExpressionScan;
37
+ /** True when every quote and bracket closes and no closer is stray. */
38
+ export declare const isBalancedExpression: (expression: string) => boolean;
39
+ /**
40
+ * Close an expression that ends mid-quote or mid-bracket:
41
+ * `-(cameraX || ''` → `-(cameraX || '')`.
42
+ *
43
+ * Returns the expression unchanged when it is already balanced, and when the
44
+ * imbalance is a STRAY CLOSER — appending cannot fix a missing opener, and
45
+ * inventing one would be a guess.
46
+ */
47
+ export declare const balanceExpression: (expression: string) => string;
48
+ /**
49
+ * Parenthesis balance of the STATIC parts of a template string — everything
50
+ * outside `${…}`.
51
+ *
52
+ * This is the number the "re-close the CSS function" step needs. Counting over
53
+ * the whole string mixes in the expressions' own parentheses, so an imbalance
54
+ * INSIDE an interpolation reads as a missing close on the CSS function and the
55
+ * step appends a unit and a `)` that belong nowhere.
56
+ */
57
+ export declare const countUnclosedStaticParens: (templateStr: string) => number;
58
+ //# sourceMappingURL=template-expression-balance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-expression-balance.d.ts","sourceRoot":"","sources":["../../../src/utils/template-expression-balance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAYH,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,gDAAgD;IAChD,gBAAgB,EAAE,MAAM,CAAA;IACxB,qEAAqE;IACrE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,eAAgB,MAAM,KAAG,cAyCnD,CAAA;AAED,uEAAuE;AACvE,eAAO,MAAM,oBAAoB,eAAgB,MAAM,KAAG,OAKzD,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,eAAgB,MAAM,KAAG,MAYtD,CAAA;AAQD;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,gBAAiB,MAAM,KAAG,MAkB/D,CAAA"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Delimiter balancing for `{{ … }}` binding expressions.
3
+ *
4
+ * THE DEFECT THIS EXISTS FOR. `parseStringWithTemplateExpressions` converted a
5
+ * UIDL string into a template literal and then handed the result straight to
6
+ * Babel. When the binding inside was unbalanced the produced source did not
7
+ * parse, `parse()` threw, and — because the inline-style caller has no
8
+ * try/catch — the SyntaxError escaped all the way out and killed the ENTIRE
9
+ * project build:
10
+ *
11
+ * const x = `translateX(${-(cameraX || ''}px) }px)`
12
+ * ^ Unexpected token, expected ","
13
+ *
14
+ * Two separate faults produced that line. The interpolated expression
15
+ * `-(cameraX || ''` was missing its closing paren, and the "re-close the CSS
16
+ * function" step counted parentheses across the WHOLE string — including the
17
+ * ones inside `${…}` — decided one was missing, and appended `px)` to the end,
18
+ * which could never help because the imbalance was inside the interpolation.
19
+ *
20
+ * One bad binding in one style property must never cost a project its build.
21
+ * These helpers make the imbalance repairable where it actually is, and let the
22
+ * caller degrade a single value instead of aborting.
23
+ */
24
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
25
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
26
+ if (ar || !(i in from)) {
27
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
28
+ ar[i] = from[i];
29
+ }
30
+ }
31
+ return to.concat(ar || Array.prototype.slice.call(from));
32
+ };
33
+ /** Closing delimiter for each opener that nests inside an expression. */
34
+ var CLOSER_FOR_OPENER = {
35
+ '(': ')',
36
+ '[': ']',
37
+ '{': '}',
38
+ };
39
+ var CLOSERS = new Set(Object.values(CLOSER_FOR_OPENER));
40
+ var QUOTES = new Set(["'", '"', '`']);
41
+ /**
42
+ * Single left-to-right pass over an expression, quote and escape aware.
43
+ * Characters inside a string literal never affect nesting.
44
+ */
45
+ export var scanExpression = function (expression) {
46
+ var unclosed = [];
47
+ var strayCloserCount = 0;
48
+ var quote = null;
49
+ for (var index = 0; index < expression.length; index++) {
50
+ var char = expression[index];
51
+ if (char === '\\') {
52
+ index++;
53
+ continue;
54
+ }
55
+ if (quote !== null) {
56
+ if (char === quote) {
57
+ quote = null;
58
+ }
59
+ continue;
60
+ }
61
+ if (QUOTES.has(char)) {
62
+ quote = char;
63
+ continue;
64
+ }
65
+ if (CLOSER_FOR_OPENER[char]) {
66
+ unclosed.push(char);
67
+ continue;
68
+ }
69
+ if (CLOSERS.has(char)) {
70
+ var expected = unclosed.length > 0 ? CLOSER_FOR_OPENER[unclosed[unclosed.length - 1]] : null;
71
+ if (expected === char) {
72
+ unclosed.pop();
73
+ }
74
+ else {
75
+ strayCloserCount++;
76
+ }
77
+ }
78
+ }
79
+ return { unclosed: unclosed, strayCloserCount: strayCloserCount, unterminatedQuote: quote };
80
+ };
81
+ /** True when every quote and bracket closes and no closer is stray. */
82
+ export var isBalancedExpression = function (expression) {
83
+ var scan = scanExpression(expression);
84
+ return (scan.unclosed.length === 0 && scan.strayCloserCount === 0 && scan.unterminatedQuote === null);
85
+ };
86
+ /**
87
+ * Close an expression that ends mid-quote or mid-bracket:
88
+ * `-(cameraX || ''` → `-(cameraX || '')`.
89
+ *
90
+ * Returns the expression unchanged when it is already balanced, and when the
91
+ * imbalance is a STRAY CLOSER — appending cannot fix a missing opener, and
92
+ * inventing one would be a guess.
93
+ */
94
+ export var balanceExpression = function (expression) {
95
+ var scan = scanExpression(expression);
96
+ if (scan.strayCloserCount > 0) {
97
+ return expression;
98
+ }
99
+ if (scan.unclosed.length === 0 && scan.unterminatedQuote === null) {
100
+ return expression;
101
+ }
102
+ var suffix = __spreadArray([scan.unterminatedQuote || ''], unclosedInReverse(scan.unclosed), true).join('');
103
+ return expression + suffix;
104
+ };
105
+ var unclosedInReverse = function (unclosed) {
106
+ return unclosed
107
+ .slice()
108
+ .reverse()
109
+ .map(function (opener) { return CLOSER_FOR_OPENER[opener]; });
110
+ };
111
+ /**
112
+ * Parenthesis balance of the STATIC parts of a template string — everything
113
+ * outside `${…}`.
114
+ *
115
+ * This is the number the "re-close the CSS function" step needs. Counting over
116
+ * the whole string mixes in the expressions' own parentheses, so an imbalance
117
+ * INSIDE an interpolation reads as a missing close on the CSS function and the
118
+ * step appends a unit and a `)` that belong nowhere.
119
+ */
120
+ export var countUnclosedStaticParens = function (templateStr) {
121
+ var depth = 0;
122
+ var index = 0;
123
+ while (index < templateStr.length) {
124
+ if (templateStr[index] === '$' && templateStr[index + 1] === '{') {
125
+ index = skipInterpolation(templateStr, index + 2);
126
+ continue;
127
+ }
128
+ if (templateStr[index] === '(') {
129
+ depth++;
130
+ }
131
+ else if (templateStr[index] === ')') {
132
+ depth--;
133
+ }
134
+ index++;
135
+ }
136
+ return depth > 0 ? depth : 0;
137
+ };
138
+ /**
139
+ * Index just past the `}` that closes an interpolation opened at `start`.
140
+ * Tracks nested braces and string literals so `${ {a:'}'} }` is skipped whole.
141
+ * An unterminated interpolation consumes the rest of the string.
142
+ */
143
+ var skipInterpolation = function (templateStr, start) {
144
+ var depth = 1;
145
+ var quote = null;
146
+ for (var index = start; index < templateStr.length; index++) {
147
+ var char = templateStr[index];
148
+ if (char === '\\') {
149
+ index++;
150
+ continue;
151
+ }
152
+ if (quote !== null) {
153
+ if (char === quote) {
154
+ quote = null;
155
+ }
156
+ continue;
157
+ }
158
+ if (QUOTES.has(char)) {
159
+ quote = char;
160
+ continue;
161
+ }
162
+ if (char === '{') {
163
+ depth++;
164
+ }
165
+ else if (char === '}') {
166
+ depth--;
167
+ if (depth === 0) {
168
+ return index + 1;
169
+ }
170
+ }
171
+ }
172
+ return templateStr.length;
173
+ };
174
+ //# sourceMappingURL=template-expression-balance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template-expression-balance.js","sourceRoot":"","sources":["../../../src/utils/template-expression-balance.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teleporthq/teleport-plugin-common",
3
- "version": "0.43.44",
3
+ "version": "0.43.47",
4
4
  "description": "Common building and modelating functions for ASTs and HASTs",
5
5
  "author": "teleportHQ",
6
6
  "license": "MIT",
@@ -27,10 +27,10 @@
27
27
  "@babel/core": "^7.5.5",
28
28
  "@babel/preset-react": "^7.22.5",
29
29
  "@babel/types": "^7.5.5",
30
- "@teleporthq/teleport-shared": "^0.43.44",
31
- "@teleporthq/teleport-types": "^0.43.44",
30
+ "@teleporthq/teleport-shared": "^0.43.47",
31
+ "@teleporthq/teleport-types": "^0.43.47",
32
32
  "jss": "^10.0.0",
33
33
  "jss-preset-default": "^10.0.0"
34
34
  },
35
- "gitHead": "c5cac00d70ddb1ebaa54836043880014d6772e78"
35
+ "gitHead": "5e8cf2c30367f6d57af1a6817a6437ffc01d60e9"
36
36
  }
@@ -6,7 +6,7 @@ import {
6
6
  convertValueToLiteral,
7
7
  getExpressionFromUIDLExpressionNode,
8
8
  } from '../../utils/ast-utils'
9
- import { StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
9
+ import { JSIdentifiers, StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
10
10
  import {
11
11
  UIDLPropDefinition,
12
12
  UIDLAttributeValue,
@@ -261,10 +261,14 @@ const createPropCallStatement = (
261
261
  const prefix = options.dynamicReferencePrefixMap.prop
262
262
  ? options.dynamicReferencePrefixMap.prop + '.'
263
263
  : ''
264
+ // Un-prefixed the prop name is called as a bare binding, so it must be
265
+ // identifier-safe; prefixed it is `props.<name>`, where the UIDL spelling is
266
+ // both legal and load-bearing.
267
+ const calleeName = prefix
268
+ ? prefix + propFunctionKey
269
+ : JSIdentifiers.createSafeJSIdentifier(propFunctionKey)
264
270
  return t.expressionStatement(
265
- t.callExpression(t.identifier(prefix + propFunctionKey), [
266
- ...args.map((arg) => convertValueToLiteral(arg)),
267
- ])
271
+ t.callExpression(t.identifier(calleeName), [...args.map((arg) => convertValueToLiteral(arg))])
268
272
  )
269
273
  }
270
274
 
@@ -310,6 +314,12 @@ export const createStateChangeStatement = (
310
314
  const statePrefix = options.dynamicReferencePrefixMap.state
311
315
  ? options.dynamicReferencePrefixMap.state + '.'
312
316
  : ''
317
+ // Reading/writing the state through a prefix (`this.class`) is a member
318
+ // access and keeps the UIDL spelling; without one the name IS the binding
319
+ // and has to match what `createStateHookAST` declared.
320
+ const stateReadName = statePrefix
321
+ ? statePrefix + stateKey
322
+ : JSIdentifiers.createSafeJSIdentifier(stateKey)
313
323
 
314
324
  const declaredType = stateDefinition.type
315
325
  const newState = eventHandlerStatement.newState
@@ -326,7 +336,7 @@ export const createStateChangeStatement = (
326
336
  )
327
337
  return null
328
338
  }
329
- newStateValue = t.unaryExpression('!', t.identifier(statePrefix + stateKey))
339
+ newStateValue = t.unaryExpression('!', t.identifier(stateReadName))
330
340
  } else if (typeof newState === 'object' && newState !== null) {
331
341
  const obj = newState as
332
342
  | UIDLDynamicReference
@@ -473,7 +483,7 @@ export const createStateChangeStatement = (
473
483
  return null
474
484
  }
475
485
  return t.expressionStatement(
476
- t.assignmentExpression('=', t.identifier(statePrefix + stateKey), newStateValue)
486
+ t.assignmentExpression('=', t.identifier(stateReadName), newStateValue)
477
487
  )
478
488
  }
479
489
  }
@@ -540,7 +550,12 @@ export const resolveAndRegisterGlobalStateSource = (
540
550
  for (const def of Object.values(definitions)) {
541
551
  if (def.id === id) {
542
552
  params.globalStateReferences.push({ id: def.id, name: def.name })
543
- return original.replace(LEGACY_GLOBAL_STATE_RE, def.name)
553
+ // The rewritten source is embedded as code, so the placeholder becomes
554
+ // the LOCAL binding — the sanitised name the destructuring declares.
555
+ return original.replace(
556
+ LEGACY_GLOBAL_STATE_RE,
557
+ JSIdentifiers.createSafeJSIdentifier(def.name)
558
+ )
544
559
  }
545
560
  }
546
561
  // Legacy prefix found but no matching definition (orphaned reference).
@@ -568,7 +583,11 @@ export const createGlobalStateExpression = (
568
583
  definitions?: Record<string, UIDLGlobalStateDefinition>,
569
584
  t = types
570
585
  ): types.Identifier | types.OptionalMemberExpression => {
571
- const name = resolveGlobalStateName(ref.content.id, definitions)
586
+ // The global-state name is destructured out of `useGlobalState()` as a local
587
+ // binding, so it goes through the same sanitiser the destructuring site uses.
588
+ const name = JSIdentifiers.createSafeJSIdentifier(
589
+ resolveGlobalStateName(ref.content.id, definitions)
590
+ )
572
591
  const refPath = ref.content.refPath || []
573
592
 
574
593
  if (refPath.length === 0) {
@@ -765,8 +784,12 @@ const createDynamicValueExpressionRaw = (
765
784
  const prefix =
766
785
  options.dynamicReferencePrefixMap[referenceType as 'prop' | 'state' | 'local'] || ''
767
786
 
787
+ // With no prefix the id IS the binding (React hooks: `class`), so it has to
788
+ // be sanitised exactly the way the declaration was. With a prefix it is a
789
+ // MEMBER (`props.class`, `this.class`), where a reserved word is perfectly
790
+ // legal and renaming it would point at a property that does not exist.
768
791
  return prefix === ''
769
- ? t.identifier(idWithPath)
792
+ ? t.identifier(JSIdentifiers.createSafeJSIdentifierPath(idWithPath))
770
793
  : t.memberExpression(t.identifier(prefix), t.identifier(idWithPath))
771
794
  }
772
795
 
@@ -1038,12 +1061,15 @@ export const createBinaryExpression = (
1038
1061
  t = types
1039
1062
  ) => {
1040
1063
  const { operand, operation, containsField } = condition
1064
+ // Same rule as `createDynamicValueExpression`: an un-prefixed key is the
1065
+ // binding itself and must be identifier-safe; a prefixed one is a member
1066
+ // access that has to keep the UIDL's original spelling.
1041
1067
  const identifier = conditionalIdentifier.prefix
1042
1068
  ? t.memberExpression(
1043
1069
  t.identifier(conditionalIdentifier.prefix),
1044
1070
  t.identifier(conditionalIdentifier.key)
1045
1071
  )
1046
- : t.identifier(conditionalIdentifier.key)
1072
+ : t.identifier(JSIdentifiers.createSafeJSIdentifierPath(conditionalIdentifier.key))
1047
1073
 
1048
1074
  // Array/Object operators
1049
1075
  if (operation === 'isEmpty') {
@@ -1,7 +1,7 @@
1
1
  import * as types from '@babel/types'
2
2
  import { parse } from '@babel/core'
3
3
  import ParsedASTNode from './parsed-ast'
4
- import { StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
4
+ import { JSIdentifiers, StringUtils, UIDLUtils } from '@teleporthq/teleport-shared'
5
5
  import {
6
6
  UIDLStateDefinition,
7
7
  UIDLPropDefinition,
@@ -16,6 +16,7 @@ import {
16
16
  } from '@teleporthq/teleport-types'
17
17
  import babelPresetReact from '@babel/preset-react'
18
18
  import { UnaryOperation, BinaryOperator } from './types'
19
+ import { balanceExpression, countUnclosedStaticParens } from './template-expression-balance'
19
20
 
20
21
  /**
21
22
  * Converts HTML attribute names to React/JSX camelCase format
@@ -140,9 +141,14 @@ export const addDynamicAttributeToJSXTag = (
140
141
  t = types
141
142
  ) => {
142
143
  const reactName = convertToReactAttributeName(name)
144
+ // Same rule as `createDynamicValueExpression`: with no prefix the value IS the
145
+ // binding (React hooks state), so it must match the sanitised name
146
+ // `createStateHookAST` declared — `value={class}` does not parse. With a
147
+ // prefix it is `props.class` / `this.class`, where the UIDL spelling is legal
148
+ // AND load-bearing, so it is left alone.
143
149
  const content =
144
150
  prefix === ''
145
- ? t.identifier(value)
151
+ ? t.identifier(JSIdentifiers.createSafeJSIdentifierPath(value))
146
152
  : t.memberExpression(t.identifier(prefix), t.identifier(value))
147
153
 
148
154
  jsxASTNode.openingElement.attributes.push(
@@ -354,9 +360,15 @@ export const parseStringWithTemplateExpressions = (str: string): types.TemplateL
354
360
  const normalized = str.replace(/state\.(\w+)/g, '$1')
355
361
 
356
362
  // Step 2: Convert well-formed {{ expr }} to ${expr}
363
+ //
364
+ // The expression is BALANCED on the way in. A binding that lost a closing
365
+ // paren upstream (`-(cameraX || ''`) produces source Babel cannot parse, and
366
+ // no amount of patching the static text around it can help — the repair has
367
+ // to happen inside the interpolation, which is the only place the bracket is
368
+ // actually missing.
357
369
  let templateStr = normalized.replace(
358
370
  /\{\{\s*(.+?)\s*\}\}/g,
359
- (_, expr: string) => '${' + expr.trim() + '}'
371
+ (_, expr: string) => '${' + balanceExpression(expr.trim()) + '}'
360
372
  )
361
373
 
362
374
  // Step 3: Handle unclosed {{ expr (no closing }})
@@ -364,16 +376,20 @@ export const parseStringWithTemplateExpressions = (str: string): types.TemplateL
364
376
  if (/\{\{/.test(templateStr)) {
365
377
  templateStr = templateStr.replace(
366
378
  /\{\{\s*(.+?)$/gm,
367
- (_, expr: string) => '${' + expr.trim() + '}'
379
+ (_, expr: string) => '${' + balanceExpression(expr.trim()) + '}'
368
380
  )
369
381
  }
370
382
 
371
383
  // Step 4: Detect and fix incomplete CSS function calls
372
384
  // If the original string contained a CSS function like translateX(...) or translate(...)
373
385
  // but the template expression consumed the closing, we need to re-close it.
374
- const openParens = (templateStr.match(/\(/g) || []).length
375
- const closeParens = (templateStr.match(/\)/g) || []).length
376
- if (openParens > closeParens) {
386
+ //
387
+ // Counted over the STATIC text only. Including the parentheses inside `${…}`
388
+ // made an imbalance in the expression look like an unclosed CSS function, so
389
+ // this step appended a unit and a `)` to the end of a string whose real fault
390
+ // was several characters earlier and inside the interpolation.
391
+ const missingStaticCloses = countUnclosedStaticParens(templateStr)
392
+ if (missingStaticCloses > 0) {
377
393
  // Determine the CSS unit by:
378
394
  // 1. Looking for existing units already in the string (e.g. "translate(${x}px, ${y" → px)
379
395
  // 2. Inferring from the CSS function name
@@ -393,22 +409,53 @@ export const parseStringWithTemplateExpressions = (str: string): types.TemplateL
393
409
  }
394
410
  }
395
411
 
396
- const missingCloses = openParens - closeParens
397
- templateStr += unit + ')'.repeat(missingCloses)
412
+ templateStr += unit + ')'.repeat(missingStaticCloses)
398
413
  }
399
414
 
400
- const ast = parse('const x = `' + templateStr + '`', {
401
- sourceType: 'module',
402
- })
415
+ // Step 5: Parse — and NEVER let a single malformed binding abort the build.
416
+ //
417
+ // The inline-style caller has no error handling of its own, so an
418
+ // unparseable template used to escape as a raw Babel SyntaxError and take the
419
+ // whole project generation down with it. A UIDL can always carry a binding
420
+ // this module cannot rescue (hand-edited, produced by an older pipeline, or
421
+ // broken in a way `balanceExpression` refuses to guess at), and the right
422
+ // answer is to degrade that ONE value: emit the original text as a plain
423
+ // string so the property still renders and the other 99 pages still build.
424
+ try {
425
+ const ast = parse('const x = `' + templateStr + '`', {
426
+ sourceType: 'module',
427
+ })
403
428
 
404
- if (!ast || !('program' in ast)) {
405
- throw new Error(`Failed to parse template expression: ${str}`)
429
+ if (ast && 'program' in ast) {
430
+ const decl = ast.program.body[0] as types.VariableDeclaration
431
+ return decl.declarations[0].init as types.TemplateLiteral
432
+ }
433
+ } catch {
434
+ // Fall through to the static fallback below.
406
435
  }
407
436
 
408
- const decl = ast.program.body[0] as types.VariableDeclaration
409
- return decl.declarations[0].init as types.TemplateLiteral
437
+ return staticTemplateLiteral(stripTemplateExpressions(str))
410
438
  }
411
439
 
440
+ /**
441
+ * Drop every `{{ … }}` binding from a string, keeping any `'literal'` fallback
442
+ * it declared. `translateX({{ -(x || 0) }}px)` → `translateX(px)` is useless as
443
+ * a style value, but `{{ name || 'Guest' }}` → `Guest` keeps the text the
444
+ * author intended. Used only when the expression could not be parsed at all.
445
+ */
446
+ const stripTemplateExpressions = (value: string): string =>
447
+ value.replace(/\{\{\s*([\s\S]*?)\s*\}\}/g, (_, expr: string) => {
448
+ const literalFallback = /\|\|\s*(['"])([\s\S]*?)\1\s*$/.exec(expr)
449
+ return literalFallback ? literalFallback[2] : ''
450
+ })
451
+
452
+ /** A TemplateLiteral with no interpolations, holding `value` verbatim. */
453
+ const staticTemplateLiteral = (value: string): types.TemplateLiteral =>
454
+ types.templateLiteral(
455
+ [types.templateElement({ raw: value.replace(/[\\`]/g, '\\$&'), cooked: value }, true)],
456
+ []
457
+ )
458
+
412
459
  const REACT_BOOLEAN_DOM_PROPS = new Set([
413
460
  'disabled',
414
461
  'required',
@@ -931,7 +978,11 @@ export const createStateHookAST = (
931
978
  return t.variableDeclaration('const', [
932
979
  t.variableDeclarator(
933
980
  t.arrayPattern([
934
- t.identifier(stateKey),
981
+ // The state NAME comes from the UIDL and may be a reserved word (a
982
+ // character sheet has a column called `class`), which is fatal in
983
+ // binding position. Reads go through the same sanitiser, so the
984
+ // declaration and every reference stay in sync.
985
+ t.identifier(JSIdentifiers.createSafeJSIdentifier(stateKey)),
935
986
  t.identifier(StringUtils.createStateStoringFunction(stateKey)),
936
987
  ]),
937
988
  t.callExpression(t.identifier('useState'), [useStateArgument])
@@ -1,4 +1,5 @@
1
1
  import { UIDLWorkflows } from '@teleporthq/teleport-types'
2
+ import { RoutePaths } from '@teleporthq/teleport-shared'
2
3
 
3
4
  /**
4
5
  * True when this UIDL component's output route contains a Next.js dynamic
@@ -13,6 +14,14 @@ export const isDynamicRoute = (uidl: {
13
14
  )
14
15
  }
15
16
 
17
+ /**
18
+ * Reading a route TEMPLATE (`/event-details/[id]`) rather than a URL. Defined in
19
+ * teleport-shared because the navlink resolver needs the same two functions and
20
+ * lives in a package that does not depend on this one; re-exported here so
21
+ * `RouteUtils` stays the single import for route questions in the plugins.
22
+ */
23
+ export const { pathHasDynamicSegment, parseDynamicPathSegments } = RoutePaths
24
+
16
25
  const MUTATION_NODE_TYPES = new Set(['data-create-item', 'data-update-item'])
17
26
 
18
27
  /**