isml-linter 5.39.0 → 5.39.4

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 (61) hide show
  1. package/CHANGELOG.md +1167 -1133
  2. package/LICENSE +21 -21
  3. package/README.md +245 -245
  4. package/bin/isml-linter.js +32 -32
  5. package/ismllinter.config.js +33 -33
  6. package/package.json +53 -53
  7. package/scaffold_files/ismllinter.config.js +47 -47
  8. package/src/Builder.js +15 -15
  9. package/src/Constants.js +139 -139
  10. package/src/IsmlLinter.js +255 -255
  11. package/src/enums/ParseStatus.js +5 -5
  12. package/src/enums/SfccTagContainer.js +287 -287
  13. package/src/isml_tree/ContainerNode.js +38 -38
  14. package/src/isml_tree/IsmlNode.js +684 -658
  15. package/src/isml_tree/MaskUtils.js +421 -419
  16. package/src/isml_tree/ParseUtils.js +514 -434
  17. package/src/isml_tree/TreeBuilder.js +273 -271
  18. package/src/publicApi.js +24 -24
  19. package/src/rules/line_by_line/enforce-isprint.js +53 -53
  20. package/src/rules/line_by_line/enforce-require.js +35 -35
  21. package/src/rules/line_by_line/lowercase-filename.js +29 -29
  22. package/src/rules/line_by_line/max-lines.js +37 -37
  23. package/src/rules/line_by_line/no-br.js +36 -36
  24. package/src/rules/line_by_line/no-git-conflict.js +43 -43
  25. package/src/rules/line_by_line/no-import-package.js +34 -34
  26. package/src/rules/line_by_line/no-inline-style.js +34 -34
  27. package/src/rules/line_by_line/no-isscript.js +34 -34
  28. package/src/rules/line_by_line/no-space-only-lines.js +47 -47
  29. package/src/rules/line_by_line/no-tabs.js +38 -38
  30. package/src/rules/line_by_line/no-trailing-spaces.js +52 -52
  31. package/src/rules/prototypes/RulePrototype.js +79 -79
  32. package/src/rules/prototypes/SingleLineRulePrototype.js +47 -47
  33. package/src/rules/prototypes/TreeRulePrototype.js +84 -84
  34. package/src/rules/tree/align-isset.js +87 -87
  35. package/src/rules/tree/contextual-attrs.js +105 -105
  36. package/src/rules/tree/custom-tags.js +54 -54
  37. package/src/rules/tree/disallow-tags.js +39 -39
  38. package/src/rules/tree/empty-eof.js +66 -66
  39. package/src/rules/tree/enforce-security.js +85 -85
  40. package/src/rules/tree/eslint-to-isscript.js +179 -179
  41. package/src/rules/tree/indent.js +853 -853
  42. package/src/rules/tree/leading-iscache.js +39 -43
  43. package/src/rules/tree/leading-iscontent.js +35 -39
  44. package/src/rules/tree/max-depth.js +54 -54
  45. package/src/rules/tree/no-deprecated-attrs.js +67 -67
  46. package/src/rules/tree/no-embedded-isml.js +17 -17
  47. package/src/rules/tree/no-hardcode.js +51 -51
  48. package/src/rules/tree/no-iselse-slash.js +35 -35
  49. package/src/rules/tree/no-redundant-context.js +134 -134
  50. package/src/rules/tree/no-require-in-loop.js +63 -63
  51. package/src/rules/tree/one-element-per-line.js +76 -76
  52. package/src/util/CommandLineUtils.js +19 -19
  53. package/src/util/ConfigUtils.js +219 -219
  54. package/src/util/ConsoleUtils.js +327 -327
  55. package/src/util/CustomTagContainer.js +45 -45
  56. package/src/util/ExceptionUtils.js +149 -131
  57. package/src/util/FileUtils.js +79 -79
  58. package/src/util/GeneralUtils.js +60 -60
  59. package/src/util/NativeExtensionUtils.js +6 -6
  60. package/src/util/RuleUtils.js +295 -295
  61. package/src/util/TempRuleUtils.js +232 -232
