isml-linter 5.39.1 → 5.40.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.
- package/CHANGELOG.md +1177 -1142
- package/LICENSE +21 -21
- package/README.md +245 -245
- package/bin/isml-linter.js +32 -32
- package/ismllinter.config.js +33 -33
- package/package.json +53 -53
- package/scaffold_files/ismllinter.config.js +47 -47
- package/src/Builder.js +15 -15
- package/src/Constants.js +139 -139
- package/src/IsmlLinter.js +255 -255
- package/src/enums/ParseStatus.js +5 -5
- package/src/enums/SfccTagContainer.js +287 -287
- package/src/isml_tree/ContainerNode.js +38 -38
- package/src/isml_tree/IsmlNode.js +692 -658
- package/src/isml_tree/MaskUtils.js +421 -419
- package/src/isml_tree/ParseUtils.js +515 -434
- package/src/isml_tree/TreeBuilder.js +273 -271
- package/src/publicApi.js +24 -24
- package/src/rules/line_by_line/enforce-isprint.js +53 -53
- package/src/rules/line_by_line/enforce-require.js +35 -35
- package/src/rules/line_by_line/lowercase-filename.js +29 -29
- package/src/rules/line_by_line/max-lines.js +37 -37
- package/src/rules/line_by_line/no-br.js +36 -36
- package/src/rules/line_by_line/no-git-conflict.js +43 -43
- package/src/rules/line_by_line/no-import-package.js +34 -34
- package/src/rules/line_by_line/no-inline-style.js +34 -34
- package/src/rules/line_by_line/no-isscript.js +34 -34
- package/src/rules/line_by_line/no-space-only-lines.js +47 -47
- package/src/rules/line_by_line/no-tabs.js +38 -38
- package/src/rules/line_by_line/no-trailing-spaces.js +52 -52
- package/src/rules/prototypes/RulePrototype.js +79 -79
- package/src/rules/prototypes/SingleLineRulePrototype.js +47 -47
- package/src/rules/prototypes/TreeRulePrototype.js +84 -84
- package/src/rules/tree/align-isset.js +87 -87
- package/src/rules/tree/contextual-attrs.js +105 -105
- package/src/rules/tree/custom-tags.js +54 -54
- package/src/rules/tree/disallow-tags.js +39 -39
- package/src/rules/tree/empty-eof.js +66 -66
- package/src/rules/tree/enforce-security.js +85 -85
- package/src/rules/tree/eslint-to-isscript.js +179 -179
- package/src/rules/tree/indent.js +856 -853
- package/src/rules/tree/leading-iscache.js +39 -43
- package/src/rules/tree/leading-iscontent.js +35 -39
- package/src/rules/tree/max-depth.js +54 -54
- package/src/rules/tree/no-deprecated-attrs.js +67 -67
- package/src/rules/tree/no-embedded-isml.js +17 -17
- package/src/rules/tree/no-hardcode.js +51 -51
- package/src/rules/tree/no-iselse-slash.js +35 -35
- package/src/rules/tree/no-redundant-context.js +134 -134
- package/src/rules/tree/no-require-in-loop.js +63 -63
- package/src/rules/tree/one-element-per-line.js +82 -76
- package/src/util/CommandLineUtils.js +19 -19
- package/src/util/ConfigUtils.js +219 -219
- package/src/util/ConsoleUtils.js +327 -327
- package/src/util/CustomTagContainer.js +45 -45
- package/src/util/ExceptionUtils.js +149 -136
- package/src/util/FileUtils.js +79 -79
- package/src/util/GeneralUtils.js +60 -60
- package/src/util/NativeExtensionUtils.js +6 -6
- package/src/util/RuleUtils.js +295 -295
- package/src/util/TempRuleUtils.js +232 -232
|
@@ -1,434 +1,515 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* The functions defined in this file do not create or modify a state, they
|
|
4
|
-
* simply analyze it and return relevant information;
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const Constants = require('../Constants');
|
|
9
|
-
const ExceptionUtils = require('../util/ExceptionUtils');
|
|
10
|
-
const SfccTagContainer = require('../enums/SfccTagContainer');
|
|
11
|
-
const GeneralUtils = require('../util/GeneralUtils');
|
|
12
|
-
const MaskUtils = require('./MaskUtils');
|
|
13
|
-
|
|
14
|
-
const getNextNonEmptyChar = content => {
|
|
15
|
-
return content.replace(new RegExp(Constants.EOL, 'g'), '').trim()[0];
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const getCharOccurrenceQty = (string, char) => (string.match(new RegExp(char, 'g')) || []).length;
|
|
19
|
-
|
|
20
|
-
const getLineBreakQty = string => getCharOccurrenceQty(string, Constants.EOL);
|
|
21
|
-
|
|
22
|
-
const getNextNonEmptyCharPos = content => {
|
|
23
|
-
const firstNonEmptyChar = getNextNonEmptyChar(content);
|
|
24
|
-
const index = content.indexOf(firstNonEmptyChar);
|
|
25
|
-
|
|
26
|
-
return Math.max(index, 0);
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const getLeadingEmptyChars = string => {
|
|
30
|
-
const leadingBlankSpacesQty = getNextNonEmptyCharPos(string);
|
|
31
|
-
|
|
32
|
-
return string.substring(0, leadingBlankSpacesQty);
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const getElementColumnNumber = (newElement, state) => {
|
|
36
|
-
if (newElement.value.indexOf(Constants.EOL) >= 0) {
|
|
37
|
-
const firstNonEmptyCharPos = getNextNonEmptyCharPos(newElement.value);
|
|
38
|
-
|
|
39
|
-
return firstNonEmptyCharPos === 0 ?
|
|
40
|
-
1 :
|
|
41
|
-
newElement.value
|
|
42
|
-
.substring(0, firstNonEmptyCharPos)
|
|
43
|
-
.split('').reverse().join('')
|
|
44
|
-
.indexOf(Constants.EOL) + 1;
|
|
45
|
-
|
|
46
|
-
} else if (state.elementList.length === 0) {
|
|
47
|
-
return getNextNonEmptyCharPos(newElement.value) + 1;
|
|
48
|
-
} else {
|
|
49
|
-
let columnNumber = 1;
|
|
50
|
-
|
|
51
|
-
for (let i = state.elementList.length - 1; i >= 0; i--) {
|
|
52
|
-
const element = state.elementList[i];
|
|
53
|
-
|
|
54
|
-
if (element.value.indexOf(Constants.EOL) >= 0) {
|
|
55
|
-
columnNumber += element.value.length - 1;
|
|
56
|
-
break;
|
|
57
|
-
|
|
58
|
-
} else if (i === 0) {
|
|
59
|
-
columnNumber += element.value.length + 1;
|
|
60
|
-
break;
|
|
61
|
-
|
|
62
|
-
} else {
|
|
63
|
-
columnNumber += element.value.length;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return columnNumber;
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const getLeadingLineBreakQty = string => {
|
|
72
|
-
const leadingString = getLeadingEmptyChars(string);
|
|
73
|
-
|
|
74
|
-
return this.getLineBreakQty(leadingString);
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const getTrailingEmptyCharsQty = string => {
|
|
78
|
-
const invertedString = string.split('').reverse().join('').replace(Constants.EOL, '_');
|
|
79
|
-
|
|
80
|
-
return Math.max(getLeadingEmptyChars(invertedString).length, 0);
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const checkBalance = (node, templatePath) => {
|
|
84
|
-
|
|
85
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
86
|
-
checkBalance(node.children[i]);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!node.isRoot() &&
|
|
90
|
-
node.parent && !node.parent.isContainer() &&
|
|
91
|
-
(node.isHtmlTag() || node.isIsmlTag()) &&
|
|
92
|
-
!node.isSelfClosing() && !node.tail
|
|
93
|
-
&& !node.parent.isOfType('iscomment')
|
|
94
|
-
) {
|
|
95
|
-
throw ExceptionUtils.unbalancedElementError(
|
|
96
|
-
node.getType(),
|
|
97
|
-
node.lineNumber,
|
|
98
|
-
node.globalPos,
|
|
99
|
-
node.head.trim().length,
|
|
100
|
-
templatePath
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const parseNextElement = state => {
|
|
106
|
-
const newElement = getNewElement(state);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
newElement.
|
|
115
|
-
newElement.
|
|
116
|
-
newElement.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
} else
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* The functions defined in this file do not create or modify a state, they
|
|
4
|
+
* simply analyze it and return relevant information;
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const Constants = require('../Constants');
|
|
9
|
+
const ExceptionUtils = require('../util/ExceptionUtils');
|
|
10
|
+
const SfccTagContainer = require('../enums/SfccTagContainer');
|
|
11
|
+
const GeneralUtils = require('../util/GeneralUtils');
|
|
12
|
+
const MaskUtils = require('./MaskUtils');
|
|
13
|
+
|
|
14
|
+
const getNextNonEmptyChar = content => {
|
|
15
|
+
return content.replace(new RegExp(Constants.EOL, 'g'), '').trim()[0];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const getCharOccurrenceQty = (string, char) => (string.match(new RegExp(char, 'g')) || []).length;
|
|
19
|
+
|
|
20
|
+
const getLineBreakQty = string => getCharOccurrenceQty(string, Constants.EOL);
|
|
21
|
+
|
|
22
|
+
const getNextNonEmptyCharPos = content => {
|
|
23
|
+
const firstNonEmptyChar = getNextNonEmptyChar(content);
|
|
24
|
+
const index = content.indexOf(firstNonEmptyChar);
|
|
25
|
+
|
|
26
|
+
return Math.max(index, 0);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getLeadingEmptyChars = string => {
|
|
30
|
+
const leadingBlankSpacesQty = getNextNonEmptyCharPos(string);
|
|
31
|
+
|
|
32
|
+
return string.substring(0, leadingBlankSpacesQty);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getElementColumnNumber = (newElement, state) => {
|
|
36
|
+
if (newElement.value.indexOf(Constants.EOL) >= 0) {
|
|
37
|
+
const firstNonEmptyCharPos = getNextNonEmptyCharPos(newElement.value);
|
|
38
|
+
|
|
39
|
+
return firstNonEmptyCharPos === 0 ?
|
|
40
|
+
1 :
|
|
41
|
+
newElement.value
|
|
42
|
+
.substring(0, firstNonEmptyCharPos)
|
|
43
|
+
.split('').reverse().join('')
|
|
44
|
+
.indexOf(Constants.EOL) + 1;
|
|
45
|
+
|
|
46
|
+
} else if (state.elementList.length === 0) {
|
|
47
|
+
return getNextNonEmptyCharPos(newElement.value) + 1;
|
|
48
|
+
} else {
|
|
49
|
+
let columnNumber = 1;
|
|
50
|
+
|
|
51
|
+
for (let i = state.elementList.length - 1; i >= 0; i--) {
|
|
52
|
+
const element = state.elementList[i];
|
|
53
|
+
|
|
54
|
+
if (element.value.indexOf(Constants.EOL) >= 0) {
|
|
55
|
+
columnNumber += element.value.length - 1;
|
|
56
|
+
break;
|
|
57
|
+
|
|
58
|
+
} else if (i === 0) {
|
|
59
|
+
columnNumber += element.value.length + 1;
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
} else {
|
|
63
|
+
columnNumber += element.value.length;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return columnNumber;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const getLeadingLineBreakQty = string => {
|
|
72
|
+
const leadingString = getLeadingEmptyChars(string);
|
|
73
|
+
|
|
74
|
+
return this.getLineBreakQty(leadingString);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const getTrailingEmptyCharsQty = string => {
|
|
78
|
+
const invertedString = string.split('').reverse().join('').replace(Constants.EOL, '_');
|
|
79
|
+
|
|
80
|
+
return Math.max(getLeadingEmptyChars(invertedString).length, 0);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const checkBalance = (node, templatePath) => {
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
86
|
+
checkBalance(node.children[i]);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!node.isRoot() &&
|
|
90
|
+
node.parent && !node.parent.isContainer() &&
|
|
91
|
+
(node.isHtmlTag() || node.isIsmlTag()) &&
|
|
92
|
+
!node.isSelfClosing() && !node.tail
|
|
93
|
+
&& !node.parent.isOfType('iscomment')
|
|
94
|
+
) {
|
|
95
|
+
throw ExceptionUtils.unbalancedElementError(
|
|
96
|
+
node.getType(),
|
|
97
|
+
node.lineNumber,
|
|
98
|
+
node.globalPos,
|
|
99
|
+
node.head.trim().length,
|
|
100
|
+
templatePath
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const parseNextElement = state => {
|
|
106
|
+
const newElement = getNewElement(state);
|
|
107
|
+
|
|
108
|
+
const trimmedElement = newElement.value.trim();
|
|
109
|
+
const previousElement = state.elementList[state.elementList.length - 1] || {};
|
|
110
|
+
const isIscommentContent = previousElement.tagType === 'iscomment' && !previousElement.isClosingTag && trimmedElement !== '</iscomment>';
|
|
111
|
+
const isIsscriptContent = previousElement.tagType === 'isscript' && !previousElement.isClosingTag && trimmedElement !== '</isscript>';
|
|
112
|
+
|
|
113
|
+
if (isIsscriptContent || isIscommentContent) {
|
|
114
|
+
newElement.lineNumber = getLineBreakQty(state.pastContent) + getLeadingLineBreakQty(newElement.value) + 1;
|
|
115
|
+
newElement.globalPos = state.pastContent.length + getLeadingEmptyChars(newElement.value).length;
|
|
116
|
+
newElement.type = 'text';
|
|
117
|
+
newElement.isSelfClosing = true;
|
|
118
|
+
|
|
119
|
+
if (state.isCrlfLineBreak && isIscommentContent) {
|
|
120
|
+
newElement.globalPos -= getLineBreakQty(newElement.value);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
trimmedElement.startsWith('<') || trimmedElement.startsWith('${') ?
|
|
124
|
+
parseTagOrExpressionElement(state, newElement) :
|
|
125
|
+
parseTextElement(state, newElement);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
newElement.columnNumber = getElementColumnNumber(newElement, state);
|
|
129
|
+
|
|
130
|
+
if (state.isCrlfLineBreak) {
|
|
131
|
+
newElement.globalPos += newElement.lineNumber - 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
state.elementList.push(newElement);
|
|
135
|
+
|
|
136
|
+
return newElement;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const parseTagOrExpressionElement = (state, newElement) => {
|
|
140
|
+
const trimmedElement = newElement.value.trim().toLowerCase();
|
|
141
|
+
const isTag = trimmedElement.startsWith('<') && !trimmedElement.startsWith('<!--');
|
|
142
|
+
const isExpression = trimmedElement.startsWith('${');
|
|
143
|
+
const isHtmlOrIsmlComment = trimmedElement.startsWith('<!--');
|
|
144
|
+
|
|
145
|
+
if (isTag) {
|
|
146
|
+
if (trimmedElement.startsWith('<is') || trimmedElement.startsWith('</is')) {
|
|
147
|
+
newElement.type = 'ismlTag';
|
|
148
|
+
} else if (trimmedElement.startsWith('<!DOCTYPE')) {
|
|
149
|
+
newElement.type = 'doctype';
|
|
150
|
+
} else {
|
|
151
|
+
newElement.type = 'htmlTag';
|
|
152
|
+
}
|
|
153
|
+
} else if (isHtmlOrIsmlComment) {
|
|
154
|
+
newElement.type = 'htmlOrIsmlComment';
|
|
155
|
+
} else if (isExpression) {
|
|
156
|
+
newElement.type = 'expression';
|
|
157
|
+
} else {
|
|
158
|
+
newElement.type = 'text';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (isTag) {
|
|
162
|
+
newElement.tagType = getElementType(trimmedElement);
|
|
163
|
+
|
|
164
|
+
newElement.isCustomTag = newElement.type === 'ismlTag' && !SfccTagContainer[newElement.tagType];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
newElement.isSelfClosing = isSelfClosing(trimmedElement);
|
|
168
|
+
|
|
169
|
+
newElement.isClosingTag = isTag && trimmedElement.startsWith('</');
|
|
170
|
+
newElement.lineNumber = getLineBreakQty(state.pastContent) + getLeadingLineBreakQty(newElement.value) + 1;
|
|
171
|
+
newElement.globalPos = state.pastContent.length + getLeadingEmptyChars(newElement.value).length;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const parseTextElement = (state, newElement) => {
|
|
175
|
+
newElement.type = 'text';
|
|
176
|
+
newElement.lineNumber = getLineBreakQty(state.pastContent.substring(0, state.pastContent.length - state.cutSpot))
|
|
177
|
+
+ getLeadingLineBreakQty(newElement.value) + 1;
|
|
178
|
+
newElement.globalPos = state.pastContent.length - state.cutSpot + getLeadingEmptyChars(newElement.value).length;
|
|
179
|
+
newElement.isSelfClosing = true;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const getElementType = trimmedElement => {
|
|
183
|
+
if (trimmedElement.startsWith('</')) {
|
|
184
|
+
const tailElementType = trimmedElement.slice(2, -1);
|
|
185
|
+
|
|
186
|
+
if (tailElementType.startsWith('${')) {
|
|
187
|
+
return 'dynamic_element';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return tailElementType;
|
|
191
|
+
} else {
|
|
192
|
+
|
|
193
|
+
const typeValueLastPos = Math.min(...[
|
|
194
|
+
trimmedElement.indexOf(' '),
|
|
195
|
+
trimmedElement.indexOf('/'),
|
|
196
|
+
trimmedElement.indexOf(Constants.EOL),
|
|
197
|
+
trimmedElement.indexOf('>')
|
|
198
|
+
].filter(j => j >= 0));
|
|
199
|
+
|
|
200
|
+
const elementType = trimmedElement.substring(1, typeValueLastPos).trim();
|
|
201
|
+
|
|
202
|
+
if (elementType.startsWith('${')) {
|
|
203
|
+
return 'dynamic_element';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return elementType;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
function isSelfClosing(trimmedElement) {
|
|
211
|
+
const ConfigUtils = require('../util/ConfigUtils');
|
|
212
|
+
|
|
213
|
+
const config = ConfigUtils.load();
|
|
214
|
+
const isTag = trimmedElement.startsWith('<') && !trimmedElement.startsWith('<!--');
|
|
215
|
+
const elementType = getElementType(trimmedElement);
|
|
216
|
+
const isDocType = trimmedElement.toLowerCase().startsWith('<!doctype ');
|
|
217
|
+
const isVoidElement = !config.disableHtml5 && Constants.voidElementsArray.indexOf(elementType) >= 0;
|
|
218
|
+
const isHtmlComment = trimmedElement.startsWith('<!--') && trimmedElement.endsWith('-->');
|
|
219
|
+
const isClosingTag = trimmedElement.endsWith('/>');
|
|
220
|
+
const isIsmlTag = trimmedElement.startsWith('<is');
|
|
221
|
+
const isStandardIsmlTag = !!SfccTagContainer[elementType];
|
|
222
|
+
const isCustomIsmlTag = isIsmlTag && !isStandardIsmlTag;
|
|
223
|
+
const isExpression = trimmedElement.startsWith('${') && trimmedElement.endsWith('}');
|
|
224
|
+
const isSfccSelfClosingTag = SfccTagContainer[elementType] && SfccTagContainer[elementType]['self-closing'];
|
|
225
|
+
|
|
226
|
+
// 'isif' tag is never self-closing;
|
|
227
|
+
if (['isif'].indexOf(elementType) >= 0) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return !!(isDocType ||
|
|
232
|
+
isVoidElement ||
|
|
233
|
+
isExpression ||
|
|
234
|
+
isHtmlComment ||
|
|
235
|
+
isTag && isClosingTag ||
|
|
236
|
+
isCustomIsmlTag ||
|
|
237
|
+
isIsmlTag && isSfccSelfClosingTag);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const getNextOpeningTagOrExpressionInitPos = content => {
|
|
241
|
+
return Math.min(...[
|
|
242
|
+
content.indexOf('<'),
|
|
243
|
+
content.indexOf('<--'),
|
|
244
|
+
content.indexOf('${')
|
|
245
|
+
].filter(j => j >= 0)) + 1;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const getNextClosingTagOrExpressionEndPos = content => {
|
|
249
|
+
return Math.min(...[
|
|
250
|
+
content.indexOf('>'),
|
|
251
|
+
content.indexOf('-->'),
|
|
252
|
+
content.indexOf('}')
|
|
253
|
+
].filter(j => j >= 0)) + 1;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const getInitialState = (templateContent, templatePath, isCrlfLineBreak) => {
|
|
257
|
+
const originalContent = GeneralUtils.toLF(templateContent);
|
|
258
|
+
const originalShadowContent = MaskUtils.maskIgnorableContent(originalContent, null, templatePath);
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
templatePath : templatePath,
|
|
262
|
+
templateName : templatePath ? path.basename(templatePath) : '',
|
|
263
|
+
originalContent : originalContent,
|
|
264
|
+
originalShadowContent : originalShadowContent,
|
|
265
|
+
remainingContent : originalContent,
|
|
266
|
+
remainingShadowContent : originalShadowContent,
|
|
267
|
+
pastContent : '',
|
|
268
|
+
elementList : [],
|
|
269
|
+
cutSpot : null,
|
|
270
|
+
isCrlfLineBreak
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const initLoopState = state => {
|
|
275
|
+
state.nextOpeningTagOrExpressionInitPos = getNextOpeningTagOrExpressionInitPos(state.remainingShadowContent);
|
|
276
|
+
state.nextClosingTagOrExpressionEndPos = getNextClosingTagOrExpressionEndPos(state.remainingShadowContent);
|
|
277
|
+
state.cutSpot = null;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const finishLoopState = state => {
|
|
281
|
+
const newElement = state.elementList[state.elementList.length - 1];
|
|
282
|
+
|
|
283
|
+
// If there is no element left (only blank spaces and / or line breaks);
|
|
284
|
+
if (!isFinite(state.nextClosingTagOrExpressionEndPos)) {
|
|
285
|
+
state.nextClosingTagOrExpressionEndPos = state.remainingShadowContent.length - 1;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (!state.cutSpot) {
|
|
289
|
+
state.remainingShadowContent = state.remainingShadowContent.substring(newElement.value.length);
|
|
290
|
+
state.remainingContent = state.remainingContent.substring(newElement.value.length);
|
|
291
|
+
state.pastContent = state.originalContent.substring(0, state.pastContent.length + newElement.value.length);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const mergeTrailingSpacesWithLastElement = state => {
|
|
296
|
+
const elementList = state.elementList;
|
|
297
|
+
const lastElement = elementList[elementList.length - 1];
|
|
298
|
+
const secondLastElement = elementList[elementList.length - 2];
|
|
299
|
+
|
|
300
|
+
if (lastElement.value.trim().length === 0) {
|
|
301
|
+
secondLastElement.value += lastElement.value;
|
|
302
|
+
elementList.pop();
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const adjustTrailingSpaces = state => {
|
|
307
|
+
|
|
308
|
+
// Note that last element is not iterated over;
|
|
309
|
+
for (let i = 0; i < state.elementList.length - 1; i++) {
|
|
310
|
+
const previousElement = i > 0 ? state.elementList[i - 1] : null;
|
|
311
|
+
const currentElement = state.elementList[i];
|
|
312
|
+
|
|
313
|
+
if (currentElement.type === 'text'
|
|
314
|
+
&& previousElement
|
|
315
|
+
&& previousElement.lineNumber !== currentElement.lineNumber
|
|
316
|
+
&& previousElement.tagType !== 'isscript'
|
|
317
|
+
) {
|
|
318
|
+
|
|
319
|
+
const trailingSpacesQty = currentElement.value
|
|
320
|
+
.replace(/\r\n/g, '_')
|
|
321
|
+
.split('')
|
|
322
|
+
.reverse()
|
|
323
|
+
.join('')
|
|
324
|
+
.search(/\S/);
|
|
325
|
+
|
|
326
|
+
if (trailingSpacesQty > 0) {
|
|
327
|
+
const trailingSpaces = currentElement.value.slice(-trailingSpacesQty);
|
|
328
|
+
|
|
329
|
+
currentElement.value = currentElement.value.slice(0, -trailingSpacesQty);
|
|
330
|
+
const nextElement = state.elementList[i + 1];
|
|
331
|
+
nextElement.value = trailingSpaces + nextElement.value;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// TODO Refactor this function;
|
|
338
|
+
const checkIfNextElementIsATagOrHtmlComment = (content, state) => {
|
|
339
|
+
const previousElementType = state.elementList.length > 0 && state.elementList[state.elementList.length - 1].tagType;
|
|
340
|
+
const isIscommentContent = previousElementType === 'iscomment';
|
|
341
|
+
const isIsscriptContent = previousElementType === 'isscript';
|
|
342
|
+
const isScriptContent = previousElementType === 'script';
|
|
343
|
+
|
|
344
|
+
return !isIscommentContent && !isScriptContent && !isIsscriptContent && content.startsWith('<') && content.substring(1).match(/^[A-z]/i) || content.startsWith('</') || content.startsWith('<!');
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const getWrapperTagContent = (state, wrapperTagType) => {
|
|
348
|
+
for (let i = 0; i < state.remainingContent.length; i++) {
|
|
349
|
+
const remainingString = state.remainingContent.substring(i);
|
|
350
|
+
|
|
351
|
+
if (remainingString.startsWith(`</${wrapperTagType}>`)) {
|
|
352
|
+
return state.remainingContent.substring(0, i);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return state.remainingContent;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const checkIfCurrentElementWrappedByTag = (state, wrapperTagType) => {
|
|
360
|
+
let depth = 0;
|
|
361
|
+
|
|
362
|
+
for (let i = state.elementList.length - 1; i >= 0 ; i--) {
|
|
363
|
+
const element = state.elementList[i];
|
|
364
|
+
|
|
365
|
+
if (element.tagType === wrapperTagType) {
|
|
366
|
+
depth += element.isClosingTag ? -1 : 1;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return depth > 0 && !state.remainingContent.trimStart().startsWith(`</${wrapperTagType}>`);
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const getTextLastContiguousMaskedCharPos = (state, isNextElementATag, isNextElementAnExpression) => {
|
|
374
|
+
const localMaskedContent0 = MaskUtils.maskExpressionContent(state.remainingContent);
|
|
375
|
+
const localMaskedContent1 = MaskUtils.maskInBetween(localMaskedContent0, '<', '>');
|
|
376
|
+
|
|
377
|
+
for (let i = 0; i < localMaskedContent1.length; i++) {
|
|
378
|
+
if (isNextElementATag && localMaskedContent1[i] === '>') {
|
|
379
|
+
return i + 1;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (isNextElementAnExpression && localMaskedContent1[i] === '}') {
|
|
383
|
+
return i + 1;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// TODO Refactor this function
|
|
389
|
+
const getNewElement = state => {
|
|
390
|
+
|
|
391
|
+
const trimmedContent = state.remainingContent.trimStart();
|
|
392
|
+
const isWithinIscomment = checkIfCurrentElementWrappedByTag(state, 'iscomment');
|
|
393
|
+
const isWithinIsscript = checkIfCurrentElementWrappedByTag(state, 'isscript');
|
|
394
|
+
const isNextElementATag = trimmedContent.startsWith('<');
|
|
395
|
+
const isNextElementAnExpression = trimmedContent.startsWith('${');
|
|
396
|
+
const isTextElement = !isNextElementATag && !isNextElementAnExpression;
|
|
397
|
+
let lastContiguousMaskedCharPos;
|
|
398
|
+
let elementValue;
|
|
399
|
+
|
|
400
|
+
if (isWithinIscomment) {
|
|
401
|
+
elementValue = getWrapperTagContent(state, 'iscomment');
|
|
402
|
+
|
|
403
|
+
} else if (isWithinIsscript) {
|
|
404
|
+
elementValue = getWrapperTagContent(state, 'isscript');
|
|
405
|
+
|
|
406
|
+
} else if (isTextElement) {
|
|
407
|
+
|
|
408
|
+
for (let i = 0; i < state.remainingContent.length; i++) {
|
|
409
|
+
const remainingString = state.remainingContent.substring(i);
|
|
410
|
+
const isNextElementATagOrHtmlComment = checkIfNextElementIsATagOrHtmlComment(remainingString, state);
|
|
411
|
+
|
|
412
|
+
if (isNextElementATagOrHtmlComment || remainingString.startsWith('${')) {
|
|
413
|
+
lastContiguousMaskedCharPos = i;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
elementValue = state.remainingContent.substring(0, lastContiguousMaskedCharPos);
|
|
419
|
+
} else {
|
|
420
|
+
if (state.elementList.length > 0 && state.elementList[state.elementList.length - 1].type === 'text') {
|
|
421
|
+
|
|
422
|
+
lastContiguousMaskedCharPos = getTextLastContiguousMaskedCharPos(state, isNextElementATag, isNextElementAnExpression);
|
|
423
|
+
} else {
|
|
424
|
+
let remainingMaskedContent = state.remainingContent;
|
|
425
|
+
|
|
426
|
+
if (isNextElementATag) {
|
|
427
|
+
remainingMaskedContent = MaskUtils.maskExpressionContent(remainingMaskedContent);
|
|
428
|
+
remainingMaskedContent = MaskUtils.maskQuoteContent(remainingMaskedContent);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
for (let i = 0; i < remainingMaskedContent.length; i++) {
|
|
432
|
+
if (isNextElementATag && remainingMaskedContent[i] === '>') {
|
|
433
|
+
lastContiguousMaskedCharPos = i + 1;
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
elementValue = state.remainingShadowContent.startsWith('_') ?
|
|
440
|
+
state.remainingContent.substring(0, lastContiguousMaskedCharPos) :
|
|
441
|
+
state.remainingContent.substring(0, state.nextClosingTagOrExpressionEndPos);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
value : elementValue,
|
|
446
|
+
type : undefined,
|
|
447
|
+
globalPos : undefined,
|
|
448
|
+
lineNumber : undefined,
|
|
449
|
+
isSelfClosing : undefined,
|
|
450
|
+
isClosingTag : undefined,
|
|
451
|
+
tagType : undefined
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const getElementList = (templateContent, templatePath, isCrlfLineBreak) => {
|
|
456
|
+
|
|
457
|
+
const state = getInitialState(templateContent, templatePath, isCrlfLineBreak);
|
|
458
|
+
const elementList = state.elementList;
|
|
459
|
+
|
|
460
|
+
do {
|
|
461
|
+
initLoopState(state);
|
|
462
|
+
parseNextElement(state);
|
|
463
|
+
finishLoopState(state);
|
|
464
|
+
} while (state.remainingShadowContent.length > 0);
|
|
465
|
+
|
|
466
|
+
adjustTrailingSpaces(state);
|
|
467
|
+
mergeTrailingSpacesWithLastElement(state);
|
|
468
|
+
|
|
469
|
+
return elementList;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const getBlankSpaceString = length => {
|
|
473
|
+
let result = '';
|
|
474
|
+
|
|
475
|
+
for (let i = 0; i < length; i++) {
|
|
476
|
+
result += ' ';
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return result;
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
const getColumnNumber = content => {
|
|
483
|
+
const leadingContent = content.substring(0, getNextNonEmptyCharPos(content));
|
|
484
|
+
const lastLineBreakPos = leadingContent.lastIndexOf(Constants.EOL);
|
|
485
|
+
const precedingEmptySpaces = leadingContent.substring(lastLineBreakPos + 1);
|
|
486
|
+
|
|
487
|
+
return precedingEmptySpaces.length + 1;
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const getFirstEmptyCharPos = content => {
|
|
491
|
+
const firstLineBreakPos = content.indexOf(Constants.EOL);
|
|
492
|
+
const firstBlankSpacePos = content.indexOf(' ');
|
|
493
|
+
|
|
494
|
+
if (firstLineBreakPos === -1 && firstBlankSpacePos === -1) {
|
|
495
|
+
return content.length;
|
|
496
|
+
} else if (firstLineBreakPos >= 0 && firstBlankSpacePos === -1) {
|
|
497
|
+
return firstLineBreakPos;
|
|
498
|
+
} else if (firstLineBreakPos === -1 && firstBlankSpacePos >= 0) {
|
|
499
|
+
return firstBlankSpacePos;
|
|
500
|
+
} else if (firstLineBreakPos >= 0 && firstBlankSpacePos >= 0) {
|
|
501
|
+
return Math.min(firstLineBreakPos, firstBlankSpacePos);
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
module.exports.getElementList = getElementList;
|
|
506
|
+
module.exports.checkBalance = checkBalance;
|
|
507
|
+
module.exports.getLineBreakQty = getLineBreakQty;
|
|
508
|
+
module.exports.getCharOccurrenceQty = getCharOccurrenceQty;
|
|
509
|
+
module.exports.getNextNonEmptyCharPos = getNextNonEmptyCharPos;
|
|
510
|
+
module.exports.getLeadingEmptyChars = getLeadingEmptyChars;
|
|
511
|
+
module.exports.getLeadingLineBreakQty = getLeadingLineBreakQty;
|
|
512
|
+
module.exports.getTrailingEmptyCharsQty = getTrailingEmptyCharsQty;
|
|
513
|
+
module.exports.getBlankSpaceString = getBlankSpaceString;
|
|
514
|
+
module.exports.getColumnNumber = getColumnNumber;
|
|
515
|
+
module.exports.getFirstEmptyCharPos = getFirstEmptyCharPos;
|