@fluffjs/cli 0.0.8 → 0.1.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/BabelHelpers.d.ts +26 -0
- package/BabelHelpers.js +65 -0
- package/Cli.d.ts +7 -10
- package/Cli.js +139 -52
- package/CodeGenerator.d.ts +53 -39
- package/CodeGenerator.js +330 -725
- package/ComponentCompiler.d.ts +14 -16
- package/ComponentCompiler.js +193 -257
- package/DomPreProcessor.d.ts +36 -0
- package/DomPreProcessor.js +645 -0
- package/ErrorHelpers.d.ts +5 -0
- package/ErrorHelpers.js +8 -0
- package/ExpressionTransformer.d.ts +38 -28
- package/ExpressionTransformer.js +558 -230
- package/Generator.d.ts +1 -5
- package/Generator.js +128 -67
- package/GetterDependencyExtractor.d.ts +4 -0
- package/GetterDependencyExtractor.js +73 -0
- package/IndexHtmlTransformer.d.ts +6 -7
- package/IndexHtmlTransformer.js +82 -88
- package/Parse5Helpers.d.ts +17 -0
- package/Parse5Helpers.js +95 -0
- package/TemplateParser.d.ts +39 -21
- package/TemplateParser.js +462 -268
- package/Typeguards.d.ts +24 -0
- package/Typeguards.js +30 -0
- package/babel-plugin-class-transform.d.ts +3 -18
- package/babel-plugin-class-transform.js +3 -11
- package/babel-plugin-component.d.ts +4 -13
- package/babel-plugin-component.js +7 -0
- package/babel-plugin-imports.d.ts +3 -11
- package/babel-plugin-imports.js +5 -31
- package/babel-plugin-reactive.d.ts +2 -19
- package/babel-plugin-reactive.js +21 -76
- package/bin.js +2 -2
- package/fluff-esbuild-plugin.d.ts +2 -5
- package/fluff-esbuild-plugin.js +4 -1
- package/index.d.ts +6 -2
- package/index.js +1 -1
- package/interfaces/BabelPluginClassTransformState.d.ts +5 -0
- package/interfaces/BabelPluginComponentState.d.ts +4 -0
- package/interfaces/BabelPluginComponentState.js +1 -0
- package/interfaces/BabelPluginImportsState.d.ts +5 -0
- package/interfaces/BabelPluginImportsState.js +1 -0
- package/interfaces/BabelPluginReactiveState.d.ts +13 -0
- package/interfaces/BabelPluginReactiveState.js +1 -0
- package/interfaces/BabelPluginReactiveWatchCallInfo.d.ts +7 -0
- package/interfaces/BabelPluginReactiveWatchCallInfo.js +1 -0
- package/interfaces/BabelPluginReactiveWatchInfo.d.ts +5 -0
- package/interfaces/BabelPluginReactiveWatchInfo.js +1 -0
- package/interfaces/BabelToken.d.ts +8 -0
- package/interfaces/BabelToken.js +1 -0
- package/interfaces/BindingInfo.d.ts +12 -0
- package/interfaces/BindingInfo.js +1 -0
- package/interfaces/BreakMarkerConfig.d.ts +4 -0
- package/interfaces/BreakMarkerConfig.js +1 -0
- package/interfaces/BreakNode.d.ts +4 -0
- package/interfaces/BreakNode.js +1 -0
- package/interfaces/BundleOptions.d.ts +9 -0
- package/interfaces/BundleOptions.js +1 -0
- package/interfaces/ClassTransformOptions.d.ts +10 -0
- package/interfaces/ClassTransformOptions.js +1 -0
- package/interfaces/CliOptions.d.ts +8 -0
- package/interfaces/CliOptions.js +1 -0
- package/interfaces/CommentNode.d.ts +5 -0
- package/interfaces/CommentNode.js +1 -0
- package/interfaces/CompileResult.d.ts +6 -0
- package/interfaces/CompileResult.js +1 -0
- package/interfaces/CompilerOptions.d.ts +6 -0
- package/interfaces/CompilerOptions.js +1 -0
- package/interfaces/ComponentInfo.d.ts +8 -0
- package/interfaces/ComponentInfo.js +1 -0
- package/interfaces/ComponentMetadata.d.ts +9 -0
- package/interfaces/ComponentMetadata.js +1 -0
- package/interfaces/ControlFlow.d.ts +19 -0
- package/interfaces/ControlFlow.js +1 -0
- package/interfaces/ControlFlowNode.d.ts +6 -0
- package/interfaces/ControlFlowNode.js +1 -0
- package/interfaces/ControlFlowParseResult.d.ts +10 -0
- package/interfaces/ControlFlowParseResult.js +1 -0
- package/interfaces/ElementNode.d.ts +11 -0
- package/interfaces/ElementNode.js +1 -0
- package/interfaces/FluffConfigInterface.d.ts +7 -0
- package/interfaces/FluffConfigInterface.js +1 -0
- package/interfaces/FluffPluginOptions.d.ts +9 -0
- package/interfaces/FluffPluginOptions.js +1 -0
- package/interfaces/FluffTarget.d.ts +15 -0
- package/interfaces/FluffTarget.js +1 -0
- package/interfaces/ForMarkerConfig.d.ts +9 -0
- package/interfaces/ForMarkerConfig.js +1 -0
- package/interfaces/ForNode.d.ts +13 -0
- package/interfaces/ForNode.js +1 -0
- package/interfaces/GeneratorOptions.d.ts +5 -0
- package/interfaces/GeneratorOptions.js +1 -0
- package/interfaces/HtmlTransformOptions.d.ts +10 -0
- package/interfaces/HtmlTransformOptions.js +1 -0
- package/interfaces/IfBranch.d.ts +8 -0
- package/interfaces/IfBranch.js +1 -0
- package/interfaces/IfMarkerConfig.d.ts +8 -0
- package/interfaces/IfMarkerConfig.js +1 -0
- package/interfaces/IfNode.d.ts +7 -0
- package/interfaces/IfNode.js +1 -0
- package/interfaces/ImportTransformOptions.d.ts +7 -0
- package/interfaces/ImportTransformOptions.js +1 -0
- package/interfaces/InterpolationNode.d.ts +12 -0
- package/interfaces/InterpolationNode.js +1 -0
- package/interfaces/ParsedTemplate.d.ts +6 -0
- package/interfaces/ParsedTemplate.js +1 -0
- package/interfaces/ParsedTemplateOld.d.ts +9 -0
- package/interfaces/ParsedTemplateOld.js +1 -0
- package/interfaces/PropertyChain.d.ts +2 -0
- package/interfaces/PropertyChain.js +1 -0
- package/interfaces/Scope.d.ts +5 -0
- package/interfaces/Scope.js +1 -0
- package/interfaces/ServeOptions.d.ts +5 -0
- package/interfaces/ServeOptions.js +1 -0
- package/interfaces/SwitchCase.d.ts +8 -0
- package/interfaces/SwitchCase.js +1 -0
- package/interfaces/SwitchMarkerConfig.d.ts +11 -0
- package/interfaces/SwitchMarkerConfig.js +1 -0
- package/interfaces/SwitchNode.d.ts +10 -0
- package/interfaces/SwitchNode.js +1 -0
- package/interfaces/TemplateBinding.d.ts +10 -0
- package/interfaces/TemplateBinding.js +1 -0
- package/interfaces/TemplateNode.d.ts +7 -0
- package/interfaces/TemplateNode.js +1 -0
- package/interfaces/TextMarkerConfig.d.ts +10 -0
- package/interfaces/TextMarkerConfig.js +1 -0
- package/interfaces/TextNode.d.ts +5 -0
- package/interfaces/TextNode.js +1 -0
- package/interfaces/TokenizeResult.d.ts +6 -0
- package/interfaces/TokenizeResult.js +1 -0
- package/interfaces/TransformOptions.d.ts +11 -0
- package/interfaces/TransformOptions.js +1 -0
- package/interfaces/index.d.ts +34 -0
- package/interfaces/index.js +1 -0
- package/package.json +9 -1
- package/types/FluffConfig.d.ts +5 -27
- package/ControlFlowParser.d.ts +0 -55
- package/ControlFlowParser.js +0 -279
- package/types.d.ts +0 -46
- /package/{types.js → interfaces/BabelPluginClassTransformState.js} +0 -0
package/TemplateParser.js
CHANGED
|
@@ -1,330 +1,524 @@
|
|
|
1
|
+
import { parseExpression } from '@babel/parser';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
import he from 'he';
|
|
1
4
|
import * as parse5 from 'parse5';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
+
import { DomPreProcessor } from './DomPreProcessor.js';
|
|
6
|
+
import { ExpressionTransformer } from './ExpressionTransformer.js';
|
|
7
|
+
import { Typeguards } from './Typeguards.js';
|
|
5
8
|
export class TemplateParser {
|
|
6
9
|
bindingId = 0;
|
|
7
|
-
bindings = [];
|
|
8
|
-
controlFlows = [];
|
|
9
|
-
controlFlowParser = new ControlFlowParser();
|
|
10
10
|
templateRefs = new Set();
|
|
11
|
-
|
|
11
|
+
scopeStack = [];
|
|
12
|
+
getterDependencyMap = new Map();
|
|
13
|
+
testYieldBeforeGetterDepsLookup = null;
|
|
14
|
+
setGetterDependencyMap(map) {
|
|
15
|
+
this.getterDependencyMap = map;
|
|
16
|
+
}
|
|
17
|
+
__setTestYieldBeforeGetterDepsLookup(callback) {
|
|
18
|
+
this.testYieldBeforeGetterDepsLookup = callback;
|
|
19
|
+
}
|
|
20
|
+
async parse(html) {
|
|
12
21
|
this.bindingId = 0;
|
|
13
|
-
this.bindings = [];
|
|
14
|
-
this.controlFlows = [];
|
|
15
22
|
this.templateRefs = new Set();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
this.scopeStack = [{ variables: new Set() }];
|
|
24
|
+
const preProcessor = new DomPreProcessor();
|
|
25
|
+
const normalized = await preProcessor.process(html);
|
|
26
|
+
const fragment = parse5.parseFragment(normalized);
|
|
27
|
+
const root = await this.processNodes(fragment.childNodes);
|
|
19
28
|
return {
|
|
20
|
-
|
|
21
|
-
bindings: this.bindings,
|
|
22
|
-
controlFlows: this.controlFlows,
|
|
29
|
+
root,
|
|
23
30
|
templateRefs: Array.from(this.templateRefs)
|
|
24
31
|
};
|
|
25
32
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
36
|
-
const block = blocks[i];
|
|
37
|
-
if (!block)
|
|
38
|
-
continue;
|
|
39
|
-
const id = `l${this.bindingId++}`;
|
|
40
|
-
const processedCases = block.cases.map(c => ({
|
|
41
|
-
value: c.value,
|
|
42
|
-
content: this.processControlFlowBlocks(this.processControlFlowContent(c.content))
|
|
43
|
-
.trim(),
|
|
44
|
-
fallthrough: c.fallthrough
|
|
45
|
-
}));
|
|
46
|
-
this.controlFlows.push({
|
|
47
|
-
id, type: 'switch', expression: block.expression, cases: processedCases
|
|
48
|
-
});
|
|
49
|
-
const placeholder = `__SWITCH_BLOCK_${i}__`;
|
|
50
|
-
const replacement = `<!--${id}--><!--/${id}-->`;
|
|
51
|
-
result = result.replace(placeholder, replacement);
|
|
52
|
-
}
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
processForBlocksNew(html) {
|
|
56
|
-
const { result: parsed, blocks } = this.controlFlowParser.parseForBlocks(html);
|
|
57
|
-
let result = parsed;
|
|
58
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
59
|
-
const block = blocks[i];
|
|
60
|
-
if (!block)
|
|
33
|
+
async processNodes(nodes) {
|
|
34
|
+
const result = [];
|
|
35
|
+
let i = 0;
|
|
36
|
+
while (i < nodes.length) {
|
|
37
|
+
const node = nodes[i];
|
|
38
|
+
if (Typeguards.isElement(node) && node.tagName === 'x-fluff-if') {
|
|
39
|
+
const ifNode = await this.processConsolidatedIf(nodes, i);
|
|
40
|
+
result.push(ifNode.node);
|
|
41
|
+
i = ifNode.nextIndex;
|
|
61
42
|
continue;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.controlFlows.push({
|
|
68
|
-
id,
|
|
69
|
-
type: 'for',
|
|
70
|
-
iterator: block.iterator,
|
|
71
|
-
iterable: block.iterable,
|
|
72
|
-
trackBy: block.trackBy,
|
|
73
|
-
content: trimmedContent
|
|
74
|
-
});
|
|
75
|
-
const placeholder = `__FOR_BLOCK_${i}__`;
|
|
76
|
-
const replacement = isOptionContent ? `<!--for-${id}--><!--/for-${id}-->` : `<!--${id}--><!--/${id}-->`;
|
|
77
|
-
result = result.replace(placeholder, replacement);
|
|
78
|
-
}
|
|
79
|
-
return result;
|
|
80
|
-
}
|
|
81
|
-
processIfBlocksNew(html) {
|
|
82
|
-
const { result: parsed, blocks } = this.controlFlowParser.parseIfBlocks(html);
|
|
83
|
-
let result = parsed;
|
|
84
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
85
|
-
const block = blocks[i];
|
|
86
|
-
if (!block)
|
|
43
|
+
}
|
|
44
|
+
if (Typeguards.isElement(node) && node.tagName === 'x-fluff-for') {
|
|
45
|
+
const forNode = await this.processConsolidatedFor(nodes, i);
|
|
46
|
+
result.push(forNode.node);
|
|
47
|
+
i = forNode.nextIndex;
|
|
87
48
|
continue;
|
|
88
|
-
const id = `l${this.bindingId++}`;
|
|
89
|
-
let ifContent = this.processControlFlowBlocks(block.ifContent);
|
|
90
|
-
ifContent = this.processControlFlowContent(ifContent);
|
|
91
|
-
let elseContent = '';
|
|
92
|
-
if (block.elseContent) {
|
|
93
|
-
elseContent = this.processControlFlowBlocks(block.elseContent);
|
|
94
|
-
elseContent = this.processControlFlowContent(elseContent);
|
|
95
49
|
}
|
|
96
|
-
this.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
result = result.replace(placeholder, replacement);
|
|
50
|
+
const processed = await this.processNode(node);
|
|
51
|
+
if (processed) {
|
|
52
|
+
result.push(processed);
|
|
53
|
+
}
|
|
54
|
+
i++;
|
|
102
55
|
}
|
|
103
56
|
return result;
|
|
104
57
|
}
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
walkNodes(nodes, source) {
|
|
111
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
58
|
+
async processConsolidatedIf(nodes, startIndex) {
|
|
59
|
+
const branches = [];
|
|
60
|
+
let i = startIndex;
|
|
61
|
+
while (i < nodes.length) {
|
|
112
62
|
const node = nodes[i];
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
63
|
+
if (!Typeguards.isElement(node)) {
|
|
64
|
+
if (Typeguards.isTextNode(node) && node.value.trim() === '') {
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
117
67
|
}
|
|
68
|
+
break;
|
|
118
69
|
}
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
70
|
+
if (node.tagName === 'x-fluff-if' && branches.length === 0) {
|
|
71
|
+
const conditionRaw = this.getAttr(node, 'x-fluff-condition') ?? '';
|
|
72
|
+
const children = await this.processNodes(node.childNodes ?? []);
|
|
73
|
+
const conditionDeps = await this.extractDeps(conditionRaw);
|
|
74
|
+
const condition = this.transformWithLocals(conditionRaw);
|
|
75
|
+
branches.push({
|
|
76
|
+
condition,
|
|
77
|
+
conditionDeps: conditionDeps.length > 0 ? conditionDeps : undefined,
|
|
78
|
+
children
|
|
79
|
+
});
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
else if (node.tagName === 'x-fluff-else-if' && branches.length > 0) {
|
|
83
|
+
const conditionRaw = this.getAttr(node, 'x-fluff-condition') ?? '';
|
|
84
|
+
const children = await this.processNodes(node.childNodes ?? []);
|
|
85
|
+
const conditionDeps = await this.extractDeps(conditionRaw);
|
|
86
|
+
const condition = this.transformWithLocals(conditionRaw);
|
|
87
|
+
branches.push({
|
|
88
|
+
condition,
|
|
89
|
+
conditionDeps: conditionDeps.length > 0 ? conditionDeps : undefined,
|
|
90
|
+
children
|
|
91
|
+
});
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
else if (node.tagName === 'x-fluff-else' && branches.length > 0) {
|
|
95
|
+
const children = await this.processNodes(node.childNodes ?? []);
|
|
96
|
+
branches.push({
|
|
97
|
+
children
|
|
98
|
+
});
|
|
99
|
+
i++;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
break;
|
|
125
104
|
}
|
|
126
105
|
}
|
|
106
|
+
return {
|
|
107
|
+
node: {
|
|
108
|
+
type: 'if',
|
|
109
|
+
branches,
|
|
110
|
+
localVariables: this.getCurrentLocalVariables()
|
|
111
|
+
},
|
|
112
|
+
nextIndex: i
|
|
113
|
+
};
|
|
127
114
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const text = textNode.value;
|
|
133
|
-
const interpolations = parseInterpolations(text);
|
|
134
|
-
if (interpolations.length === 0) {
|
|
135
|
-
return [textNode];
|
|
115
|
+
async processConsolidatedFor(nodes, startIndex) {
|
|
116
|
+
const forElement = nodes[startIndex];
|
|
117
|
+
if (!Typeguards.isElement(forElement)) {
|
|
118
|
+
throw new Error('Expected element node');
|
|
136
119
|
}
|
|
137
|
-
const
|
|
138
|
-
let
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const transformed = transformInterpolation(expr, iteratorVar);
|
|
146
|
-
if (usesIterator) {
|
|
147
|
-
nodes.push(this.createTextNode(`\${${transformed}}`));
|
|
148
|
-
}
|
|
149
|
-
else {
|
|
150
|
-
const id = `l${this.bindingId++}`;
|
|
151
|
-
nodes.push(this.createSpanWithTextBind(transformed, id));
|
|
120
|
+
const forNode = await this.processForElement(forElement);
|
|
121
|
+
let i = startIndex + 1;
|
|
122
|
+
while (i < nodes.length) {
|
|
123
|
+
const node = nodes[i];
|
|
124
|
+
if (!Typeguards.isElement(node)) {
|
|
125
|
+
if (Typeguards.isTextNode(node) && node.value.trim() === '') {
|
|
126
|
+
i++;
|
|
127
|
+
continue;
|
|
152
128
|
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
if (node.tagName === 'x-fluff-empty') {
|
|
132
|
+
forNode.emptyContent = await this.processNodes(node.childNodes ?? []);
|
|
133
|
+
i++;
|
|
134
|
+
break;
|
|
153
135
|
}
|
|
154
136
|
else {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
node: forNode,
|
|
142
|
+
nextIndex: i
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async processNode(node) {
|
|
146
|
+
if (Typeguards.isElement(node)) {
|
|
147
|
+
return this.processElement(node);
|
|
148
|
+
}
|
|
149
|
+
else if (Typeguards.isTextNode(node)) {
|
|
150
|
+
const text = node.value.trim();
|
|
151
|
+
if (text.length === 0) {
|
|
152
|
+
return null;
|
|
160
153
|
}
|
|
161
|
-
|
|
154
|
+
return { type: 'text', content: node.value };
|
|
162
155
|
}
|
|
163
|
-
if (
|
|
164
|
-
|
|
156
|
+
else if (Typeguards.isCommentNode(node)) {
|
|
157
|
+
return { type: 'comment', content: node.data };
|
|
165
158
|
}
|
|
166
|
-
return
|
|
159
|
+
return null;
|
|
167
160
|
}
|
|
168
|
-
|
|
161
|
+
async processElement(element) {
|
|
162
|
+
const { tagName } = element;
|
|
163
|
+
if (tagName === 'x-fluff-if') {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
else if (tagName === 'x-fluff-else-if') {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
else if (tagName === 'x-fluff-else') {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
else if (tagName === 'x-fluff-for') {
|
|
173
|
+
return this.processForElement(element);
|
|
174
|
+
}
|
|
175
|
+
else if (tagName === 'x-fluff-switch') {
|
|
176
|
+
return this.processSwitchElement(element);
|
|
177
|
+
}
|
|
178
|
+
else if (tagName === 'x-fluff-case') {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
else if (tagName === 'x-fluff-default') {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
else if (tagName === 'x-fluff-empty') {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
else if (tagName === 'x-fluff-fallthrough') {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
else if (tagName === 'x-fluff-break') {
|
|
191
|
+
return { type: 'break' };
|
|
192
|
+
}
|
|
193
|
+
else if (tagName === 'x-fluff-text') {
|
|
194
|
+
return this.processTextElement(element);
|
|
195
|
+
}
|
|
196
|
+
return this.processRegularElement(element);
|
|
197
|
+
}
|
|
198
|
+
async processForElement(element) {
|
|
199
|
+
const iterator = this.getAttr(element, 'x-fluff-iterator') ?? 'item';
|
|
200
|
+
const iterableRaw = this.getAttr(element, 'x-fluff-iterable') ?? '';
|
|
201
|
+
const trackBy = this.getAttr(element, 'x-fluff-track');
|
|
202
|
+
const iterableDeps = await this.extractDeps(iterableRaw);
|
|
203
|
+
const iterable = this.transformWithLocals(iterableRaw);
|
|
204
|
+
this.pushScope([iterator, '$index']);
|
|
205
|
+
const childNodes = element.childNodes ?? [];
|
|
206
|
+
const children = [];
|
|
207
|
+
let emptyContent = undefined;
|
|
208
|
+
for (const child of childNodes) {
|
|
209
|
+
if (Typeguards.isElement(child) && child.tagName === 'x-fluff-empty') {
|
|
210
|
+
emptyContent = await this.processNodes(child.childNodes ?? []);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
const processed = await this.processNode(child);
|
|
214
|
+
if (processed) {
|
|
215
|
+
children.push(processed);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const localVariables = this.getCurrentLocalVariables();
|
|
220
|
+
this.popScope();
|
|
169
221
|
return {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
222
|
+
type: 'for',
|
|
223
|
+
iterator,
|
|
224
|
+
iterable,
|
|
225
|
+
iterableDeps: iterableDeps.length > 0 ? iterableDeps : undefined,
|
|
226
|
+
trackBy: trackBy ?? undefined,
|
|
227
|
+
emptyContent,
|
|
228
|
+
children,
|
|
229
|
+
localVariables
|
|
173
230
|
};
|
|
174
231
|
}
|
|
175
|
-
|
|
232
|
+
async processSwitchElement(element) {
|
|
233
|
+
const expressionRaw = this.getAttr(element, 'x-fluff-expr') ?? '';
|
|
234
|
+
const expressionDeps = await this.extractDeps(expressionRaw);
|
|
235
|
+
const expression = this.transformWithLocals(expressionRaw);
|
|
236
|
+
const cases = [];
|
|
237
|
+
const childNodes = element.childNodes ?? [];
|
|
238
|
+
for (const child of childNodes) {
|
|
239
|
+
if (!Typeguards.isElement(child))
|
|
240
|
+
continue;
|
|
241
|
+
if (child.tagName === 'x-fluff-case') {
|
|
242
|
+
const valueRaw = this.getAttr(child, 'x-fluff-value') ?? '';
|
|
243
|
+
const valueExpression = this.transformWithLocals(valueRaw);
|
|
244
|
+
const caseChildren = await this.processNodes(child.childNodes ?? []);
|
|
245
|
+
const hasFallthrough = this.hasChildElement(child, 'x-fluff-fallthrough');
|
|
246
|
+
const filteredChildren = caseChildren.filter(c => !(c.type === 'break'));
|
|
247
|
+
cases.push({
|
|
248
|
+
valueExpression,
|
|
249
|
+
isDefault: false,
|
|
250
|
+
fallthrough: hasFallthrough,
|
|
251
|
+
children: filteredChildren
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
else if (child.tagName === 'x-fluff-default') {
|
|
255
|
+
const caseChildren = await this.processNodes(child.childNodes ?? []);
|
|
256
|
+
const hasFallthrough = this.hasChildElement(child, 'x-fluff-fallthrough');
|
|
257
|
+
const filteredChildren = caseChildren.filter(c => !(c.type === 'break'));
|
|
258
|
+
cases.push({
|
|
259
|
+
isDefault: true,
|
|
260
|
+
fallthrough: hasFallthrough,
|
|
261
|
+
children: filteredChildren
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
176
265
|
return {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
parentNode: null
|
|
266
|
+
type: 'switch',
|
|
267
|
+
expression,
|
|
268
|
+
expressionDeps: expressionDeps.length > 0 ? expressionDeps : undefined,
|
|
269
|
+
cases,
|
|
270
|
+
localVariables: this.getCurrentLocalVariables()
|
|
183
271
|
};
|
|
184
272
|
}
|
|
185
|
-
|
|
186
|
-
|
|
273
|
+
hasChildElement(element, tagName) {
|
|
274
|
+
for (const child of element.childNodes ?? []) {
|
|
275
|
+
if (Typeguards.isElement(child) && child.tagName === tagName) {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
187
280
|
}
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
281
|
+
async processTextElement(element) {
|
|
282
|
+
const expression = this.getAttr(element, 'x-fluff-expr') ?? '';
|
|
283
|
+
const pipesAttr = this.getAttr(element, 'x-fluff-pipes');
|
|
284
|
+
const id = `l${this.bindingId++}`;
|
|
285
|
+
const deps = await this.extractDeps(expression);
|
|
286
|
+
const transformedExpression = this.transformWithLocals(expression, { eventReplacementName: '__ev' });
|
|
287
|
+
const result = {
|
|
288
|
+
type: 'interpolation',
|
|
289
|
+
expression: transformedExpression,
|
|
290
|
+
deps: deps.length > 0 ? deps : undefined,
|
|
291
|
+
id
|
|
292
|
+
};
|
|
293
|
+
if (pipesAttr) {
|
|
294
|
+
try {
|
|
295
|
+
const parsed = JSON.parse(pipesAttr);
|
|
296
|
+
if (Array.isArray(parsed)) {
|
|
297
|
+
result.pipes = parsed.map(pipe => {
|
|
298
|
+
if (!Typeguards.isRecord(pipe)) {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
if (typeof pipe.name !== 'string' || !Array.isArray(pipe.args)) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
const args = pipe.args.filter((arg) => typeof arg === 'string')
|
|
305
|
+
.map(arg => this.transformWithLocals(arg));
|
|
306
|
+
return {
|
|
307
|
+
name: pipe.name,
|
|
308
|
+
args
|
|
309
|
+
};
|
|
310
|
+
})
|
|
311
|
+
.filter((pipe) => pipe !== null);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
199
315
|
}
|
|
200
316
|
}
|
|
201
|
-
return
|
|
317
|
+
return result;
|
|
202
318
|
}
|
|
203
|
-
|
|
319
|
+
async processRegularElement(element) {
|
|
204
320
|
const bindings = [];
|
|
205
|
-
const
|
|
321
|
+
const attributes = {};
|
|
322
|
+
let hasBindings = false;
|
|
206
323
|
for (const attr of element.attrs) {
|
|
207
|
-
if (attr.name.startsWith('
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
else if (attr.name.startsWith('[') && attr.name.endsWith(']')) {
|
|
215
|
-
const prop = attr.name.slice(1, -1);
|
|
216
|
-
const expr = attr.value;
|
|
217
|
-
if (prop.startsWith('class.')) {
|
|
218
|
-
bindings.push({ type: 'class', className: prop.slice(6), expression: expr });
|
|
219
|
-
}
|
|
220
|
-
else if (prop.startsWith('style.')) {
|
|
221
|
-
bindings.push({ type: 'style', styleProp: prop.slice(6), expression: expr });
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
const dangerousProps = ['innerHTML', 'outerHTML', 'href', 'src'];
|
|
225
|
-
const isUnsafe = prop.endsWith('.unsafe');
|
|
226
|
-
const baseProp = isUnsafe ? prop.slice(0, -7) : prop;
|
|
227
|
-
if (dangerousProps.includes(baseProp) && !isUnsafe) {
|
|
228
|
-
throw new Error(`XSS Protection: [${prop}] binding is blocked. ` + `Use [${prop}.unsafe] if you understand the security implications ` + 'and have sanitized user input.');
|
|
324
|
+
if (attr.name.startsWith('x-fluff-attrib-')) {
|
|
325
|
+
const bindingInfo = await this.parseBindingAttribute(attr.value);
|
|
326
|
+
if (bindingInfo) {
|
|
327
|
+
bindings.push(bindingInfo);
|
|
328
|
+
hasBindings = true;
|
|
329
|
+
if (bindingInfo.binding === 'ref') {
|
|
330
|
+
this.templateRefs.add(bindingInfo.name);
|
|
229
331
|
}
|
|
230
|
-
bindings.push({ type: 'property', target: baseProp, expression: expr });
|
|
231
332
|
}
|
|
232
|
-
attrsToRemove.push(attr.name);
|
|
233
|
-
}
|
|
234
|
-
else if (attr.name.startsWith('(') && attr.name.endsWith(')')) {
|
|
235
|
-
const eventName = attr.name.slice(1, -1);
|
|
236
|
-
bindings.push({ type: 'event', eventName, expression: attr.value });
|
|
237
|
-
attrsToRemove.push(attr.name);
|
|
238
333
|
}
|
|
239
|
-
else
|
|
240
|
-
|
|
241
|
-
const refName = originalName.slice(1);
|
|
242
|
-
attrsToRemove.push(attr.name);
|
|
243
|
-
element.attrs.push({ name: 'data-ref', value: refName });
|
|
244
|
-
this.templateRefs.add(refName);
|
|
334
|
+
else {
|
|
335
|
+
attributes[attr.name] = attr.value;
|
|
245
336
|
}
|
|
246
337
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
element.
|
|
251
|
-
|
|
252
|
-
|
|
338
|
+
const children = await this.processNodes(element.childNodes ?? []);
|
|
339
|
+
const node = {
|
|
340
|
+
type: 'element',
|
|
341
|
+
tagName: element.tagName,
|
|
342
|
+
attributes,
|
|
343
|
+
bindings,
|
|
344
|
+
children
|
|
345
|
+
};
|
|
346
|
+
if (hasBindings) {
|
|
347
|
+
node.id = `l${this.bindingId++}`;
|
|
348
|
+
}
|
|
349
|
+
return node;
|
|
350
|
+
}
|
|
351
|
+
async parseBindingAttribute(jsonValue) {
|
|
352
|
+
try {
|
|
353
|
+
const decoded = he.decode(jsonValue);
|
|
354
|
+
const parsed = JSON.parse(decoded);
|
|
355
|
+
if (!Typeguards.isRecord(parsed)) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
if (typeof parsed.name !== 'string' || typeof parsed.binding !== 'string' || typeof parsed.expression !== 'string') {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
const validBindings = ['property', 'event', 'class', 'style', 'two-way', 'ref'];
|
|
362
|
+
const binding = validBindings.find(b => b === parsed.binding);
|
|
363
|
+
if (!binding) {
|
|
364
|
+
return null;
|
|
253
365
|
}
|
|
366
|
+
const deps = await this.extractDeps(parsed.expression);
|
|
367
|
+
const transformedExpression = this.transformWithLocals(parsed.expression, {
|
|
368
|
+
eventReplacementName: '__ev',
|
|
369
|
+
templateRefs: Array.from(this.templateRefs)
|
|
370
|
+
});
|
|
371
|
+
const result = {
|
|
372
|
+
name: parsed.name,
|
|
373
|
+
binding,
|
|
374
|
+
expression: transformedExpression,
|
|
375
|
+
deps: deps.length > 0 ? deps : undefined
|
|
376
|
+
};
|
|
377
|
+
if (typeof parsed.subscribe === 'string') {
|
|
378
|
+
result.subscribe = parsed.subscribe;
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return null;
|
|
254
384
|
}
|
|
255
385
|
}
|
|
256
|
-
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
return parse5.serialize(fragment);
|
|
386
|
+
getAttr(element, name) {
|
|
387
|
+
const attr = element.attrs.find(a => a.name === name);
|
|
388
|
+
return attr ? attr.value : null;
|
|
260
389
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
390
|
+
transformWithLocals(expression, options) {
|
|
391
|
+
const localVars = this.getCurrentLocalVariables();
|
|
392
|
+
return ExpressionTransformer.transformExpression(expression, {
|
|
393
|
+
addThisPrefix: true,
|
|
394
|
+
localVars,
|
|
395
|
+
localsObjectName: 'l',
|
|
396
|
+
eventReplacementName: options?.eventReplacementName,
|
|
397
|
+
templateRefs: options?.templateRefs
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
pushScope(variables) {
|
|
401
|
+
const parent = this.scopeStack[this.scopeStack.length - 1];
|
|
402
|
+
this.scopeStack.push({
|
|
403
|
+
variables: new Set(variables),
|
|
404
|
+
parent
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
popScope() {
|
|
408
|
+
if (this.scopeStack.length > 1) {
|
|
409
|
+
this.scopeStack.pop();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
getCurrentLocalVariables() {
|
|
413
|
+
const allVars = [];
|
|
414
|
+
for (const scope of this.scopeStack) {
|
|
415
|
+
for (const v of scope.variables) {
|
|
416
|
+
if (!allVars.includes(v)) {
|
|
417
|
+
allVars.push(v);
|
|
275
418
|
}
|
|
276
419
|
}
|
|
277
420
|
}
|
|
421
|
+
return allVars;
|
|
278
422
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
attr.value = JSON.stringify([originalProp, attr.value]);
|
|
423
|
+
async extractDeps(expression) {
|
|
424
|
+
const deps = [];
|
|
425
|
+
try {
|
|
426
|
+
const ast = parseExpression(expression);
|
|
427
|
+
const extractChain = (node) => {
|
|
428
|
+
if (t.isIdentifier(node)) {
|
|
429
|
+
return [node.name];
|
|
287
430
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
431
|
+
else if (t.isMemberExpression(node)) {
|
|
432
|
+
const objectChain = extractChain(node.object);
|
|
433
|
+
if (!objectChain)
|
|
434
|
+
return null;
|
|
435
|
+
if (node.computed) {
|
|
436
|
+
if (t.isNumericLiteral(node.property)) {
|
|
437
|
+
return [...objectChain, `[${node.property.value}]`];
|
|
438
|
+
}
|
|
439
|
+
else if (t.isStringLiteral(node.property)) {
|
|
440
|
+
return [...objectChain, `[${JSON.stringify(node.property.value)}]`];
|
|
441
|
+
}
|
|
442
|
+
else if (t.isIdentifier(node.property)) {
|
|
443
|
+
return [...objectChain, `[${node.property.name}]`];
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
else if (t.isIdentifier(node.property)) {
|
|
448
|
+
return [...objectChain, node.property.name];
|
|
449
|
+
}
|
|
294
450
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
451
|
+
return null;
|
|
452
|
+
};
|
|
453
|
+
const visited = new Set();
|
|
454
|
+
const visitNode = (node, skipMemberParts = false) => {
|
|
455
|
+
if (visited.has(node))
|
|
456
|
+
return;
|
|
457
|
+
visited.add(node);
|
|
458
|
+
if (t.isMemberExpression(node)) {
|
|
459
|
+
const chain = extractChain(node);
|
|
460
|
+
if (chain && chain.length > 0) {
|
|
461
|
+
const simplified = chain.length === 1 ? chain[0] : chain;
|
|
462
|
+
const exists = deps.some(d => JSON.stringify(d) === JSON.stringify(simplified));
|
|
463
|
+
if (!exists) {
|
|
464
|
+
deps.push(simplified);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return;
|
|
311
468
|
}
|
|
312
|
-
|
|
313
|
-
|
|
469
|
+
if (t.isIdentifier(node) && !skipMemberParts) {
|
|
470
|
+
const simplified = node.name;
|
|
471
|
+
const exists = deps.some(d => JSON.stringify(d) === JSON.stringify(simplified));
|
|
472
|
+
if (!exists) {
|
|
473
|
+
deps.push(simplified);
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (!Typeguards.isRecord(node))
|
|
478
|
+
return;
|
|
479
|
+
for (const key of Object.keys(node)) {
|
|
480
|
+
if (key === 'type' || key === 'loc' || key === 'start' || key === 'end')
|
|
481
|
+
continue;
|
|
482
|
+
const child = node[key];
|
|
483
|
+
if (Array.isArray(child)) {
|
|
484
|
+
for (const item of child) {
|
|
485
|
+
if (Typeguards.isBabelNode(item)) {
|
|
486
|
+
visitNode(item);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else if (Typeguards.isBabelNode(child)) {
|
|
491
|
+
visitNode(child);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
visitNode(ast);
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
// If parsing fails, return empty deps
|
|
499
|
+
}
|
|
500
|
+
if (this.getterDependencyMap.size > 0) {
|
|
501
|
+
const expanded = [];
|
|
502
|
+
for (const dep of deps) {
|
|
503
|
+
const firstProp = typeof dep === 'string' ? dep : (Array.isArray(dep) ? dep[0] : null);
|
|
504
|
+
if (typeof firstProp === 'string') {
|
|
505
|
+
if (this.testYieldBeforeGetterDepsLookup) {
|
|
506
|
+
await this.testYieldBeforeGetterDepsLookup();
|
|
507
|
+
}
|
|
508
|
+
const getterDeps = this.getterDependencyMap.get(firstProp);
|
|
509
|
+
if (getterDeps && getterDeps.length > 0) {
|
|
510
|
+
for (const gd of getterDeps) {
|
|
511
|
+
if (!expanded.includes(gd)) {
|
|
512
|
+
expanded.push(gd);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
expanded.push(dep);
|
|
314
519
|
}
|
|
520
|
+
return expanded;
|
|
315
521
|
}
|
|
316
|
-
|
|
317
|
-
createSpanWithTextBind(expr, id) {
|
|
318
|
-
return {
|
|
319
|
-
nodeName: 'span',
|
|
320
|
-
tagName: 'span',
|
|
321
|
-
attrs: [
|
|
322
|
-
{ name: 'data-text-bind', value: expr },
|
|
323
|
-
{ name: 'data-lid', value: id }
|
|
324
|
-
],
|
|
325
|
-
childNodes: [],
|
|
326
|
-
namespaceURI: parse5Html.NS.HTML,
|
|
327
|
-
parentNode: null
|
|
328
|
-
};
|
|
522
|
+
return deps;
|
|
329
523
|
}
|
|
330
524
|
}
|