@fluffjs/cli 0.0.8 → 0.1.0

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 (142) hide show
  1. package/BabelHelpers.d.ts +26 -0
  2. package/BabelHelpers.js +65 -0
  3. package/Cli.d.ts +5 -10
  4. package/Cli.js +123 -52
  5. package/CodeGenerator.d.ts +53 -39
  6. package/CodeGenerator.js +330 -725
  7. package/ComponentCompiler.d.ts +14 -16
  8. package/ComponentCompiler.js +187 -256
  9. package/DomPreProcessor.d.ts +36 -0
  10. package/DomPreProcessor.js +645 -0
  11. package/ErrorHelpers.d.ts +5 -0
  12. package/ErrorHelpers.js +8 -0
  13. package/ExpressionTransformer.d.ts +38 -28
  14. package/ExpressionTransformer.js +558 -230
  15. package/Generator.d.ts +1 -5
  16. package/Generator.js +128 -67
  17. package/GetterDependencyExtractor.d.ts +4 -0
  18. package/GetterDependencyExtractor.js +73 -0
  19. package/IndexHtmlTransformer.d.ts +6 -7
  20. package/IndexHtmlTransformer.js +82 -88
  21. package/Parse5Helpers.d.ts +16 -0
  22. package/Parse5Helpers.js +81 -0
  23. package/TemplateParser.d.ts +39 -21
  24. package/TemplateParser.js +462 -268
  25. package/Typeguards.d.ts +24 -0
  26. package/Typeguards.js +30 -0
  27. package/babel-plugin-class-transform.d.ts +3 -18
  28. package/babel-plugin-class-transform.js +3 -11
  29. package/babel-plugin-component.d.ts +4 -13
  30. package/babel-plugin-component.js +7 -0
  31. package/babel-plugin-imports.d.ts +3 -11
  32. package/babel-plugin-imports.js +5 -31
  33. package/babel-plugin-reactive.d.ts +2 -19
  34. package/babel-plugin-reactive.js +21 -76
  35. package/bin.js +2 -2
  36. package/fluff-esbuild-plugin.d.ts +2 -5
  37. package/fluff-esbuild-plugin.js +4 -1
  38. package/index.d.ts +6 -2
  39. package/index.js +1 -1
  40. package/interfaces/BabelPluginClassTransformState.d.ts +5 -0
  41. package/interfaces/BabelPluginComponentState.d.ts +4 -0
  42. package/interfaces/BabelPluginComponentState.js +1 -0
  43. package/interfaces/BabelPluginImportsState.d.ts +5 -0
  44. package/interfaces/BabelPluginImportsState.js +1 -0
  45. package/interfaces/BabelPluginReactiveState.d.ts +13 -0
  46. package/interfaces/BabelPluginReactiveState.js +1 -0
  47. package/interfaces/BabelPluginReactiveWatchCallInfo.d.ts +7 -0
  48. package/interfaces/BabelPluginReactiveWatchCallInfo.js +1 -0
  49. package/interfaces/BabelPluginReactiveWatchInfo.d.ts +5 -0
  50. package/interfaces/BabelPluginReactiveWatchInfo.js +1 -0
  51. package/interfaces/BabelToken.d.ts +8 -0
  52. package/interfaces/BabelToken.js +1 -0
  53. package/interfaces/BindingInfo.d.ts +12 -0
  54. package/interfaces/BindingInfo.js +1 -0
  55. package/interfaces/BreakMarkerConfig.d.ts +4 -0
  56. package/interfaces/BreakMarkerConfig.js +1 -0
  57. package/interfaces/BreakNode.d.ts +4 -0
  58. package/interfaces/BreakNode.js +1 -0
  59. package/interfaces/BundleOptions.d.ts +8 -0
  60. package/interfaces/BundleOptions.js +1 -0
  61. package/interfaces/ClassTransformOptions.d.ts +10 -0
  62. package/interfaces/ClassTransformOptions.js +1 -0
  63. package/interfaces/CliOptions.d.ts +6 -0
  64. package/interfaces/CliOptions.js +1 -0
  65. package/interfaces/CommentNode.d.ts +5 -0
  66. package/interfaces/CommentNode.js +1 -0
  67. package/interfaces/CompileResult.d.ts +6 -0
  68. package/interfaces/CompileResult.js +1 -0
  69. package/interfaces/CompilerOptions.d.ts +6 -0
  70. package/interfaces/CompilerOptions.js +1 -0
  71. package/interfaces/ComponentInfo.d.ts +8 -0
  72. package/interfaces/ComponentInfo.js +1 -0
  73. package/interfaces/ComponentMetadata.d.ts +9 -0
  74. package/interfaces/ComponentMetadata.js +1 -0
  75. package/interfaces/ControlFlow.d.ts +19 -0
  76. package/interfaces/ControlFlow.js +1 -0
  77. package/interfaces/ControlFlowNode.d.ts +6 -0
  78. package/interfaces/ControlFlowNode.js +1 -0
  79. package/interfaces/ControlFlowParseResult.d.ts +10 -0
  80. package/interfaces/ControlFlowParseResult.js +1 -0
  81. package/interfaces/ElementNode.d.ts +11 -0
  82. package/interfaces/ElementNode.js +1 -0
  83. package/interfaces/FluffConfigInterface.d.ts +7 -0
  84. package/interfaces/FluffConfigInterface.js +1 -0
  85. package/interfaces/FluffPluginOptions.d.ts +9 -0
  86. package/interfaces/FluffPluginOptions.js +1 -0
  87. package/interfaces/FluffTarget.d.ts +15 -0
  88. package/interfaces/FluffTarget.js +1 -0
  89. package/interfaces/ForMarkerConfig.d.ts +9 -0
  90. package/interfaces/ForMarkerConfig.js +1 -0
  91. package/interfaces/ForNode.d.ts +13 -0
  92. package/interfaces/ForNode.js +1 -0
  93. package/interfaces/GeneratorOptions.d.ts +5 -0
  94. package/interfaces/GeneratorOptions.js +1 -0
  95. package/interfaces/HtmlTransformOptions.d.ts +9 -0
  96. package/interfaces/HtmlTransformOptions.js +1 -0
  97. package/interfaces/IfBranch.d.ts +8 -0
  98. package/interfaces/IfBranch.js +1 -0
  99. package/interfaces/IfMarkerConfig.d.ts +8 -0
  100. package/interfaces/IfMarkerConfig.js +1 -0
  101. package/interfaces/IfNode.d.ts +7 -0
  102. package/interfaces/IfNode.js +1 -0
  103. package/interfaces/ImportTransformOptions.d.ts +7 -0
  104. package/interfaces/ImportTransformOptions.js +1 -0
  105. package/interfaces/InterpolationNode.d.ts +12 -0
  106. package/interfaces/InterpolationNode.js +1 -0
  107. package/interfaces/ParsedTemplate.d.ts +6 -0
  108. package/interfaces/ParsedTemplate.js +1 -0
  109. package/interfaces/ParsedTemplateOld.d.ts +9 -0
  110. package/interfaces/ParsedTemplateOld.js +1 -0
  111. package/interfaces/PropertyChain.d.ts +2 -0
  112. package/interfaces/PropertyChain.js +1 -0
  113. package/interfaces/Scope.d.ts +5 -0
  114. package/interfaces/Scope.js +1 -0
  115. package/interfaces/ServeOptions.d.ts +5 -0
  116. package/interfaces/ServeOptions.js +1 -0
  117. package/interfaces/SwitchCase.d.ts +8 -0
  118. package/interfaces/SwitchCase.js +1 -0
  119. package/interfaces/SwitchMarkerConfig.d.ts +11 -0
  120. package/interfaces/SwitchMarkerConfig.js +1 -0
  121. package/interfaces/SwitchNode.d.ts +10 -0
  122. package/interfaces/SwitchNode.js +1 -0
  123. package/interfaces/TemplateBinding.d.ts +10 -0
  124. package/interfaces/TemplateBinding.js +1 -0
  125. package/interfaces/TemplateNode.d.ts +7 -0
  126. package/interfaces/TemplateNode.js +1 -0
  127. package/interfaces/TextMarkerConfig.d.ts +10 -0
  128. package/interfaces/TextMarkerConfig.js +1 -0
  129. package/interfaces/TextNode.d.ts +5 -0
  130. package/interfaces/TextNode.js +1 -0
  131. package/interfaces/TokenizeResult.d.ts +6 -0
  132. package/interfaces/TokenizeResult.js +1 -0
  133. package/interfaces/TransformOptions.d.ts +11 -0
  134. package/interfaces/TransformOptions.js +1 -0
  135. package/interfaces/index.d.ts +34 -0
  136. package/interfaces/index.js +1 -0
  137. package/package.json +9 -1
  138. package/types/FluffConfig.d.ts +5 -27
  139. package/ControlFlowParser.d.ts +0 -55
  140. package/ControlFlowParser.js +0 -279
  141. package/types.d.ts +0 -46
  142. /package/{types.js → interfaces/BabelPluginClassTransformState.js} +0 -0