@@ -1,853 +1,853 @@
1
- const TreeRulePrototype = require('../prototypes/TreeRulePrototype');
2
- const ParseUtils = require('../../isml_tree/ParseUtils');
3
- const TreeBuilder = require('../../isml_tree/TreeBuilder');
4
- const Constants = require('../../Constants');
5
-
6
- const ruleId = require('path').basename(__filename).slice(0, -3);
7
- const description = 'Line incorrectly indented';
8
-
9
- const Rule = Object.create(TreeRulePrototype);
10
-
11
- Rule.init(ruleId, description);
12
-
13
- Rule.getDefaultAttrs = () => {
14
- return {
15
- size : 4,
16
- attributeOffset : 4,
17
- standAloneClosingChars : {
18
- nonSelfClosingTag : 'always',
19
- selfClosingTag : 'never',
20
- quote : 'never'
21
- }
22
- };
23
- };
24
-
25
- Rule.getIndentation = function(depth = 1) {
26
- const indentationSize = this.getConfigs().indent * depth;
27
- let indentation = '';
28
-
29
- for (let i = 0; i < indentationSize; ++i) {
30
- indentation += ' ';
31
- }
32
-
33
- return indentation;
34
- };
35
-
36
- Rule.getAttributeIndentationOffset = function() {
37
- const offsetSize = this.getConfigs().attributeOffset;
38
- let indentation = '';
39
-
40
- for (let i = 0; i < offsetSize; ++i) {
41
- indentation += ' ';
42
- }
43
-
44
- return indentation;
45
- };
46
-
47
- Rule.isBroken = function(node) {
48
-
49
- const configIndentSize = this.getConfigs().indent;
50
- const expectedIndentation = getExpectedIndentation(node, configIndentSize);
51
- const actualIndentation = getActualIndentation(node);
52
-
53
- return !node.isRoot() &&
54
- !node.isContainer() &&
55
- !node.isEmpty() &&
56
- !node.isInSameLineAsParentEnd() &&
57
- expectedIndentation !== actualIndentation &&
58
- !node.isInSameLineAsPreviousSibling();
59
- };
60
-
61
- Rule.isTailBroken = function(node) {
62
-
63
- const configIndentSize = this.getConfigs().indent;
64
- const expectedIndentation = getExpectedIndentation(node, configIndentSize);
65
- const actualIndentation = getActualTailIndentation(node);
66
- const isInSameLineAsOpeningTag = node.lineNumber === node.tailLineNumber;
67
- const isInSameLineAsLastChild = node.hasChildren() && node.getLastChild().getLastLineNumber() === node.tailLineNumber;
68
-
69
- return !node.isRoot() &&
70
- !node.isContainer() &&
71
- !node.isEmpty() &&
72
- !node.isInSameLineAsParent() &&
73
- expectedIndentation !== actualIndentation &&
74
- !isInSameLineAsLastChild &&
75
- !isInSameLineAsOpeningTag;
76
- };
77
-
78
- Rule.isClosingCharBroken = function(node) {
79
-
80
- const closingCharsConfigs = Rule.getConfigs().standAloneClosingChars;
81
- const isFirstChildInSameLine = node.hasChildren() && node.endLineNumber === node.children[0].lineNumber;
82
-
83
- if (!node.isTag()
84
- || !node.isMultiLineOpeningTag()
85
- || isFirstChildInSameLine
86
- || !closingCharsConfigs
87
- || !node.isSelfClosing() && (!closingCharsConfigs.nonSelfClosingTag || closingCharsConfigs.nonSelfClosingTag === 'any')
88
- || node.isSelfClosing() && (!closingCharsConfigs.selfClosingTag || closingCharsConfigs.selfClosingTag === 'any')
89
- ) {
90
- return {
91
- isBroken : false
92
- };
93
- }
94
-
95
- if (node.isSelfClosing()) {
96
- if (closingCharsConfigs.selfClosingTag === 'always') {
97
- const nodeHeadLineList = node.head.trim().split(Constants.EOL);
98
- const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '/>';
99
-
100
- return {
101
- isBroken : !isClosingCharStandingAlone,
102
- config : 'always'
103
- };
104
-
105
- } else if (closingCharsConfigs.selfClosingTag === 'never') {
106
- const nodeHeadLineList = node.head.trim().split(Constants.EOL);
107
- const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '/>';
108
-
109
- return {
110
- isBroken : isClosingCharStandingAlone,
111
- config : 'never'
112
- };
113
- }
114
- } else {
115
- if (closingCharsConfigs.nonSelfClosingTag === 'always') {
116
- const nodeHeadLineList = node.head.trim().split(Constants.EOL);
117
- const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '>';
118
-
119
- return {
120
- isBroken : !isClosingCharStandingAlone,
121
- config : 'always'
122
- };
123
-
124
- } else if (closingCharsConfigs.nonSelfClosingTag === 'never') {
125
- const nodeHeadLineList = node.head.trim().split(Constants.EOL);
126
- const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '>';
127
-
128
- return {
129
- isBroken : isClosingCharStandingAlone,
130
- config : 'never'
131
- };
132
- }
133
- }
134
- };
135
-
136
- Rule.isQuoteClosingCharBroken = function(node) {
137
-
138
- const quoteConfig = Rule.getConfigs().standAloneClosingChars.quote;
139
- const attributeList = node.getAttributeList();
140
- const result = [];
141
-
142
- for (let i = 0; i < attributeList.length; i++) {
143
- const attribute = attributeList[i];
144
-
145
- if (attribute.value) {
146
- const attributeValueLineList = attribute.fullContent.trim().split(Constants.EOL);
147
- const isClosingCharStandingAlone = attributeValueLineList[attributeValueLineList.length - 1].trim() === attribute.quoteChar;
148
- const lineNumber = attribute.lineNumber + ParseUtils.getLineBreakQty(attribute.fullContent);
149
- const lineList = attribute.value.split(Constants.EOL);
150
- const columnNumber = lineList[lineList.length - 1].length + 1;
151
- const globalPos = attribute.globalPos
152
- + attribute.fullContent.lastIndexOf(attribute.quoteChar)
153
- + lineNumber - 2;
154
- const formattedLineList = lineList
155
- .map( line => line.trim())
156
- .filter( line => line );
157
- const lastLine = formattedLineList[formattedLineList.length - 1];
158
-
159
- const message = quoteConfig === 'always' && !isClosingCharStandingAlone ? getStandAloneQuoteDescription(lastLine) :
160
- quoteConfig === 'never' && isClosingCharStandingAlone ? getNonStandAloneQuoteDescription(lastLine) :
161
- '';
162
-
163
- if (message) {
164
- result.push({
165
- quoteChar : attribute.quoteChar,
166
- lineNumber : lineNumber,
167
- columnNumber : columnNumber,
168
- globalPos : globalPos,
169
- length : attribute.quoteChar.length,
170
- message : message
171
- });
172
- }
173
- }
174
- }
175
-
176
- return result;
177
- };
178
-
179
- Rule.check = function(node, data) {
180
-
181
- const ruleConfig = this.getConfigs();
182
- const typeArray = ['script', 'iscomment'];
183
- let occurrenceList = [];
184
-
185
- if (node.isRoot() || !node.parent.isOneOfTypes(typeArray)) {
186
- occurrenceList = this.checkChildren(node, data);
187
-
188
- const errorGlobalPos = node.globalPos - getActualIndentation(node);
189
- const attributeErrorList = getAttributeErrorList(node);
190
-
191
- // Checks node value;
192
- if (this.isBroken(node)) {
193
- const configIndentSize = ruleConfig.indent;
194
- const expectedIndentation = getExpectedIndentation(node, configIndentSize);
195
- const actualIndentation = getActualIndentation(node);
196
- const nodeHead = node.head.trim();
197
- const occurrenceLength = actualIndentation === 0 ?
198
- nodeHead.length + ParseUtils.getLineBreakQty(nodeHead) :
199
- getActualIndentation(node);
200
-
201
- const error = this.getError(
202
- node.head.trim(),
203
- node.lineNumber,
204
- node.columnNumber,
205
- errorGlobalPos,
206
- occurrenceLength,
207
- getOccurrenceDescription(expectedIndentation, actualIndentation)
208
- );
209
-
210
- occurrenceList.push(error);
211
- }
212
-
213
- if (attributeErrorList.length > 0) {
214
- for (let i = 0; i < attributeErrorList.length; i++) {
215
- const attributeError = attributeErrorList[i];
216
- occurrenceList.push(attributeError);
217
- }
218
- }
219
-
220
- // Checks node tail value;
221
- if (node.tail && this.isTailBroken(node)) {
222
- const configIndentSize = ruleConfig.indent;
223
- const expectedIndentation = getExpectedIndentation(node, configIndentSize);
224
- const actualIndentation = getActualTailIndentation(node);
225
- const nodeTail = node.tail.trim();
226
- const occurrenceLength = actualIndentation === 0 ?
227
- nodeTail.length + ParseUtils.getLineBreakQty(nodeTail) :
228
- getActualTailIndentation(node);
229
- const tailGlobalPos = node.tailGlobalPos - getActualTailIndentation(node);
230
-
231
- const error = this.getError(
232
- node.tail.trim(),
233
- node.tailLineNumber,
234
- node.tailColumnNumber,
235
- tailGlobalPos,
236
- occurrenceLength,
237
- getOccurrenceDescription(expectedIndentation, actualIndentation)
238
- );
239
-
240
- occurrenceList.push(error);
241
- }
242
-
243
- const quoteOccurrenceList = this.isQuoteClosingCharBroken(node);
244
- occurrenceList.push(...quoteOccurrenceList);
245
-
246
- const checkResult = this.isClosingCharBroken(node);
247
- if (checkResult.isBroken) {
248
- if (node.isSelfClosing()) {
249
- const closingChar = '/>';
250
- const globalPos = node.globalPos
251
- + node.head.trim().lastIndexOf(closingChar);
252
- const lineList = node.head.split(Constants.EOL);
253
- const columnNumber = lineList[lineList.length - 1].lastIndexOf(closingChar) + 1;
254
- const message = checkResult.config === 'always' ?
255
- getStandAloneCharDescription(node.getType(), closingChar) :
256
- getNonStandAloneCharDescription(node.getType(), closingChar);
257
-
258
- const error = this.getError(
259
- node.head.trim(),
260
- node.endLineNumber,
261
- columnNumber,
262
- globalPos,
263
- closingChar.length,
264
- message
265
- );
266
-
267
- occurrenceList.push(error);
268
-
269
- } else {
270
- const closingChar = '>';
271
- const globalPos = node.globalPos
272
- + node.head.trim().length - closingChar.length;
273
- const lineList = node.head.trim().split(Constants.EOL);
274
- const columnNumber = lineList[lineList.length - 1].lastIndexOf(closingChar) + 1;
275
- const message = checkResult.config === 'always' ?
276
- getStandAloneCharDescription(node.getType(), closingChar) :
277
- getNonStandAloneCharDescription(node.getType(), closingChar);
278
-
279
- const error = this.getError(
280
- node.head.trim(),
281
- node.endLineNumber,
282
- columnNumber,
283
- globalPos,
284
- closingChar.length,
285
- message
286
- );
287
-
288
- occurrenceList.push(error);
289
- }
290
- }
291
- }
292
-
293
- return this.return(node, occurrenceList, ruleConfig);
294
- };
295
-
296
- Rule.getFixedContent = node => {
297
- if (node.isRoot() || !node.parent.isOfType('script')) {
298
- removeAllIndentation(node);
299
- addCorrectIndentation(node);
300
- }
301
-
302
- return node.toString();
303
- };
304
-
305
- /**
306
- * PRIVATE FUNCTIONS
307
- */
308
-
309
- const getAttributeValueErrorList = function(node, attribute) {
310
-
311
- if (!attribute.value) {
312
- return [];
313
- }
314
-
315
- const configIndentSize = Rule.getConfigs().indent;
316
- const configAttributeOffsetSize = Rule.getConfigs().attributeOffset;
317
- const result = [];
318
-
319
- if (attribute.hasMultilineValue) {
320
- const expectedIndentation = node.depth * configIndentSize + configAttributeOffsetSize;
321
- const attributeValueList = attribute.value
322
- .split(Constants.EOL)
323
- .filter( attr => attr.trim() && ['{', '}'].indexOf(attr.trim()) === -1);
324
-
325
- for (let i = 0; i < attributeValueList.length; i++) {
326
-
327
- if (i > 0) {
328
- const partialResult = getAttributeNestedValueError(attribute, attributeValueList, i, expectedIndentation, configAttributeOffsetSize);
329
-
330
- if (partialResult.error) {
331
- result.push(partialResult.error);
332
- }
333
-
334
- if (partialResult.shouldContinueLoop) {
335
- continue;
336
- }
337
- }
338
-
339
- const error = getAttributeValueError(attribute, attributeValueList, i, expectedIndentation);
340
-
341
- if (error) {
342
- result.push(error);
343
- }
344
- }
345
- }
346
-
347
- return result;
348
- };
349
-
350
- const getAttributeErrorList = function(node) {
351
- const configIndentSize = Rule.getConfigs().indent;
352
- const attributeList = node.getAttributeList();
353
- const result = [];
354
-
355
- for (let i = 0; i < attributeList.length; i++) {
356
- const attribute = attributeList[i];
357
-
358
- if (!attribute.isInSameLineAsTagName && attribute.isFirstInLine) {
359
- const expectedIndentation = node.depth * configIndentSize;
360
-
361
- if (attribute.columnNumber - 1 !== expectedIndentation) {
362
- const occurrenceGlobalPos = attribute.globalPos + node.lineNumber - attribute.columnNumber;
363
- const occurrenceLength = attribute.columnNumber === 1 ?
364
- attribute.length + ParseUtils.getLineBreakQty(attribute.value) :
365
- attribute.columnNumber - 1;
366
-
367
- const error = Rule.getError(
368
- attribute.fullContent,
369
- attribute.lineNumber,
370
- attribute.columnNumber,
371
- occurrenceGlobalPos,
372
- occurrenceLength,
373
- getOccurrenceDescription(expectedIndentation, attribute.columnNumber - 1)
374
- );
375
-
376
- result.push(error);
377
- }
378
- }
379
-
380
- const attributeErrorList = getAttributeValueErrorList(node, attribute);
381
- result.push(...attributeErrorList);
382
- }
383
-
384
- return result;
385
- };
386
-
387
- const removeIndentation = content => {
388
- const startingPos = ParseUtils.getNextNonEmptyCharPos(content);
389
- const endingPos = content.length - ParseUtils.getNextNonEmptyCharPos(content.split('').reverse().join(''));
390
- const fullLeadingContent = content.substring(0, startingPos);
391
- const actualContent = content.substring(startingPos, endingPos);
392
- const preLineBreakContent = fullLeadingContent.substring(0, fullLeadingContent.lastIndexOf(Constants.EOL) + 1);
393
- const fullTrailingContent = content.substring(endingPos);
394
- const lastLineBreakPos = fullTrailingContent.lastIndexOf(Constants.EOL);
395
- const trimmedTrailingContent = lastLineBreakPos === -1 ?
396
- fullTrailingContent :
397
- fullTrailingContent.substring(0, lastLineBreakPos + 1);
398
-
399
- // Removes indentation from node attributes;
400
- const indentlessContent = actualContent
401
- .split(Constants.EOL)
402
- .map(line => line.trimStart())
403
- .join(Constants.EOL);
404
-
405
- return preLineBreakContent + indentlessContent + trimmedTrailingContent;
406
- };
407
-
408
- const addIndentationToText = node => {
409
- const content = node.head;
410
- const lineArray = content
411
- .split(Constants.EOL)
412
- .filter( (line, i) => !(i === 0 && line === ''))
413
- .map(line => line.trimStart());
414
-
415
- const formattedLineArray = [];
416
- const startingPos = ParseUtils.getNextNonEmptyCharPos(content);
417
- const fullLeadingContent = content.substring(0, startingPos);
418
- const preLineBreakContent = fullLeadingContent.substring(0, fullLeadingContent.lastIndexOf(Constants.EOL) + 1);
419
- const correctIndentation = node.isInSameLineAsParent() ? '' : Rule.getIndentation(node.depth - 1);
420
-
421
- for (let i = 0; i < lineArray.length; i++) {
422
- let formattedLine = lineArray[i];
423
-
424
- if (lineArray[i].length !== 0) {
425
- formattedLine = correctIndentation + lineArray[i];
426
- }
427
-
428
- if (!(i === 0 && formattedLine.length === 0)) {
429
- formattedLineArray.push(formattedLine);
430
- }
431
- }
432
-
433
- return preLineBreakContent + formattedLineArray.join(Constants.EOL);
434
- };
435
-
436
- const getIndentedNestedIsmlContent = (attribute, nodeIndentation, attributeOffset) => {
437
- if (attribute.fullContent.startsWith('<isif')) {
438
- const attributeRootNode = TreeBuilder.parse(attribute.fullContent, null, null, true);
439
- const fixedContent = Rule.getFixedContent(attributeRootNode);
440
-
441
- return fixedContent
442
- .split(Constants.EOL)
443
- .map( (line, i) => {
444
- if (i === 0 && attribute.isFirstInLine && !attribute.isInSameLineAsTagName && attribute.index !== 0) {
445
- return Constants.EOL + nodeIndentation + attributeOffset + line;
446
- }
447
-
448
- return nodeIndentation + attributeOffset + line;
449
- })
450
- .join(Constants.EOL);
451
- }
452
- };
453
-
454
- const getTreeBuiltAttributeIndentedValue = (attribute, nodeIndentation, attributeOffset) => {
455
- const attributeRootNode = TreeBuilder.parse(attribute.value, null, null, true);
456
- const fixedContent = Rule.getFixedContent(attributeRootNode);
457
-
458
- return fixedContent
459
- .split(Constants.EOL)
460
- .map( (line, i) => {
461
-
462
- // If line contains only blank spaces;
463
- if (!line.trim()) {
464
- return '';
465
- }
466
-
467
- if (i === 0 && attribute.isFirstInLine && !attribute.isInSameLineAsTagName && attribute.index !== 0) {
468
- return Constants.EOL + nodeIndentation + attributeOffset + line;
469
- }
470
-
471
- if (i === 0 && attribute.isFirstValueInSameLineAsAttributeName) {
472
- return line;
473
- }
474
-
475
- return nodeIndentation + attributeOffset + line;
476
- })
477
- .filter( (line, i, list) => {
478
- if (i === 0 && attribute.isFirstValueInSameLineAsAttributeName && line === '') {
479
- return false;
480
- }
481
-
482
- if (i === list.length - 1 && line === '') {
483
- return false;
484
- }
485
-
486
- return i === 0 || line;
487
- })
488
- .join(Constants.EOL);
489
- };
490
-
491
- const getAttributeIndentedValues = (attribute, nodeIndentation, attributeOffset) => {
492
- let result = '';
493
-
494
- if (attribute.value) {
495
- if (attribute.value.indexOf('<is') >= 0) {
496
- result = '=' + attribute.quoteChar + getTreeBuiltAttributeIndentedValue(attribute, nodeIndentation, attributeOffset + attributeOffset);
497
-
498
- } else {
499
- result = '=' + attribute.quoteChar + attribute.value
500
- .split(Constants.EOL)
501
- .filter( value => value )
502
- .map( (value, i) => {
503
- if (i === 0) {
504
- return attribute.isFirstValueInSameLineAsAttributeName ?
505
- value :
506
- Constants.EOL + nodeIndentation + attributeOffset + attributeOffset + value;
507
- }
508
-
509
- return nodeIndentation + attributeOffset + attributeOffset + value;
510
- })
511
- .join(Constants.EOL);
512
- }
513
- }
514
-
515
- return result;
516
- };
517
-
518
-
519
- const indentAttribute = (attributeList, index, nodeIndentation, attributeOffset) => {
520
- const attribute = attributeList[index];
521
- let result = '';
522
-
523
- const isInSameLineAsPreviousAttribute = index > 0 && attributeList[index - 1].lineNumber === attribute.lineNumber;
524
-
525
- if (attribute.hasMultilineValue) {
526
- if (attribute.isNestedIsmlTag) {
527
- result += getIndentedNestedIsmlContent(attribute, nodeIndentation, attributeOffset);
528
-
529
- } else {
530
- let attributePrefix = '';
531
-
532
- if (attribute.isFirstInLine) {
533
- if (index > 0) {
534
- attributePrefix += Constants.EOL;
535
- }
536
-
537
- attributePrefix += nodeIndentation + attributeOffset;
538
- }
539
-
540
- const formattedAttributeName = attribute.isExpressionAttribute ?
541
- getExpressionAttributeIndentation(attribute.name, nodeIndentation, attributeOffset) :
542
- attribute.name;
543
-
544
- const formattedAttributeValue = getAttributeIndentedValues(attribute, nodeIndentation, attributeOffset);
545
-
546
- const closingQuote = shouldAddIndentationToClosingQuote(attribute) ?
547
- Constants.EOL + nodeIndentation + attributeOffset + attribute.quoteChar :
548
- attribute.quoteChar;
549
-
550
- const valueList = attributePrefix
551
- + (index > 0 && attribute.isInSameLineAsTagName ? ' ' : '')
552
- + formattedAttributeName
553
- + formattedAttributeValue
554
- + (attribute.isExpressionAttribute ? '' : closingQuote);
555
-
556
- result += valueList;
557
- }
558
- } else {
559
- if (isInSameLineAsPreviousAttribute) {
560
- result += ' ';
561
-
562
- } else if (!attribute.isInSameLineAsTagName) {
563
- if (index !== 0) {
564
- result += Constants.EOL;
565
- }
566
-
567
- result += nodeIndentation + attributeOffset;
568
- }
569
-
570
- result += attribute.fullContent;
571
- }
572
-
573
- return result;
574
- };
575
-
576
- const getExpressionAttributeIndentation = (attributeValue, nodeIndentation, attributeOffset) => {
577
- return attributeValue
578
- .split(Constants.EOL)
579
- .map( (line, i) => {
580
- return nodeIndentation + attributeOffset + (i > 0 ? attributeOffset : '') + line;
581
- })
582
- .join(Constants.EOL)
583
- .trimStart();
584
- };
585
-
586
- const getClosingChars = node => {
587
- const nodeHead = node.head.trim();
588
-
589
- if (nodeHead.endsWith(' />')) {
590
- return ' />';
591
- } else if (nodeHead.endsWith('/>')) {
592
- return ' />';
593
- } else if (nodeHead.endsWith(' >')) {
594
- return ' >';
595
- } else if (nodeHead.endsWith('>')) {
596
- return '>';
597
- }
598
-
599
- return '';
600
- };
601
-
602
- const addIndentation = (node, isOpeningTag) => {
603
- const content = isOpeningTag ? node.head : node.tail;
604
- const startingPos = ParseUtils.getNextNonEmptyCharPos(content);
605
- const endingPos = content.length - ParseUtils.getNextNonEmptyCharPos(content.split('').reverse().join(''));
606
- const fullLeadingContent = content.substring(0, startingPos);
607
- const preLineBreakContent = fullLeadingContent.substring(0, fullLeadingContent.lastIndexOf(Constants.EOL) + 1);
608
- const fullTrailingContent = content.substring(endingPos);
609
- const nodeIndentation = node.isInSameLineAsParent() && isOpeningTag ? '' : Rule.getIndentation(node.depth - 1);
610
- const attributeOffset = Rule.getAttributeIndentationOffset();
611
- const attributeList = node.getAttributeList();
612
- let contentResult = '';
613
-
614
- if (isOpeningTag) {
615
- const shouldAddIndentationToClosingChar = shouldAddIndentationToClosingChars(node);
616
- const closingChars = getClosingChars(node);
617
- const tagNameEndPos = ParseUtils.getLeadingLineBreakQty(node.head)
618
- + ParseUtils.getFirstEmptyCharPos(node.head.trim()) + 1;
619
-
620
- if (ParseUtils.getLineBreakQty(node.head.trim()) > 0 || shouldAddIndentationToClosingChar && node.isSelfClosing()) {
621
- contentResult = attributeList.length > 0 ?
622
- node.head.substring(0, tagNameEndPos).trimStart() :
623
- node.head.trimStart();
624
-
625
- for (let i = 0; i < attributeList.length; i++) {
626
- contentResult += indentAttribute(attributeList, i, nodeIndentation, attributeOffset);
627
- }
628
-
629
- if (attributeList.length > 0) {
630
- contentResult += shouldAddIndentationToClosingChar ?
631
- Constants.EOL + nodeIndentation + closingChars.trimStart() :
632
- closingChars;
633
- }
634
- } else {
635
- contentResult = node.head.trim();
636
- }
637
- } else {
638
- contentResult = node.tail.trim();
639
- }
640
-
641
- return preLineBreakContent + nodeIndentation + contentResult + fullTrailingContent;
642
- };
643
-
644
- const removeAllIndentation = node => {
645
- if (!node.isRoot() && !node.isContainer() && !node.parent.isOneOfTypes(['isscript', 'script'])) {
646
-
647
- const shouldRemoveHeadIndentation = node.head && !node.isInSameLineAsPreviousSibling() && !node.isInSameLineAsParent() && !(node.lineNumber === node.parent.endLineNumber);
648
- const shouldRemoveTailIndentation = node.tail && !(node.hasChildren() && node.getLastChild().lineNumber === node.tailLineNumber);
649
-
650
- if (shouldRemoveHeadIndentation) {
651
- node.head = removeIndentation(node.head);
652
- }
653
-
654
- if (shouldRemoveTailIndentation) {
655
- node.tail = removeIndentation(node.tail);
656
- }
657
- }
658
-
659
- for (let i = 0; i < node.children.length; i++) {
660
- removeAllIndentation(node.children[i]);
661
- }
662
- };
663
-
664
- const addCorrectIndentation = node => {
665
-
666
- if (!node.isRoot() && !node.isContainer() && !node.parent.isOneOfTypes(['isscript', 'script'])) {
667
- if (node.parent.isOfType('iscomment')) {
668
- const shouldAddIndentationToText = checkIfShouldAddIndentationToHead(node);
669
-
670
- if (shouldAddIndentationToText) {
671
- node.head = addIndentationToText(node);
672
- }
673
- } else {
674
- const shouldAddIndentationToHead = checkIfShouldAddIndentationToHead(node);
675
- const shouldAddIndentationToTail = checkIfShouldAddIndentationToTail(node);
676
-
677
- if (shouldAddIndentationToHead) {
678
- node.head = addIndentation(node, true);
679
- }
680
-
681
- if (shouldAddIndentationToTail) {
682
- node.tail = addIndentation(node, false);
683
- }
684
- }
685
- }
686
-
687
- for (let i = 0; i < node.children.length; i++) {
688
- addCorrectIndentation(node.children[i]);
689
- }
690
- };
691
-
692
- const shouldAddIndentationToClosingChars = node => {
693
- const closingCharsConfigs = Rule.getConfigs().standAloneClosingChars;
694
-
695
- if (node.isSelfClosing()) {
696
- if (closingCharsConfigs.selfClosingTag === 'always') {
697
- return true;
698
- } else if (closingCharsConfigs.selfClosingTag === 'never') {
699
- return false;
700
- }
701
- } else {
702
- if (closingCharsConfigs.nonSelfClosingTag === 'always') {
703
- return true;
704
- } else if (closingCharsConfigs.nonSelfClosingTag === 'never') {
705
- return false;
706
- }
707
- }
708
-
709
- const lineList = node.head.split(Constants.EOL);
710
- const lastLine = lineList[lineList.length - 1];
711
-
712
- return ['/>', '>'].indexOf(lastLine) >= 0;
713
- };
714
-
715
- const shouldAddIndentationToClosingQuote = attribute => {
716
- const closingCharsConfigs = Rule.getConfigs().standAloneClosingChars;
717
-
718
- if (!attribute.value || !closingCharsConfigs.quote || closingCharsConfigs.quote === 'always') {
719
- return true;
720
- } else if (closingCharsConfigs.quote === 'never') {
721
- return false;
722
- }
723
-
724
- const lineList = attribute.value.split(Constants.EOL);
725
- const lastLine = lineList[lineList.length - 1];
726
-
727
- return lastLine.trim().length === 0;
728
- };
729
-
730
- const checkIfShouldAddIndentationToHead = node => {
731
- const previousSibling = node.getPreviousSibling();
732
- const isInSameLineAsPrevSiblingLastLine = !node.isRoot() &&
733
- previousSibling &&
734
- node.lineNumber === previousSibling.getLastLineNumber();
735
- const isInSameLineAsParentHeadEnd = node.parent.endLineNumber === node.lineNumber && !node.parent.isContainer();
736
-
737
- const shouldAdd = !node.isRoot() &&
738
- !isInSameLineAsPrevSiblingLastLine &&
739
- !isInSameLineAsParentHeadEnd &&
740
- (node.isFirstChild() || previousSibling && node.lineNumber !== previousSibling.lineNumber) &&
741
- node.head && node.lineNumber !== node.parent.endLineNumber;
742
-
743
- return shouldAdd;
744
- };
745
-
746
- const checkIfShouldAddIndentationToTail = node => {
747
- const hasTail = !!node.tail;
748
- const isLastClause = !!node.parent && node.parent.isContainer() && !node.isLastChild();
749
- const isInSameLineAsChild = !node.hasChildren() || node.getLastChild().isInSameLineAsParent();
750
- const isTailInSameLineAsChild = !node.hasChildren() || node.tailLineNumber === node.getLastChild().getLastLineNumber();
751
- const isBrokenIntoMultipleLines = !node.hasChildren() && node.tailLineNumber && node.lineNumber !== node.tailLineNumber;
752
-
753
- const shouldAdd = hasTail &&
754
- !isTailInSameLineAsChild &&
755
- !isInSameLineAsChild &&
756
- !isLastClause
757
- ||
758
- isBrokenIntoMultipleLines;
759
-
760
- return shouldAdd;
761
- };
762
-
763
- // TODO This a workaround, it should be handled directly in ParseUtils.getElementList();
764
- const getEslintChildTrailingSpaces = node => {
765
- if (node.isOfType('isscript')) {
766
- const child = node.getLastChild();
767
-
768
- const trailingSpacesQty = child.head
769
- .replace(/\r\n/g, '_')
770
- .split('')
771
- .reverse()
772
- .join('')
773
- .search(/\S/);
774
-
775
- return trailingSpacesQty - 1;
776
- }
777
-
778
- return 0;
779
- };
780
-
781
- const getAttributeNestedValueError = (attribute, attributeValueList, i, expectedIndentation, configAttributeOffsetSize) => {
782
- const tagList = ['isif', 'iselse', 'iselseif'];
783
- let shouldContinueLoop = false;
784
-
785
- for (let j = 0; j < tagList.length; j++) {
786
- const element = tagList[j];
787
- const previousAttribute = attributeValueList[i - 1];
788
-
789
- if (previousAttribute.indexOf(element) >= 0 && previousAttribute.indexOf('</isif>') === -1) {
790
- const error = getAttributeValueError(attribute, attributeValueList, i, expectedIndentation + configAttributeOffsetSize);
791
-
792
- shouldContinueLoop = true;
793
-
794
- if (error) {
795
- return {
796
- error,
797
- shouldContinueLoop
798
- };
799
- }
800
- }
801
- }
802
-
803
- return {
804
- shouldContinueLoop
805
- };
806
- };
807
-
808
- const getAttributeValueError = (attribute, attributeValueList, i, expectedIndentation) => {
809
- const attributeValue = attributeValueList[i];
810
- const valueColumnNumber = ParseUtils.getLeadingEmptyChars(attributeValue).length;
811
- const attributeValueStartPos = attribute.value.indexOf(attributeValue);
812
- const attributeValuePrefix = attribute.value.substring(0, attributeValueStartPos);
813
- const isValueInSameLineAsAttributeName = ParseUtils.getLineBreakQty(attributeValuePrefix) === 0;
814
-
815
- if (!isValueInSameLineAsAttributeName && valueColumnNumber !== expectedIndentation) {
816
- const attributeValueFullPrefix = attribute.fullContent.substring(0, attribute.fullContent.indexOf(attributeValue.trim()));
817
- const lineBreakQty = ParseUtils.getLineBreakQty(attributeValueFullPrefix);
818
- const occurrenceGlobalPos = attribute.globalPos + attribute.fullContent.indexOf(attributeValueList[i]) + attribute.lineNumber - 1;
819
- const lineNumber = attribute.lineNumber + lineBreakQty;
820
-
821
- const occurrenceColumnNumber = valueColumnNumber === 0 ?
822
- valueColumnNumber :
823
- 0;
824
-
825
- const occurrenceLength = valueColumnNumber === 0 ?
826
- attributeValue.length :
827
- valueColumnNumber;
828
-
829
- const error = Rule.getError(
830
- attributeValue.trim(),
831
- lineNumber,
832
- occurrenceColumnNumber,
833
- occurrenceGlobalPos,
834
- occurrenceLength,
835
- getOccurrenceDescription(expectedIndentation, valueColumnNumber)
836
- );
837
-
838
- return error;
839
- }
840
-
841
- return null;
842
- };
843
-
844
- const getStandAloneQuoteDescription = lastValue => `Closing quote should not be in the same line as "${lastValue}"`;
845
- const getNonStandAloneQuoteDescription = lastValue => `Closing quote should be in the same line as "${lastValue}"`;
846
- const getStandAloneCharDescription = (tagName, closingChars) => `"${closingChars}" should not be in the same line as <${tagName}> tag last attribute`;
847
- const getNonStandAloneCharDescription = (tagName, closingChars) => `"${closingChars}" should be in the same line as <${tagName}> tag last attribute`;
848
- const getOccurrenceDescription = (expected, actual) => `Expected indentation of ${expected} spaces but found ${actual}`;
849
- const getExpectedIndentation = (node, configIndentSize) => (node.depth - 1) * configIndentSize;
850
- const getActualIndentation = node => node.getIndentationSize();
851
- const getActualTailIndentation = node => node.getTailIndentationSize() + getEslintChildTrailingSpaces(node);
852
-
853
- module.exports = Rule;
1
+ const TreeRulePrototype = require('../prototypes/TreeRulePrototype');
2
+ const ParseUtils = require('../../isml_tree/ParseUtils');
3
+ const TreeBuilder = require('../../isml_tree/TreeBuilder');
4
+ const Constants = require('../../Constants');
5
+
6
+ const ruleId = require('path').basename(__filename).slice(0, -3);
7
+ const description = 'Line incorrectly indented';
8
+
9
+ const Rule = Object.create(TreeRulePrototype);
10
+
11
+ Rule.init(ruleId, description);
12
+
13
+ Rule.getDefaultAttrs = () => {
14
+ return {
15
+ size : 4,
16
+ attributeOffset : 4,
17
+ standAloneClosingChars : {
18
+ nonSelfClosingTag : 'always',
19
+ selfClosingTag : 'never',
20
+ quote : 'never'
21
+ }
22
+ };
23
+ };
24
+
25
+ Rule.getIndentation = function(depth = 1) {
26
+ const indentationSize = this.getConfigs().indent * depth;
27
+ let indentation = '';
28
+
29
+ for (let i = 0; i < indentationSize; ++i) {
30
+ indentation += ' ';
31
+ }
32
+
33
+ return indentation;
34
+ };
35
+
36
+ Rule.getAttributeIndentationOffset = function() {
37
+ const offsetSize = this.getConfigs().attributeOffset;
38
+ let indentation = '';
39
+
40
+ for (let i = 0; i < offsetSize; ++i) {
41
+ indentation += ' ';
42
+ }
43
+
44
+ return indentation;
45
+ };
46
+
47
+ Rule.isBroken = function(node) {
48
+
49
+ const configIndentSize = this.getConfigs().indent;
50
+ const expectedIndentation = getExpectedIndentation(node, configIndentSize);
51
+ const actualIndentation = getActualIndentation(node);
52
+
53
+ return !node.isRoot() &&
54
+ !node.isContainer() &&
55
+ !node.isEmpty() &&
56
+ !node.isInSameLineAsParentEnd() &&
57
+ expectedIndentation !== actualIndentation &&
58
+ !node.isInSameLineAsPreviousSibling();
59
+ };
60
+
61
+ Rule.isTailBroken = function(node) {
62
+
63
+ const configIndentSize = this.getConfigs().indent;
64
+ const expectedIndentation = getExpectedIndentation(node, configIndentSize);
65
+ const actualIndentation = getActualTailIndentation(node);
66
+ const isInSameLineAsOpeningTag = node.lineNumber === node.tailLineNumber;
67
+ const isInSameLineAsLastChild = node.hasChildren() && node.getLastChild().getLastLineNumber() === node.tailLineNumber;
68
+
69
+ return !node.isRoot() &&
70
+ !node.isContainer() &&
71
+ !node.isEmpty() &&
72
+ !node.isInSameLineAsParent() &&
73
+ expectedIndentation !== actualIndentation &&
74
+ !isInSameLineAsLastChild &&
75
+ !isInSameLineAsOpeningTag;
76
+ };
77
+
78
+ Rule.isClosingCharBroken = function(node) {
79
+
80
+ const closingCharsConfigs = Rule.getConfigs().standAloneClosingChars;
81
+ const isFirstChildInSameLine = node.hasChildren() && node.endLineNumber === node.children[0].lineNumber;
82
+
83
+ if (!node.isTag()
84
+ || !node.isMultiLineOpeningTag()
85
+ || isFirstChildInSameLine
86
+ || !closingCharsConfigs
87
+ || !node.isSelfClosing() && (!closingCharsConfigs.nonSelfClosingTag || closingCharsConfigs.nonSelfClosingTag === 'any')
88
+ || node.isSelfClosing() && (!closingCharsConfigs.selfClosingTag || closingCharsConfigs.selfClosingTag === 'any')
89
+ ) {
90
+ return {
91
+ isBroken : false
92
+ };
93
+ }
94
+
95
+ if (node.isSelfClosing()) {
96
+ if (closingCharsConfigs.selfClosingTag === 'always') {
97
+ const nodeHeadLineList = node.head.trim().split(Constants.EOL);
98
+ const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '/>';
99
+
100
+ return {
101
+ isBroken : !isClosingCharStandingAlone,
102
+ config : 'always'
103
+ };
104
+
105
+ } else if (closingCharsConfigs.selfClosingTag === 'never') {
106
+ const nodeHeadLineList = node.head.trim().split(Constants.EOL);
107
+ const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '/>';
108
+
109
+ return {
110
+ isBroken : isClosingCharStandingAlone,
111
+ config : 'never'
112
+ };
113
+ }
114
+ } else {
115
+ if (closingCharsConfigs.nonSelfClosingTag === 'always') {
116
+ const nodeHeadLineList = node.head.trim().split(Constants.EOL);
117
+ const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '>';
118
+
119
+ return {
120
+ isBroken : !isClosingCharStandingAlone,
121
+ config : 'always'
122
+ };
123
+
124
+ } else if (closingCharsConfigs.nonSelfClosingTag === 'never') {
125
+ const nodeHeadLineList = node.head.trim().split(Constants.EOL);
126
+ const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '>';
127
+
128
+ return {
129
+ isBroken : isClosingCharStandingAlone,
130
+ config : 'never'
131
+ };
132
+ }
133
+ }
134
+ };
135
+
136
+ Rule.isQuoteClosingCharBroken = function(node) {
137
+
138
+ const quoteConfig = Rule.getConfigs().standAloneClosingChars.quote;
139
+ const attributeList = node.getAttributeList();
140
+ const result = [];
141
+
142
+ for (let i = 0; i < attributeList.length; i++) {
143
+ const attribute = attributeList[i];
144
+
145
+ if (attribute.value) {
146
+ const attributeValueLineList = attribute.fullContent.trim().split(Constants.EOL);
147
+ const isClosingCharStandingAlone = attributeValueLineList[attributeValueLineList.length - 1].trim() === attribute.quoteChar;
148
+ const lineNumber = attribute.lineNumber + ParseUtils.getLineBreakQty(attribute.fullContent);
149
+ const lineList = attribute.value.split(Constants.EOL);
150
+ const columnNumber = lineList[lineList.length - 1].length + 1;
151
+ const globalPos = attribute.globalPos
152
+ + attribute.fullContent.lastIndexOf(attribute.quoteChar)
153
+ + lineNumber - 2;
154
+ const formattedLineList = lineList
155
+ .map( line => line.trim())
156
+ .filter( line => line );
157
+ const lastLine = formattedLineList[formattedLineList.length - 1];
158
+
159
+ const message = quoteConfig === 'always' && !isClosingCharStandingAlone ? getStandAloneQuoteDescription(lastLine) :
160
+ quoteConfig === 'never' && isClosingCharStandingAlone ? getNonStandAloneQuoteDescription(lastLine) :
161
+ '';
162
+
163
+ if (message) {
164
+ result.push({
165
+ quoteChar : attribute.quoteChar,
166
+ lineNumber : lineNumber,
167
+ columnNumber : columnNumber,
168
+ globalPos : globalPos,
169
+ length : attribute.quoteChar.length,
170
+ message : message
171
+ });
172
+ }
173
+ }
174
+ }
175
+
176
+ return result;
177
+ };
178
+
179
+ Rule.check = function(node, data) {
180
+
181
+ const ruleConfig = this.getConfigs();
182
+ const typeArray = ['script', 'iscomment'];
183
+ let occurrenceList = [];
184
+
185
+ if (node.isRoot() || !node.parent.isOneOfTypes(typeArray)) {
186
+ occurrenceList = this.checkChildren(node, data);
187
+
188
+ const errorGlobalPos = node.globalPos - getActualIndentation(node);
189
+ const attributeErrorList = getAttributeErrorList(node);
190
+
191
+ // Checks node value;
192
+ if (this.isBroken(node)) {
193
+ const configIndentSize = ruleConfig.indent;
194
+ const expectedIndentation = getExpectedIndentation(node, configIndentSize);
195
+ const actualIndentation = getActualIndentation(node);
196
+ const nodeHead = node.head.trim();
197
+ const occurrenceLength = actualIndentation === 0 ?
198
+ nodeHead.length + ParseUtils.getLineBreakQty(nodeHead) :
199
+ getActualIndentation(node);
200
+
201
+ const error = this.getError(
202
+ node.head.trim(),
203
+ node.lineNumber,
204
+ node.columnNumber,
205
+ errorGlobalPos,
206
+ occurrenceLength,
207
+ getOccurrenceDescription(expectedIndentation, actualIndentation)
208
+ );
209
+
210
+ occurrenceList.push(error);
211
+ }
212
+
213
+ if (attributeErrorList.length > 0) {
214
+ for (let i = 0; i < attributeErrorList.length; i++) {
215
+ const attributeError = attributeErrorList[i];
216
+ occurrenceList.push(attributeError);
217
+ }
218
+ }
219
+
220
+ // Checks node tail value;
221
+ if (node.tail && this.isTailBroken(node)) {
222
+ const configIndentSize = ruleConfig.indent;
223
+ const expectedIndentation = getExpectedIndentation(node, configIndentSize);
224
+ const actualIndentation = getActualTailIndentation(node);
225
+ const nodeTail = node.tail.trim();
226
+ const occurrenceLength = actualIndentation === 0 ?
227
+ nodeTail.length + ParseUtils.getLineBreakQty(nodeTail) :
228
+ getActualTailIndentation(node);
229
+ const tailGlobalPos = node.tailGlobalPos - getActualTailIndentation(node);
230
+
231
+ const error = this.getError(
232
+ node.tail.trim(),
233
+ node.tailLineNumber,
234
+ node.tailColumnNumber,
235
+ tailGlobalPos,
236
+ occurrenceLength,
237
+ getOccurrenceDescription(expectedIndentation, actualIndentation)
238
+ );
239
+
240
+ occurrenceList.push(error);
241
+ }
242
+
243
+ const quoteOccurrenceList = this.isQuoteClosingCharBroken(node);
244
+ occurrenceList.push(...quoteOccurrenceList);
245
+
246
+ const checkResult = this.isClosingCharBroken(node);
247
+ if (checkResult.isBroken) {
248
+ if (node.isSelfClosing()) {
249
+ const closingChar = '/>';
250
+ const globalPos = node.globalPos
251
+ + node.head.trim().lastIndexOf(closingChar);
252
+ const lineList = node.head.split(Constants.EOL);
253
+ const columnNumber = lineList[lineList.length - 1].lastIndexOf(closingChar) + 1;
254
+ const message = checkResult.config === 'always' ?
255
+ getStandAloneCharDescription(node.getType(), closingChar) :
256
+ getNonStandAloneCharDescription(node.getType(), closingChar);
257
+
258
+ const error = this.getError(
259
+ node.head.trim(),
260
+ node.endLineNumber,
261
+ columnNumber,
262
+ globalPos,
263
+ closingChar.length,
264
+ message
265
+ );
266
+
267
+ occurrenceList.push(error);
268
+
269
+ } else {
270
+ const closingChar = '>';
271
+ const globalPos = node.globalPos
272
+ + node.head.trim().length - closingChar.length;
273
+ const lineList = node.head.trim().split(Constants.EOL);
274
+ const columnNumber = lineList[lineList.length - 1].lastIndexOf(closingChar) + 1;
275
+ const message = checkResult.config === 'always' ?
276
+ getStandAloneCharDescription(node.getType(), closingChar) :
277
+ getNonStandAloneCharDescription(node.getType(), closingChar);
278
+
279
+ const error = this.getError(
280
+ node.head.trim(),
281
+ node.endLineNumber,
282
+ columnNumber,
283
+ globalPos,
284
+ closingChar.length,
285
+ message
286
+ );
287
+
288
+ occurrenceList.push(error);
289
+ }
290
+ }
291
+ }
292
+
293
+ return this.return(node, occurrenceList, ruleConfig);
294
+ };
295
+
296
+ Rule.getFixedContent = node => {
297
+ if (node.isRoot() || !node.parent.isOfType('script')) {
298
+ removeAllIndentation(node);
299
+ addCorrectIndentation(node);
300
+ }
301
+
302
+ return node.toString();
303
+ };
304
+
305
+ /**
306
+ * PRIVATE FUNCTIONS
307
+ */
308
+
309
+ const getAttributeValueErrorList = function(node, attribute) {
310
+
311
+ if (!attribute.value) {
312
+ return [];
313
+ }
314
+
315
+ const configIndentSize = Rule.getConfigs().indent;
316
+ const configAttributeOffsetSize = Rule.getConfigs().attributeOffset;
317
+ const result = [];
318
+
319
+ if (attribute.hasMultilineValue) {
320
+ const expectedIndentation = node.depth * configIndentSize + configAttributeOffsetSize;
321
+ const attributeValueList = attribute.value
322
+ .split(Constants.EOL)
323
+ .filter( attr => attr.trim() && ['{', '}'].indexOf(attr.trim()) === -1);
324
+
325
+ for (let i = 0; i < attributeValueList.length; i++) {
326
+
327
+ if (i > 0) {
328
+ const partialResult = getAttributeNestedValueError(attribute, attributeValueList, i, expectedIndentation, configAttributeOffsetSize);
329
+
330
+ if (partialResult.error) {
331
+ result.push(partialResult.error);
332
+ }
333
+
334
+ if (partialResult.shouldContinueLoop) {
335
+ continue;
336
+ }
337
+ }
338
+
339
+ const error = getAttributeValueError(attribute, attributeValueList, i, expectedIndentation);
340
+
341
+ if (error) {
342
+ result.push(error);
343
+ }
344
+ }
345
+ }
346
+
347
+ return result;
348
+ };
349
+
350
+ const getAttributeErrorList = function(node) {
351
+ const configIndentSize = Rule.getConfigs().indent;
352
+ const attributeList = node.getAttributeList();
353
+ const result = [];
354
+
355
+ for (let i = 0; i < attributeList.length; i++) {
356
+ const attribute = attributeList[i];
357
+
358
+ if (!attribute.isInSameLineAsTagName && attribute.isFirstInLine) {
359
+ const expectedIndentation = node.depth * configIndentSize;
360
+
361
+ if (attribute.columnNumber - 1 !== expectedIndentation) {
362
+ const occurrenceGlobalPos = attribute.globalPos + node.lineNumber - attribute.columnNumber;
363
+ const occurrenceLength = attribute.columnNumber === 1 ?
364
+ attribute.length + ParseUtils.getLineBreakQty(attribute.value) :
365
+ attribute.columnNumber - 1;
366
+
367
+ const error = Rule.getError(
368
+ attribute.fullContent,
369
+ attribute.lineNumber,
370
+ attribute.columnNumber,
371
+ occurrenceGlobalPos,
372
+ occurrenceLength,
373
+ getOccurrenceDescription(expectedIndentation, attribute.columnNumber - 1)
374
+ );
375
+
376
+ result.push(error);
377
+ }
378
+ }
379
+
380
+ const attributeErrorList = getAttributeValueErrorList(node, attribute);
381
+ result.push(...attributeErrorList);
382
+ }
383
+
384
+ return result;
385
+ };
386
+
387
+ const removeIndentation = content => {
388
+ const startingPos = ParseUtils.getNextNonEmptyCharPos(content);
389
+ const endingPos = content.length - ParseUtils.getNextNonEmptyCharPos(content.split('').reverse().join(''));
390
+ const fullLeadingContent = content.substring(0, startingPos);
391
+ const actualContent = content.substring(startingPos, endingPos);
392
+ const preLineBreakContent = fullLeadingContent.substring(0, fullLeadingContent.lastIndexOf(Constants.EOL) + 1);
393
+ const fullTrailingContent = content.substring(endingPos);
394
+ const lastLineBreakPos = fullTrailingContent.lastIndexOf(Constants.EOL);
395
+ const trimmedTrailingContent = lastLineBreakPos === -1 ?
396
+ fullTrailingContent :
397
+ fullTrailingContent.substring(0, lastLineBreakPos + 1);
398
+
399
+ // Removes indentation from node attributes;
400
+ const indentlessContent = actualContent
401
+ .split(Constants.EOL)
402
+ .map(line => line.trimStart())
403
+ .join(Constants.EOL);
404
+
405
+ return preLineBreakContent + indentlessContent + trimmedTrailingContent;
406
+ };
407
+
408
+ const addIndentationToText = node => {
409
+ const content = node.head;
410
+ const lineArray = content
411
+ .split(Constants.EOL)
412
+ .filter( (line, i) => !(i === 0 && line === ''))
413
+ .map(line => line.trimStart());
414
+
415
+ const formattedLineArray = [];
416
+ const startingPos = ParseUtils.getNextNonEmptyCharPos(content);
417
+ const fullLeadingContent = content.substring(0, startingPos);
418
+ const preLineBreakContent = fullLeadingContent.substring(0, fullLeadingContent.lastIndexOf(Constants.EOL) + 1);
419
+ const correctIndentation = node.isInSameLineAsParent() ? '' : Rule.getIndentation(node.depth - 1);
420
+
421
+ for (let i = 0; i < lineArray.length; i++) {
422
+ let formattedLine = lineArray[i];
423
+
424
+ if (lineArray[i].length !== 0) {
425
+ formattedLine = correctIndentation + lineArray[i];
426
+ }
427
+
428
+ if (!(i === 0 && formattedLine.length === 0)) {
429
+ formattedLineArray.push(formattedLine);
430
+ }
431
+ }
432
+
433
+ return preLineBreakContent + formattedLineArray.join(Constants.EOL);
434
+ };
435
+
436
+ const getIndentedNestedIsmlContent = (attribute, nodeIndentation, attributeOffset) => {
437
+ if (attribute.fullContent.startsWith('<isif')) {
438
+ const attributeRootNode = TreeBuilder.parse(attribute.fullContent, null, null, true);
439
+ const fixedContent = Rule.getFixedContent(attributeRootNode);
440
+
441
+ return fixedContent
442
+ .split(Constants.EOL)
443
+ .map( (line, i) => {
444
+ if (i === 0 && attribute.isFirstInLine && !attribute.isInSameLineAsTagName && attribute.index !== 0) {
445
+ return Constants.EOL + nodeIndentation + attributeOffset + line;
446
+ }
447
+
448
+ return nodeIndentation + attributeOffset + line;
449
+ })
450
+ .join(Constants.EOL);
451
+ }
452
+ };
453
+
454
+ const getTreeBuiltAttributeIndentedValue = (attribute, nodeIndentation, attributeOffset) => {
455
+ const attributeRootNode = TreeBuilder.parse(attribute.value, null, null, true);
456
+ const fixedContent = Rule.getFixedContent(attributeRootNode);
457
+
458
+ return fixedContent
459
+ .split(Constants.EOL)
460
+ .map( (line, i) => {
461
+
462
+ // If line contains only blank spaces;
463
+ if (!line.trim()) {
464
+ return '';
465
+ }
466
+
467
+ if (i === 0 && attribute.isFirstInLine && !attribute.isInSameLineAsTagName && attribute.index !== 0) {
468
+ return Constants.EOL + nodeIndentation + attributeOffset + line;
469
+ }
470
+
471
+ if (i === 0 && attribute.isFirstValueInSameLineAsAttributeName) {
472
+ return line;
473
+ }
474
+
475
+ return nodeIndentation + attributeOffset + line;
476
+ })
477
+ .filter( (line, i, list) => {
478
+ if (i === 0 && attribute.isFirstValueInSameLineAsAttributeName && line === '') {
479
+ return false;
480
+ }
481
+
482
+ if (i === list.length - 1 && line === '') {
483
+ return false;
484
+ }
485
+
486
+ return i === 0 || line;
487
+ })
488
+ .join(Constants.EOL);
489
+ };
490
+
491
+ const getAttributeIndentedValues = (attribute, nodeIndentation, attributeOffset) => {
492
+ let result = '';
493
+
494
+ if (attribute.value) {
495
+ if (attribute.value.indexOf('<is') >= 0) {
496
+ result = '=' + attribute.quoteChar + getTreeBuiltAttributeIndentedValue(attribute, nodeIndentation, attributeOffset + attributeOffset);
497
+
498
+ } else {
499
+ result = '=' + attribute.quoteChar + attribute.value
500
+ .split(Constants.EOL)
501
+ .filter( value => value )
502
+ .map( (value, i) => {
503
+ if (i === 0) {
504
+ return attribute.isFirstValueInSameLineAsAttributeName ?
505
+ value :
506
+ Constants.EOL + nodeIndentation + attributeOffset + attributeOffset + value;
507
+ }
508
+
509
+ return nodeIndentation + attributeOffset + attributeOffset + value;
510
+ })
511
+ .join(Constants.EOL);
512
+ }
513
+ }
514
+
515
+ return result;
516
+ };
517
+
518
+
519
+ const indentAttribute = (attributeList, index, nodeIndentation, attributeOffset) => {
520
+ const attribute = attributeList[index];
521
+ let result = '';
522
+
523
+ const isInSameLineAsPreviousAttribute = index > 0 && attributeList[index - 1].lineNumber === attribute.lineNumber;
524
+
525
+ if (attribute.hasMultilineValue) {
526
+ if (attribute.isNestedIsmlTag) {
527
+ result += getIndentedNestedIsmlContent(attribute, nodeIndentation, attributeOffset);
528
+
529
+ } else {
530
+ let attributePrefix = '';
531
+
532
+ if (attribute.isFirstInLine) {
533
+ if (index > 0) {
534
+ attributePrefix += Constants.EOL;
535
+ }
536
+
537
+ attributePrefix += nodeIndentation + attributeOffset;
538
+ }
539
+
540
+ const formattedAttributeName = attribute.isExpressionAttribute ?
541
+ getExpressionAttributeIndentation(attribute.name, nodeIndentation, attributeOffset) :
542
+ attribute.name;
543
+
544
+ const formattedAttributeValue = getAttributeIndentedValues(attribute, nodeIndentation, attributeOffset);
545
+
546
+ const closingQuote = shouldAddIndentationToClosingQuote(attribute) ?
547
+ Constants.EOL + nodeIndentation + attributeOffset + attribute.quoteChar :
548
+ attribute.quoteChar;
549
+
550
+ const valueList = attributePrefix
551
+ + (index > 0 && attribute.isInSameLineAsTagName ? ' ' : '')
552
+ + formattedAttributeName
553
+ + formattedAttributeValue
554
+ + (attribute.isExpressionAttribute ? '' : closingQuote);
555
+
556
+ result += valueList;
557
+ }
558
+ } else {
559
+ if (isInSameLineAsPreviousAttribute) {
560
+ result += ' ';
561
+
562
+ } else if (!attribute.isInSameLineAsTagName) {
563
+ if (index !== 0) {
564
+ result += Constants.EOL;
565
+ }
566
+
567
+ result += nodeIndentation + attributeOffset;
568
+ }
569
+
570
+ result += attribute.fullContent;
571
+ }
572
+
573
+ return result;
574
+ };
575
+
576
+ const getExpressionAttributeIndentation = (attributeValue, nodeIndentation, attributeOffset) => {
577
+ return attributeValue
578
+ .split(Constants.EOL)
579
+ .map( (line, i) => {
580
+ return nodeIndentation + attributeOffset + (i > 0 ? attributeOffset : '') + line;
581
+ })
582
+ .join(Constants.EOL)
583
+ .trimStart();
584
+ };
585
+
586
+ const getClosingChars = node => {
587
+ const nodeHead = node.head.trim();
588
+
589
+ if (nodeHead.endsWith(' />')) {
590
+ return ' />';
591
+ } else if (nodeHead.endsWith('/>')) {
592
+ return ' />';
593
+ } else if (nodeHead.endsWith(' >')) {
594
+ return ' >';
595
+ } else if (nodeHead.endsWith('>')) {
596
+ return '>';
597
+ }
598
+
599
+ return '';
600
+ };
601
+
602
+ const addIndentation = (node, isOpeningTag) => {
603
+ const content = isOpeningTag ? node.head : node.tail;
604
+ const startingPos = ParseUtils.getNextNonEmptyCharPos(content);
605
+ const endingPos = content.length - ParseUtils.getNextNonEmptyCharPos(content.split('').reverse().join(''));
606
+ const fullLeadingContent = content.substring(0, startingPos);
607
+ const preLineBreakContent = fullLeadingContent.substring(0, fullLeadingContent.lastIndexOf(Constants.EOL) + 1);
608
+ const fullTrailingContent = content.substring(endingPos);
609
+ const nodeIndentation = node.isInSameLineAsParent() && isOpeningTag ? '' : Rule.getIndentation(node.depth - 1);
610
+ const attributeOffset = Rule.getAttributeIndentationOffset();
611
+ const attributeList = node.getAttributeList();
612
+ let contentResult = '';
613
+
614
+ if (isOpeningTag) {
615
+ const shouldAddIndentationToClosingChar = shouldAddIndentationToClosingChars(node);
616
+ const closingChars = getClosingChars(node);
617
+ const tagNameEndPos = ParseUtils.getLeadingLineBreakQty(node.head)
618
+ + ParseUtils.getFirstEmptyCharPos(node.head.trim()) + 1;
619
+
620
+ if (ParseUtils.getLineBreakQty(node.head.trim()) > 0 || shouldAddIndentationToClosingChar && node.isSelfClosing()) {
621
+ contentResult = attributeList.length > 0 ?
622
+ node.head.substring(0, tagNameEndPos).trimStart() :
623
+ node.head.trimStart();
624
+
625
+ for (let i = 0; i < attributeList.length; i++) {
626
+ contentResult += indentAttribute(attributeList, i, nodeIndentation, attributeOffset);
627
+ }
628
+
629
+ if (attributeList.length > 0) {
630
+ contentResult += shouldAddIndentationToClosingChar ?
631
+ Constants.EOL + nodeIndentation + closingChars.trimStart() :
632
+ closingChars;
633
+ }
634
+ } else {
635
+ contentResult = node.head.trim();
636
+ }
637
+ } else {
638
+ contentResult = node.tail.trim();
639
+ }
640
+
641
+ return preLineBreakContent + nodeIndentation + contentResult + fullTrailingContent;
642
+ };
643
+
644
+ const removeAllIndentation = node => {
645
+ if (!node.isRoot() && !node.isContainer() && !node.parent.isOneOfTypes(['isscript', 'script'])) {
646
+
647
+ const shouldRemoveHeadIndentation = node.head && !node.isInSameLineAsPreviousSibling() && !node.isInSameLineAsParent() && !(node.lineNumber === node.parent.endLineNumber);
648
+ const shouldRemoveTailIndentation = node.tail && !(node.hasChildren() && node.getLastChild().lineNumber === node.tailLineNumber);
649
+
650
+ if (shouldRemoveHeadIndentation) {
651
+ node.head = removeIndentation(node.head);
652
+ }
653
+
654
+ if (shouldRemoveTailIndentation) {
655
+ node.tail = removeIndentation(node.tail);
656
+ }
657
+ }
658
+
659
+ for (let i = 0; i < node.children.length; i++) {
660
+ removeAllIndentation(node.children[i]);
661
+ }
662
+ };
663
+
664
+ const addCorrectIndentation = node => {
665
+
666
+ if (!node.isRoot() && !node.isContainer() && !node.parent.isOneOfTypes(['isscript', 'script'])) {
667
+ if (node.parent.isOfType('iscomment')) {
668
+ const shouldAddIndentationToText = checkIfShouldAddIndentationToHead(node);
669
+
670
+ if (shouldAddIndentationToText) {
671
+ node.head = addIndentationToText(node);
672
+ }
673
+ } else {
674
+ const shouldAddIndentationToHead = checkIfShouldAddIndentationToHead(node);
675
+ const shouldAddIndentationToTail = checkIfShouldAddIndentationToTail(node);
676
+
677
+ if (shouldAddIndentationToHead) {
678
+ node.head = addIndentation(node, true);
679
+ }
680
+
681
+ if (shouldAddIndentationToTail) {
682
+ node.tail = addIndentation(node, false);
683
+ }
684
+ }
685
+ }
686
+
687
+ for (let i = 0; i < node.children.length; i++) {
688
+ addCorrectIndentation(node.children[i]);
689
+ }
690
+ };
691
+
692
+ const shouldAddIndentationToClosingChars = node => {
693
+ const closingCharsConfigs = Rule.getConfigs().standAloneClosingChars;
694
+
695
+ if (node.isSelfClosing()) {
696
+ if (closingCharsConfigs.selfClosingTag === 'always') {
697
+ return true;
698
+ } else if (closingCharsConfigs.selfClosingTag === 'never') {
699
+ return false;
700
+ }
701
+ } else {
702
+ if (closingCharsConfigs.nonSelfClosingTag === 'always') {
703
+ return true;
704
+ } else if (closingCharsConfigs.nonSelfClosingTag === 'never') {
705
+ return false;
706
+ }
707
+ }
708
+
709
+ const lineList = node.head.split(Constants.EOL);
710
+ const lastLine = lineList[lineList.length - 1];
711
+
712
+ return ['/>', '>'].indexOf(lastLine) >= 0;
713
+ };
714
+
715
+ const shouldAddIndentationToClosingQuote = attribute => {
716
+ const closingCharsConfigs = Rule.getConfigs().standAloneClosingChars;
717
+
718
+ if (!attribute.value || !closingCharsConfigs.quote || closingCharsConfigs.quote === 'always') {
719
+ return true;
720
+ } else if (closingCharsConfigs.quote === 'never') {
721
+ return false;
722
+ }
723
+
724
+ const lineList = attribute.value.split(Constants.EOL);
725
+ const lastLine = lineList[lineList.length - 1];
726
+
727
+ return lastLine.trim().length === 0;
728
+ };
729
+
730
+ const checkIfShouldAddIndentationToHead = node => {
731
+ const previousSibling = node.getPreviousSibling();
732
+ const isInSameLineAsPrevSiblingLastLine = !node.isRoot() &&
733
+ previousSibling &&
734
+ node.lineNumber === previousSibling.getLastLineNumber();
735
+ const isInSameLineAsParentHeadEnd = node.parent.endLineNumber === node.lineNumber && !node.parent.isContainer();
736
+
737
+ const shouldAdd = !node.isRoot() &&
738
+ !isInSameLineAsPrevSiblingLastLine &&
739
+ !isInSameLineAsParentHeadEnd &&
740
+ (node.isFirstChild() || previousSibling && node.lineNumber !== previousSibling.lineNumber) &&
741
+ node.head && node.lineNumber !== node.parent.endLineNumber;
742
+
743
+ return shouldAdd;
744
+ };
745
+
746
+ const checkIfShouldAddIndentationToTail = node => {
747
+ const hasTail = !!node.tail;
748
+ const isLastClause = !!node.parent && node.parent.isContainer() && !node.isLastChild();
749
+ const isInSameLineAsChild = !node.hasChildren() || node.getLastChild().isInSameLineAsParent();
750
+ const isTailInSameLineAsChild = !node.hasChildren() || node.tailLineNumber === node.getLastChild().getLastLineNumber();
751
+ const isBrokenIntoMultipleLines = !node.hasChildren() && node.tailLineNumber && node.lineNumber !== node.tailLineNumber;
752
+
753
+ const shouldAdd = hasTail &&
754
+ !isTailInSameLineAsChild &&
755
+ !isInSameLineAsChild &&
756
+ !isLastClause
757
+ ||
758
+ isBrokenIntoMultipleLines;
759
+
760
+ return shouldAdd;
761
+ };
762
+
763
+ // TODO This a workaround, it should be handled directly in ParseUtils.getElementList();
764
+ const getEslintChildTrailingSpaces = node => {
765
+ if (node.isOfType('isscript')) {
766
+ const child = node.getLastChild();
767
+
768
+ const trailingSpacesQty = child.head
769
+ .replace(/\r\n/g, '_')
770
+ .split('')
771
+ .reverse()
772
+ .join('')
773
+ .search(/\S/);
774
+
775
+ return trailingSpacesQty - 1;
776
+ }
777
+
778
+ return 0;
779
+ };
780
+
781
+ const getAttributeNestedValueError = (attribute, attributeValueList, i, expectedIndentation, configAttributeOffsetSize) => {
782
+ const tagList = ['isif', 'iselse', 'iselseif'];
783
+ let shouldContinueLoop = false;
784
+
785
+ for (let j = 0; j < tagList.length; j++) {
786
+ const element = tagList[j];
787
+ const previousAttribute = attributeValueList[i - 1];
788
+
789
+ if (previousAttribute.indexOf(element) >= 0 && previousAttribute.indexOf('</isif>') === -1) {
790
+ const error = getAttributeValueError(attribute, attributeValueList, i, expectedIndentation + configAttributeOffsetSize);
791
+
792
+ shouldContinueLoop = true;
793
+
794
+ if (error) {
795
+ return {
796
+ error,
797
+ shouldContinueLoop
798
+ };
799
+ }
800
+ }
801
+ }
802
+
803
+ return {
804
+ shouldContinueLoop
805
+ };
806
+ };
807
+
808
+ const getAttributeValueError = (attribute, attributeValueList, i, expectedIndentation) => {
809
+ const attributeValue = attributeValueList[i];
810
+ const valueColumnNumber = ParseUtils.getLeadingEmptyChars(attributeValue).length;
811
+ const attributeValueStartPos = attribute.value.indexOf(attributeValue);
812
+ const attributeValuePrefix = attribute.value.substring(0, attributeValueStartPos);
813
+ const isValueInSameLineAsAttributeName = ParseUtils.getLineBreakQty(attributeValuePrefix) === 0;
814
+
815
+ if (!isValueInSameLineAsAttributeName && valueColumnNumber !== expectedIndentation) {
816
+ const attributeValueFullPrefix = attribute.fullContent.substring(0, attribute.fullContent.indexOf(attributeValue.trim()));
817
+ const lineBreakQty = ParseUtils.getLineBreakQty(attributeValueFullPrefix);
818
+ const occurrenceGlobalPos = attribute.globalPos + attribute.fullContent.indexOf(attributeValueList[i]) + attribute.lineNumber - 1;
819
+ const lineNumber = attribute.lineNumber + lineBreakQty;
820
+
821
+ const occurrenceColumnNumber = valueColumnNumber === 0 ?
822
+ valueColumnNumber :
823
+ 0;
824
+
825
+ const occurrenceLength = valueColumnNumber === 0 ?
826
+ attributeValue.length :
827
+ valueColumnNumber;
828
+
829
+ const error = Rule.getError(
830
+ attributeValue.trim(),
831
+ lineNumber,
832
+ occurrenceColumnNumber,
833
+ occurrenceGlobalPos,
834
+ occurrenceLength,
835
+ getOccurrenceDescription(expectedIndentation, valueColumnNumber)
836
+ );
837
+
838
+ return error;
839
+ }
840
+
841
+ return null;
842
+ };
843
+
844
+ const getStandAloneQuoteDescription = lastValue => `Closing quote should not be in the same line as "${lastValue}"`;
845
+ const getNonStandAloneQuoteDescription = lastValue => `Closing quote should be in the same line as "${lastValue}"`;
846
+ const getStandAloneCharDescription = (tagName, closingChars) => `"${closingChars}" should not be in the same line as <${tagName}> tag last attribute`;
847
+ const getNonStandAloneCharDescription = (tagName, closingChars) => `"${closingChars}" should be in the same line as <${tagName}> tag last attribute`;
848
+ const getOccurrenceDescription = (expected, actual) => `Expected indentation of ${expected} spaces but found ${actual}`;
849
+ const getExpectedIndentation = (node, configIndentSize) => (node.depth - 1) * configIndentSize;
850
+ const getActualIndentation = node => node.getIndentationSize();
851
+ const getActualTailIndentation = node => node.getTailIndentationSize() + getEslintChildTrailingSpaces(node);
852
+
853
+ module.exports = Rule;