@@ -1,276 +1,604 @@
1
- import _generate from '@babel/generator';
2
1
  import { parse } from '@babel/parser';
3
- import _traverse from '@babel/traverse';
4
2
  import * as t from '@babel/types';
5
- const traverse = _traverse.default ?? _traverse;
6
- const generate = _generate.default ?? _generate;
7
- export function transformExpression(expr, options = {}) {
8
- const { addThisPrefix: shouldAddThisPrefix = true, nullSafe = false, iteratorName, iteratorReplacement, localVars = [] } = options;
9
- try {
10
- const ast = parse(`(${expr})`, {
11
- sourceType: 'module', plugins: ['typescript']
12
- });
13
- traverse(ast, {
14
- Identifier(path) {
15
- const { name } = path.node;
16
- if (['true', 'false', 'null', 'undefined', 'this'].includes(name)) {
17
- return;
18
- }
19
- if (localVars.includes(name)) {
20
- return;
21
- }
22
- if (t.isMemberExpression(path.parent) && path.parent.property === path.node && !path.parent.computed) {
23
- return;
24
- }
25
- if (t.isOptionalMemberExpression(path.parent) && path.parent.property === path.node) {
26
- return;
27
- }
28
- if (iteratorName && name === iteratorName && iteratorReplacement) {
29
- const replacementAst = parse(`(${iteratorReplacement})`, { sourceType: 'module' });
30
- const [firstStmt] = replacementAst.program.body;
31
- if (!t.isExpressionStatement(firstStmt))
32
- return;
33
- const replacementExpr = firstStmt.expression;
34
- path.replaceWith(replacementExpr);
35
- return;
36
- }
37
- if (shouldAddThisPrefix) {
38
- path.replaceWith(t.memberExpression(t.thisExpression(), t.identifier(name)));
39
- }
40
- }
41
- });
42
- if (nullSafe) {
3
+ import { generate, traverse } from './BabelHelpers.js';
4
+ import { ErrorHelpers } from './ErrorHelpers.js';
5
+ export class ExpressionTransformer {
6
+ static RESERVED_KEYWORDS = [
7
+ 'true', 'false', 'null', 'undefined', 'this',
8
+ 'void', 'typeof', 'delete', 'new', 'instanceof', 'in',
9
+ 'if', 'else', 'for', 'while', 'do', 'switch', 'case', 'break', 'continue', 'return', 'throw',
10
+ 'try', 'catch', 'finally', 'function', 'class', 'extends', 'super',
11
+ 'import', 'export', 'default', 'const', 'let', 'var', 'async', 'await', 'yield',
12
+ 'debugger', 'with',
13
+ '$event'
14
+ ];
15
+ static GLOBAL_OBJECTS = [
16
+ 'Array', 'Object', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
17
+ 'Math', 'Date', 'JSON', 'RegExp', 'Error', 'Map', 'Set', 'WeakMap', 'WeakSet',
18
+ 'Promise', 'Proxy', 'Reflect', 'Intl',
19
+ 'console', 'window', 'document', 'navigator', 'location', 'history',
20
+ 'localStorage', 'sessionStorage', 'fetch', 'XMLHttpRequest',
21
+ 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval', 'requestAnimationFrame',
22
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'encodeURI', 'decodeURI',
23
+ 'encodeURIComponent', 'decodeURIComponent', 'atob', 'btoa',
24
+ 'Infinity', 'NaN', 'globalThis'
25
+ ];
26
+ static transformExpression(expr, options = {}) {
27
+ const { addThisPrefix: shouldAddThisPrefix = true, nullSafe = false, iteratorName, iteratorReplacement, localVars = [], localsObjectName, eventReplacementName, templateRefs = [] } = options;
28
+ try {
29
+ const ast = parse(`(${expr})`, {
30
+ sourceType: 'module', plugins: ['typescript']
31
+ });
43
32
  traverse(ast, {
44
- MemberExpression: {
45
- exit(path) {
46
- const obj = path.node.object;
47
- const isThisMember = t.isMemberExpression(obj) && t.isThisExpression(obj.object);
48
- const isOptionalChain = t.isOptionalMemberExpression(obj);
49
- const isDeepChain = t.isMemberExpression(obj);
50
- if (!path.node.optional && (isThisMember || isOptionalChain || isDeepChain)) {
51
- const prop = path.node.property;
52
- if (t.isExpression(prop)) {
53
- const newNode = t.optionalMemberExpression(path.node.object, prop, path.node.computed, true);
54
- path.replaceWith(newNode);
33
+ Identifier(path) {
34
+ const { name } = path.node;
35
+ if (localsObjectName && name === localsObjectName) {
36
+ return;
37
+ }
38
+ if (t.isMemberExpression(path.parent) && path.parent.property === path.node && !path.parent.computed) {
39
+ return;
40
+ }
41
+ if (t.isOptionalMemberExpression(path.parent) && path.parent.property === path.node) {
42
+ return;
43
+ }
44
+ if (t.isObjectProperty(path.parent) && path.parent.key === path.node && !path.parent.computed) {
45
+ return;
46
+ }
47
+ if (name === '$event' && eventReplacementName) {
48
+ path.replaceWith(t.identifier(eventReplacementName));
49
+ return;
50
+ }
51
+ if (eventReplacementName && name === eventReplacementName) {
52
+ return;
53
+ }
54
+ if (ExpressionTransformer.RESERVED_KEYWORDS.includes(name) || ExpressionTransformer.GLOBAL_OBJECTS.includes(name)) {
55
+ return;
56
+ }
57
+ if (localVars.includes(name)) {
58
+ if (localsObjectName) {
59
+ path.replaceWith(t.memberExpression(t.identifier(localsObjectName), t.identifier(name)));
60
+ }
61
+ return;
62
+ }
63
+ if (templateRefs.includes(name)) {
64
+ path.replaceWith(t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__getShadowRoot')), []), t.identifier('querySelector')), [t.stringLiteral(`[data-ref="${name}"]`)]));
65
+ return;
66
+ }
67
+ if (t.isObjectProperty(path.parent) && path.parent.shorthand && path.parent.value === path.node) {
68
+ const grandParent = path.parentPath?.parentPath?.node;
69
+ if (grandParent && t.isObjectPattern(grandParent)) {
70
+ return;
71
+ }
72
+ }
73
+ const collectFunctionParams = (startPath) => {
74
+ const params = new Set();
75
+ let cur = startPath.parentPath;
76
+ while (cur !== null) {
77
+ if (t.isArrowFunctionExpression(cur.node) || t.isFunctionExpression(cur.node) || t.isFunctionDeclaration(cur.node)) {
78
+ for (const param of cur.node.params) {
79
+ if (t.isIdentifier(param)) {
80
+ params.add(param.name);
81
+ }
82
+ else if (t.isAssignmentPattern(param) && t.isIdentifier(param.left)) {
83
+ params.add(param.left.name);
84
+ }
85
+ else if (t.isRestElement(param) && t.isIdentifier(param.argument)) {
86
+ params.add(param.argument.name);
87
+ }
88
+ }
89
+ }
90
+ const next = cur.parentPath;
91
+ if (next === null) {
92
+ break;
55
93
  }
94
+ cur = next;
56
95
  }
96
+ return params;
97
+ };
98
+ if (collectFunctionParams(path)
99
+ .has(name)) {
100
+ return;
101
+ }
102
+ if (iteratorName && name === iteratorName && iteratorReplacement) {
103
+ const replacementAst = parse(`(${iteratorReplacement})`, { sourceType: 'module' });
104
+ const [firstStmt] = replacementAst.program.body;
105
+ if (!t.isExpressionStatement(firstStmt))
106
+ return;
107
+ const replacementExpr = firstStmt.expression;
108
+ path.replaceWith(replacementExpr);
109
+ return;
110
+ }
111
+ if (shouldAddThisPrefix) {
112
+ path.replaceWith(t.memberExpression(t.thisExpression(), t.identifier(name)));
57
113
  }
58
114
  }
59
115
  });
116
+ if (nullSafe) {
117
+ traverse(ast, {
118
+ MemberExpression: {
119
+ exit(path) {
120
+ const obj = path.node.object;
121
+ const isThisMember = t.isMemberExpression(obj) && t.isThisExpression(obj.object);
122
+ const isOptionalChain = t.isOptionalMemberExpression(obj);
123
+ const isDeepChain = t.isMemberExpression(obj);
124
+ if (!path.node.optional && (isThisMember || isOptionalChain || isDeepChain)) {
125
+ const prop = path.node.property;
126
+ if (t.isExpression(prop)) {
127
+ const newNode = t.optionalMemberExpression(path.node.object, prop, path.node.computed, true);
128
+ path.replaceWith(newNode);
129
+ }
130
+ }
131
+ }
132
+ }
133
+ });
134
+ }
135
+ const output = generate(ast, { compact: true });
136
+ let { code } = output;
137
+ code = code.replace(/;+$/, '');
138
+ const isWrappedParens = (s) => {
139
+ if (!s.startsWith('(') || !s.endsWith(')'))
140
+ return false;
141
+ let depth = 0;
142
+ for (let i = 0; i < s.length; i++) {
143
+ if (s[i] === '(')
144
+ depth++;
145
+ else if (s[i] === ')')
146
+ depth--;
147
+ if (depth === 0 && i < s.length - 1)
148
+ return false;
149
+ }
150
+ return depth === 0;
151
+ };
152
+ if (isWrappedParens(code)) {
153
+ code = code.slice(1, -1);
154
+ }
155
+ return code;
60
156
  }
61
- const output = generate(ast, { compact: true });
62
- let { code } = output;
63
- if (code.startsWith('(') && code.endsWith(');')) {
64
- code = code.slice(1, -2);
65
- }
66
- else if (code.startsWith('(') && code.endsWith(')')) {
67
- code = code.slice(1, -1);
157
+ catch (e) {
158
+ const message = ErrorHelpers.getErrorMessage(e);
159
+ throw new Error(`Failed to parse expression "${expr}": ${message}`);
68
160
  }
69
- code = code.replace(/;+$/, '');
70
- return code;
71
161
  }
72
- catch (e) {
73
- const message = e instanceof Error ? e.message : String(e);
74
- throw new Error(`Failed to parse expression "${expr}": ${message}`);
162
+ static addThisPrefix(expr) {
163
+ return ExpressionTransformer.transformExpression(expr, { addThisPrefix: true, nullSafe: false });
75
164
  }
76
- }
77
- export function addThisPrefix(expr) {
78
- return transformExpression(expr, { addThisPrefix: true, nullSafe: false });
79
- }
80
- export function addThisPrefixSafe(expr) {
81
- return transformExpression(expr, { addThisPrefix: true, nullSafe: true });
82
- }
83
- export function transformForExpression(expr, iteratorName, iteratorReplacement) {
84
- return transformExpression(expr, {
85
- addThisPrefix: true,
86
- nullSafe: false,
87
- iteratorName,
88
- iteratorReplacement,
89
- localVars: ['__items', '__idx', '__item']
90
- });
91
- }
92
- export function transformForExpressionKeepIterator(expr, iteratorName) {
93
- return transformExpression(expr, {
94
- addThisPrefix: true, nullSafe: false, localVars: [iteratorName, '__items', '__idx', '__item', '__currentItems']
95
- });
96
- }
97
- export function parsePipedExpression(expr) {
98
- let remaining = expr.trim();
99
- const pipes = [];
100
- while (true) {
101
- let pipeIdx = -1;
102
- for (let i = remaining.length - 1; i >= 0; i--) {
103
- if (remaining[i] === '|') {
104
- if (remaining[i - 1] === '|' || remaining[i + 1] === '|')
105
- continue;
106
- const left = remaining.slice(0, i)
107
- .trim();
108
- if (!left)
109
- continue;
165
+ static addThisPrefixSafe(expr) {
166
+ return ExpressionTransformer.transformExpression(expr, { addThisPrefix: true, nullSafe: true });
167
+ }
168
+ static transformForExpression(expr, iteratorName, iteratorReplacement) {
169
+ return ExpressionTransformer.transformExpression(expr, {
170
+ addThisPrefix: true,
171
+ nullSafe: false,
172
+ iteratorName,
173
+ iteratorReplacement,
174
+ localVars: ['__items', '__idx', '__item']
175
+ });
176
+ }
177
+ static transformForExpressionKeepIterator(expr, iteratorName) {
178
+ return ExpressionTransformer.transformExpression(expr, {
179
+ addThisPrefix: true,
180
+ nullSafe: false,
181
+ localVars: [iteratorName, '__items', '__idx', '__item', '__currentItems']
182
+ });
183
+ }
184
+ static hasTokens(ast) {
185
+ return ast !== null && typeof ast === 'object' && 'tokens' in ast && Array.isArray(ast.tokens);
186
+ }
187
+ static hasLocIndex(e) {
188
+ if (e === null || typeof e !== 'object' || !('loc' in e))
189
+ return false;
190
+ const { loc } = e;
191
+ return loc !== null && typeof loc === 'object' && 'index' in loc && typeof loc.index === 'number';
192
+ }
193
+ static tokenizeExpression(code, startPos = 0) {
194
+ const substring = code.slice(startPos);
195
+ let tokens = [];
196
+ try {
197
+ const ast = parse(substring, {
198
+ sourceType: 'module',
199
+ tokens: true
200
+ });
201
+ if (ExpressionTransformer.hasTokens(ast)) {
202
+ ({ tokens } = ast);
203
+ }
204
+ }
205
+ catch (e) {
206
+ if (ExpressionTransformer.hasLocIndex(e)) {
207
+ const partialCode = substring.substring(0, e.loc.index);
110
208
  try {
111
- parse(left, { sourceType: 'module' });
112
- pipeIdx = i;
113
- break;
209
+ const partialAst = parse(partialCode, {
210
+ sourceType: 'module',
211
+ tokens: true
212
+ });
213
+ if (ExpressionTransformer.hasTokens(partialAst)) {
214
+ ({ tokens } = partialAst);
215
+ }
114
216
  }
115
217
  catch {
218
+ return { index: startPos, tokenCount: 0, stopReason: 'end' };
116
219
  }
117
220
  }
118
221
  }
119
- if (pipeIdx === -1)
120
- break;
121
- const pipeExpr = remaining.slice(pipeIdx + 1)
122
- .trim();
123
- const colonParts = pipeExpr.split(':');
124
- const pipeName = colonParts[0].trim();
125
- const args = colonParts.slice(1)
126
- .map(s => s.trim());
127
- pipes.unshift({ name: pipeName, args });
128
- remaining = remaining.slice(0, pipeIdx)
129
- .trim();
130
- }
131
- return { expression: remaining, pipes };
132
- }
133
- export function transformInterpolation(expr, iteratorVar) {
134
- const localVars = iteratorVar ? [iteratorVar] : [];
135
- const { expression: baseExpr, pipes } = parsePipedExpression(expr);
136
- if (pipes.length > 0) {
137
- let result = transformExpression(baseExpr, { addThisPrefix: true, nullSafe: true, localVars });
138
- for (const pipe of pipes) {
139
- const argsStr = pipe.args.length > 0 ? ', ' + pipe.args.join(', ') : '';
140
- result = `this.__pipe('${pipe.name}', ${result}${argsStr})`;
141
- }
142
- return `${result} ?? ''`;
143
- }
144
- const transformed = transformExpression(expr, { addThisPrefix: true, nullSafe: true, localVars });
145
- return `${transformed} ?? ''`;
146
- }
147
- export function extractRootIdentifier(expr) {
148
- try {
149
- const ast = parse(expr, { sourceType: 'module' });
150
- const [stmt] = ast.program.body;
151
- if (!t.isExpressionStatement(stmt))
152
- return null;
153
- let node = stmt.expression;
154
- while (true) {
155
- if (t.isIdentifier(node)) {
156
- return node.name;
222
+ let depth = 0;
223
+ let stopIndex = substring.length;
224
+ let stopReason = 'end';
225
+ let tokenCount = 0;
226
+ for (let i = 0; i < tokens.length; i++) {
227
+ const token = tokens[i];
228
+ if (token.type.label === '(' || token.type.label === '[' || token.type.label === '{' || token.type.label === '${') {
229
+ depth++;
157
230
  }
158
- else if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
159
- node = node.object;
231
+ if (token.type.label === ')' || token.type.label === ']' || token.type.label === '}') {
232
+ depth--;
160
233
  }
161
- else if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) {
162
- node = node.callee;
234
+ if (depth < 0) {
235
+ stopIndex = token.start;
236
+ stopReason = 'negative_depth';
237
+ tokenCount = i;
238
+ break;
163
239
  }
164
- else if (t.isBinaryExpression(node) || t.isLogicalExpression(node)) {
165
- node = node.left;
240
+ if (depth === 0 && (token.type.label === '|' || token.type.label === ';' || token.type.label === ':')) {
241
+ stopIndex = token.start;
242
+ stopReason = 'delimiter';
243
+ tokenCount = i;
244
+ break;
166
245
  }
167
- else if (t.isUnaryExpression(node)) {
168
- node = node.argument;
246
+ tokenCount = i + 1;
247
+ }
248
+ if (stopReason === 'end' && tokens.length > 0) {
249
+ stopIndex = tokens[tokens.length - 1].end;
250
+ }
251
+ return { index: startPos + stopIndex, tokenCount, stopReason };
252
+ }
253
+ static parseSecondaryExpression(input, startPos = 0) {
254
+ const substring = input.slice(startPos);
255
+ try {
256
+ parse(substring, { sourceType: 'module' });
257
+ return {
258
+ expression: substring.trim(),
259
+ endPos: input.length
260
+ };
261
+ }
262
+ catch (e) {
263
+ if (!(e instanceof Error) || !('pos' in e)) {
264
+ return null;
169
265
  }
170
- else if (t.isConditionalExpression(node)) {
171
- node = node.test;
266
+ const errorPos = typeof e.pos === 'number' ? e.pos : null;
267
+ if (errorPos === null || errorPos === 0) {
268
+ return null;
172
269
  }
173
- else {
270
+ const candidate = substring.slice(0, errorPos)
271
+ .trim();
272
+ if (candidate.length === 0) {
273
+ return null;
274
+ }
275
+ try {
276
+ parse(candidate, { sourceType: 'module' });
277
+ return {
278
+ expression: candidate,
279
+ endPos: startPos + errorPos
280
+ };
281
+ }
282
+ catch {
174
283
  return null;
175
284
  }
176
285
  }
177
286
  }
178
- catch (e) {
179
- const message = e instanceof Error ? e.message : String(e);
180
- throw new Error(`Failed to parse expression "${expr}": ${message}`);
287
+ static parsePipeIdentifier(str, startPos) {
288
+ let pos = startPos;
289
+ while (pos < str.length && /\s/.test(str[pos])) {
290
+ pos++;
291
+ }
292
+ if (pos >= str.length)
293
+ return null;
294
+ const firstChar = str[pos];
295
+ if (!/[a-zA-Z_$]/.test(firstChar))
296
+ return null;
297
+ let name = firstChar;
298
+ pos++;
299
+ while (pos < str.length && /[a-zA-Z0-9_$]/.test(str[pos])) {
300
+ name += str[pos];
301
+ pos++;
302
+ }
303
+ return { name, endPos: pos };
181
304
  }
182
- }
183
- export function parseInterpolations(text) {
184
- const results = [];
185
- let searchStart = 0;
186
- while (searchStart < text.length) {
187
- const openIdx = text.indexOf('{{', searchStart);
188
- if (openIdx === -1)
189
- break;
190
- let foundValid = false;
191
- for (let closeIdx = openIdx + 3; closeIdx <= text.length; closeIdx++) {
192
- if (text[closeIdx - 2] !== '}' || text[closeIdx - 1] !== '}')
193
- continue;
194
- const candidate = text.slice(openIdx + 2, closeIdx - 2)
195
- .trim();
196
- if (!candidate)
197
- continue;
198
- const { expression: baseExpr } = parsePipedExpression(candidate);
199
- try {
200
- parse(baseExpr, { sourceType: 'module' });
201
- results.push({
202
- start: openIdx, end: closeIdx, expr: candidate
203
- });
204
- searchStart = closeIdx;
205
- foundValid = true;
305
+ static parsePrimaryExpression(text, startPos) {
306
+ const offset = startPos ?? 0;
307
+ const pipes = [];
308
+ const tokenResult = ExpressionTransformer.tokenizeExpression(text, offset);
309
+ if (tokenResult.tokenCount === 0) {
310
+ return {
311
+ expression: text.slice(offset)
312
+ .trim(), pipes: [], endPos: text.length
313
+ };
314
+ }
315
+ const baseCandidate = text.slice(offset, tokenResult.index)
316
+ .trim();
317
+ const baseResult = ExpressionTransformer.parseSecondaryExpression(baseCandidate);
318
+ if (!baseResult) {
319
+ return { expression: baseCandidate, pipes: [], endPos: tokenResult.index };
320
+ }
321
+ const baseExpression = baseResult.expression;
322
+ if (tokenResult.stopReason !== 'delimiter') {
323
+ return { expression: baseExpression, pipes: [], endPos: tokenResult.index };
324
+ }
325
+ const delimChar = text[tokenResult.index];
326
+ if (delimChar !== '|') {
327
+ return { expression: baseExpression, pipes: [], endPos: tokenResult.index };
328
+ }
329
+ let pos = tokenResult.index + 1;
330
+ while (pos < text.length) {
331
+ const pipeId = ExpressionTransformer.parsePipeIdentifier(text, pos);
332
+ if (!pipeId || pipeId.name.length === 0)
206
333
  break;
334
+ const pipeName = pipeId.name;
335
+ const args = [];
336
+ pos = pipeId.endPos;
337
+ while (pos < text.length && /\s/.test(text[pos])) {
338
+ pos++;
207
339
  }
208
- catch {
340
+ while (pos < text.length && text[pos] === ':') {
341
+ pos++;
342
+ while (pos < text.length && /\s/.test(text[pos])) {
343
+ pos++;
344
+ }
345
+ const prefix = `_arg${args.length + 1}=`;
346
+ const argText = prefix + text.slice(pos);
347
+ const argTokenResult = ExpressionTransformer.tokenizeExpression(argText, 0);
348
+ const argEndPos = argTokenResult.index - prefix.length;
349
+ if (argTokenResult.tokenCount > 0) {
350
+ const argCandidate = text.slice(pos, pos + argEndPos)
351
+ .trim();
352
+ const argParsed = ExpressionTransformer.parseSecondaryExpression(argCandidate);
353
+ if (argParsed) {
354
+ args.push(argParsed.expression);
355
+ }
356
+ else {
357
+ args.push(argCandidate);
358
+ }
359
+ pos = pos + argEndPos;
360
+ if (argTokenResult.stopReason === 'delimiter' && argText[argTokenResult.index] === '|') {
361
+ break;
362
+ }
363
+ }
364
+ else {
365
+ break;
366
+ }
367
+ }
368
+ pipes.push({ name: pipeName, args });
369
+ while (pos < text.length && /\s/.test(text[pos])) {
370
+ pos++;
371
+ }
372
+ if (pos < text.length && text[pos] === '|') {
373
+ pos++;
374
+ }
375
+ else {
376
+ break;
209
377
  }
210
378
  }
211
- if (!foundValid) {
212
- searchStart = openIdx + 2;
213
- }
379
+ return { expression: baseExpression, pipes, endPos: pos };
214
380
  }
215
- return results;
216
- }
217
- export function transformPipedExpression(expr, addThisPrefixToExpr = true) {
218
- const { expression, pipes } = parsePipedExpression(expr);
219
- if (pipes.length === 0) {
220
- return expression;
381
+ static transformInterpolation(expr, iteratorVar) {
382
+ const localVars = iteratorVar ? [iteratorVar] : [];
383
+ const { expression: baseExpr, pipes } = ExpressionTransformer.parsePrimaryExpression(expr);
384
+ if (pipes.length > 0) {
385
+ const resultStr = ExpressionTransformer.transformExpression(baseExpr, {
386
+ addThisPrefix: true,
387
+ nullSafe: true,
388
+ localVars
389
+ });
390
+ let resultExpr = ExpressionTransformer.parseExpressionToAst(resultStr);
391
+ for (const pipe of pipes) {
392
+ const transformedArgs = pipe.args.map(arg => ExpressionTransformer.parseExpressionToAst(ExpressionTransformer.transformExpression(arg, {
393
+ addThisPrefix: true,
394
+ nullSafe: true,
395
+ localVars
396
+ })));
397
+ resultExpr = t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__pipe')), [t.stringLiteral(pipe.name), resultExpr, ...transformedArgs]);
398
+ }
399
+ const nullishExpr = t.logicalExpression('??', resultExpr, t.stringLiteral(''));
400
+ return generate(t.program([t.expressionStatement(nullishExpr)]), { compact: true })
401
+ .code
402
+ .replace(/;$/, '');
403
+ }
404
+ const transformed = ExpressionTransformer.transformExpression(expr, {
405
+ addThisPrefix: true,
406
+ nullSafe: true,
407
+ localVars
408
+ });
409
+ const transformedExpr = ExpressionTransformer.parseExpressionToAst(transformed);
410
+ const nullishExpr = t.logicalExpression('??', transformedExpr, t.stringLiteral(''));
411
+ return generate(t.program([t.expressionStatement(nullishExpr)]), { compact: true })
412
+ .code
413
+ .replace(/;$/, '');
221
414
  }
222
- let result = addThisPrefixToExpr ? transformExpression(expression, {
223
- addThisPrefix: true,
224
- nullSafe: true
225
- }) : expression;
226
- for (const pipe of pipes) {
227
- const argsStr = pipe.args.length > 0 ? ', ' + pipe.args.join(', ') : '';
228
- result = `this.__pipe('${pipe.name}', ${result}${argsStr})`;
415
+ static parseExpressionToAst(expr) {
416
+ const ast = parse(`(${expr})`, { sourceType: 'module' });
417
+ const [stmt] = ast.program.body;
418
+ if (t.isExpressionStatement(stmt)) {
419
+ return stmt.expression;
420
+ }
421
+ return t.identifier('undefined');
229
422
  }
230
- return result;
231
- }
232
- export function renameVariable(expr, oldName, newName) {
233
- try {
234
- const ast = parse(expr, { sourceType: 'module' });
235
- traverse(ast, {
236
- Identifier(path) {
237
- if (path.node.name === oldName) {
238
- if (t.isMemberExpression(path.parent) && path.parent.property === path.node && !path.parent.computed) {
239
- return;
423
+ static extractRootIdentifier(expr) {
424
+ try {
425
+ const ast = parse(`(${expr})`, { sourceType: 'module' });
426
+ const [stmt] = ast.program.body;
427
+ if (!t.isExpressionStatement(stmt))
428
+ return null;
429
+ let node = stmt.expression;
430
+ while (true) {
431
+ if (t.isIdentifier(node)) {
432
+ return node.name;
433
+ }
434
+ else if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
435
+ node = node.object;
436
+ }
437
+ else if (t.isCallExpression(node) || t.isOptionalCallExpression(node)) {
438
+ node = node.callee;
439
+ }
440
+ else if (t.isBinaryExpression(node) || t.isLogicalExpression(node)) {
441
+ node = node.left;
442
+ }
443
+ else if (t.isUnaryExpression(node)) {
444
+ node = node.argument;
445
+ }
446
+ else if (t.isConditionalExpression(node)) {
447
+ node = node.test;
448
+ }
449
+ else if (t.isAwaitExpression(node)) {
450
+ node = node.argument;
451
+ }
452
+ else if (t.isAssignmentExpression(node)) {
453
+ node = node.left;
454
+ }
455
+ else if (t.isParenthesizedExpression(node)) {
456
+ node = node.expression;
457
+ }
458
+ else if (t.isObjectExpression(node)) {
459
+ let foundSpread = undefined;
460
+ for (const prop of node.properties) {
461
+ if (t.isSpreadElement(prop)) {
462
+ foundSpread = prop;
463
+ break;
464
+ }
465
+ }
466
+ if (foundSpread) {
467
+ node = foundSpread.argument;
468
+ }
469
+ else {
470
+ return null;
240
471
  }
241
- path.node.name = newName;
472
+ }
473
+ else {
474
+ return null;
242
475
  }
243
476
  }
244
- });
245
- let { code } = generate(ast, { compact: false });
246
- if (code.endsWith(';'))
247
- code = code.slice(0, -1);
248
- return code;
477
+ }
478
+ catch (e) {
479
+ const message = ErrorHelpers.getErrorMessage(e);
480
+ throw new Error(`Failed to parse expression "${expr}": ${message}`);
481
+ }
249
482
  }
250
- catch (e) {
251
- const message = e instanceof Error ? e.message : String(e);
252
- throw new Error(`Failed to parse expression "${expr}": ${message}`);
483
+ static parseInterpolations(text) {
484
+ const results = [];
485
+ let searchStart = 0;
486
+ while (searchStart < text.length) {
487
+ const openIdx = text.indexOf('{{', searchStart);
488
+ if (openIdx === -1)
489
+ break;
490
+ let foundValid = false;
491
+ for (let closeIdx = openIdx + 3; closeIdx <= text.length; closeIdx++) {
492
+ if (text[closeIdx - 2] !== '}' || text[closeIdx - 1] !== '}')
493
+ continue;
494
+ const candidate = text.slice(openIdx + 2, closeIdx - 2)
495
+ .trim();
496
+ if (!candidate)
497
+ continue;
498
+ const { expression: baseExpr } = ExpressionTransformer.parsePrimaryExpression(candidate);
499
+ try {
500
+ parse(`(${baseExpr})`, { sourceType: 'module' });
501
+ results.push({
502
+ start: openIdx, end: closeIdx, expr: candidate
503
+ });
504
+ searchStart = closeIdx;
505
+ foundValid = true;
506
+ break;
507
+ }
508
+ catch {
509
+ }
510
+ }
511
+ if (!foundValid) {
512
+ searchStart = openIdx + 2;
513
+ }
514
+ }
515
+ return results;
253
516
  }
254
- }
255
- export function expressionUsesVariable(expr, varName) {
256
- try {
257
- const ast = parse(expr, { sourceType: 'module' });
258
- let found = false;
259
- traverse(ast, {
260
- Identifier(path) {
261
- if (path.node.name === varName) {
262
- if (t.isMemberExpression(path.parent) && path.parent.property === path.node && !path.parent.computed) {
263
- return;
517
+ static transformPipedExpression(expr, addThisPrefixToExpr = true) {
518
+ const { expression, pipes } = ExpressionTransformer.parsePrimaryExpression(expr);
519
+ if (pipes.length === 0) {
520
+ return expression;
521
+ }
522
+ const resultStr = addThisPrefixToExpr ? ExpressionTransformer.transformExpression(expression, {
523
+ addThisPrefix: true,
524
+ nullSafe: true
525
+ }) : expression;
526
+ let resultExpr = ExpressionTransformer.parseExpressionToAst(resultStr);
527
+ for (const pipe of pipes) {
528
+ const transformedArgs = addThisPrefixToExpr
529
+ ? pipe.args.map(arg => ExpressionTransformer.parseExpressionToAst(ExpressionTransformer.transformExpression(arg, {
530
+ addThisPrefix: true,
531
+ nullSafe: true
532
+ })))
533
+ : pipe.args.map(arg => ExpressionTransformer.parseExpressionToAst(arg));
534
+ resultExpr = t.callExpression(t.memberExpression(t.thisExpression(), t.identifier('__pipe')), [t.stringLiteral(pipe.name), resultExpr, ...transformedArgs]);
535
+ }
536
+ return generate(t.program([t.expressionStatement(resultExpr)]), { compact: true })
537
+ .code
538
+ .replace(/;$/, '');
539
+ }
540
+ static renameVariable(expr, oldName, newName) {
541
+ try {
542
+ const ast = parse(expr, { sourceType: 'module' });
543
+ traverse(ast, {
544
+ Identifier(path) {
545
+ if (path.node.name === oldName) {
546
+ if (t.isMemberExpression(path.parent) && path.parent.property === path.node && !path.parent.computed) {
547
+ return;
548
+ }
549
+ path.node.name = newName;
264
550
  }
265
- found = true;
266
- path.stop();
267
551
  }
268
- }
269
- });
270
- return found;
552
+ });
553
+ let { code } = generate(ast, { compact: false });
554
+ if (code.endsWith(';'))
555
+ code = code.slice(0, -1);
556
+ return code;
557
+ }
558
+ catch (e) {
559
+ const message = ErrorHelpers.getErrorMessage(e);
560
+ throw new Error(`Failed to parse expression "${expr}": ${message}`);
561
+ }
271
562
  }
272
- catch (e) {
273
- const message = e instanceof Error ? e.message : String(e);
274
- throw new Error(`Failed to parse expression "${expr}": ${message}`);
563
+ static expressionUsesVariable(expr, varName) {
564
+ try {
565
+ const { expression: baseExpr } = ExpressionTransformer.parsePrimaryExpression(expr);
566
+ const ast = parse(`(${baseExpr})`, { sourceType: 'module' });
567
+ let found = false;
568
+ traverse(ast, {
569
+ Identifier(path) {
570
+ if (path.node.name === varName) {
571
+ if (t.isMemberExpression(path.parent) && path.parent.property === path.node && !path.parent.computed) {
572
+ return;
573
+ }
574
+ found = true;
575
+ path.stop();
576
+ }
577
+ }
578
+ });
579
+ return found;
580
+ }
581
+ catch (e) {
582
+ const message = ErrorHelpers.getErrorMessage(e);
583
+ throw new Error(`Failed to parse expression "${expr}": ${message}`);
584
+ }
585
+ }
586
+ static replaceThisExpression(expr, replacement) {
587
+ try {
588
+ const ast = parse(expr, { sourceType: 'module' });
589
+ traverse(ast, {
590
+ ThisExpression(path) {
591
+ path.replaceWith(t.identifier(replacement));
592
+ }
593
+ });
594
+ let { code } = generate(ast, { compact: false });
595
+ if (code.endsWith(';'))
596
+ code = code.slice(0, -1);
597
+ return code;
598
+ }
599
+ catch (e) {
600
+ const message = ErrorHelpers.getErrorMessage(e);
601
+ throw new Error(`Failed to parse expression "${expr}": ${message}`);
602
+ }
275
603
  }
276
604
  }