isml-linter
Version:
ISML Linter is a tool for examining if your project's templates follow a specified set of rules defined by your dev team. The available rules can be roughly grouped into:
889 lines (714 loc) • 34.8 kB
JavaScript
const TreeRulePrototype = require('../prototypes/TreeRulePrototype');
const ParseUtils = require('../../isml_tree/ParseUtils');
const TreeBuilder = require('../../isml_tree/TreeBuilder');
const Constants = require('../../Constants');
const ruleId = require('path').basename(__filename).slice(0, -3);
const description = 'Line incorrectly indented';
const IGNORED_TAGS = ['isscript', 'script', 'style'];
const Rule = Object.create(TreeRulePrototype);
Rule.init(ruleId, description);
Rule.getDefaultAttrs = () => {
return {
size : 4,
attributeOffset : 4,
standAloneClosingChars : {
nonSelfClosingTag : 'always',
selfClosingTag : 'never',
quote : 'never'
}
};
};
Rule.getIndentation = function(depth = 1) {
const indentationSize = this.getConfigs().indent * depth;
let indentation = '';
for (let i = 0; i < indentationSize; ++i) {
indentation += ' ';
}
return indentation;
};
Rule.getAttributeIndentationOffset = function() {
const offsetSize = this.getConfigs().attributeOffset;
let indentation = '';
for (let i = 0; i < offsetSize; ++i) {
indentation += ' ';
}
return indentation;
};
Rule.isBroken = function(node) {
const configIndentSize = this.getConfigs().indent;
const expectedIndentation = getExpectedIndentation(node, configIndentSize);
const actualIndentation = getActualIndentation(node);
return !node.isRoot() &&
!node.isContainer() &&
!node.isEmpty() &&
!node.isInSameLineAsParentEnd() &&
!node.parent.isOneOfTypes(IGNORED_TAGS) &&
expectedIndentation !== actualIndentation &&
!node.isInSameLineAsPreviousSibling();
};
Rule.isTailBroken = function(node) {
const configIndentSize = this.getConfigs().indent;
const expectedIndentation = getExpectedIndentation(node, configIndentSize);
const actualIndentation = getActualTailIndentation(node);
const isInSameLineAsOpeningTag = node.endLineNumber === node.tailLineNumber;
const isInSameLineAsLastChild = node.hasChildren() && node.getLastChild().getLastLineNumber() === node.tailLineNumber;
return !node.isRoot() &&
!node.isContainer() &&
!node.isEmpty() &&
!node.isInSameLineAsParent() &&
expectedIndentation !== actualIndentation &&
!isInSameLineAsLastChild &&
!isInSameLineAsOpeningTag;
};
Rule.isClosingCharBroken = function(node) {
const closingCharsConfigs = Rule.getConfigs().standAloneClosingChars;
const isFirstChildInSameLine = node.hasChildren() && node.endLineNumber === node.children[0].lineNumber;
if (!node.isTag()
|| !node.isMultiLineOpeningTag()
|| isFirstChildInSameLine
|| !closingCharsConfigs
|| !node.isSelfClosing() && (!closingCharsConfigs.nonSelfClosingTag || closingCharsConfigs.nonSelfClosingTag === 'any')
|| node.isSelfClosing() && (!closingCharsConfigs.selfClosingTag || closingCharsConfigs.selfClosingTag === 'any')
) {
return {
isBroken : false
};
}
if (node.isSelfClosing()) {
if (closingCharsConfigs.selfClosingTag === 'always') {
const nodeHeadLineList = node.head.trim().split(Constants.EOL);
const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '/>';
return {
isBroken : !isClosingCharStandingAlone,
config : 'always'
};
} else if (closingCharsConfigs.selfClosingTag === 'never') {
const nodeHeadLineList = node.head.trim().split(Constants.EOL);
const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '/>';
return {
isBroken : isClosingCharStandingAlone,
config : 'never'
};
}
} else {
if (closingCharsConfigs.nonSelfClosingTag === 'always') {
const nodeHeadLineList = node.head.trim().split(Constants.EOL);
const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '>';
return {
isBroken : !isClosingCharStandingAlone,
config : 'always'
};
} else if (closingCharsConfigs.nonSelfClosingTag === 'never') {
const nodeHeadLineList = node.head.trim().split(Constants.EOL);
const isClosingCharStandingAlone = nodeHeadLineList[nodeHeadLineList.length - 1].trim() === '>';
return {
isBroken : isClosingCharStandingAlone,
config : 'never'
};
}
}
};
Rule.isQuoteClosingCharBroken = function(node) {
const quoteConfig = Rule.getConfigs().standAloneClosingChars.quote;
const attributeList = node.getAttributeList();
const result = [];
for (let i = 0; i < attributeList.length; i++) {
const attribute = attributeList[i];
if (attribute.value) {
const attributeValueLineList = attribute.fullContent.trim().split(Constants.EOL);
const isClosingCharStandingAlone = attributeValueLineList[attributeValueLineList.length - 1].trim() === attribute.quoteChar;
const lineNumber = attribute.lineNumber + ParseUtils.getLineBreakQty(attribute.fullContent);
const lineList = attribute.value.split(Constants.EOL);
const columnNumber = lineList[lineList.length - 1].length + 1;
const globalPos = attribute.globalPos
+ attribute.fullContent.lastIndexOf(attribute.quoteChar)
+ lineNumber - 2;
const formattedLineList = lineList
.map( line => line.trim())
.filter( line => line );
const lastLine = formattedLineList[formattedLineList.length - 1];
const message = quoteConfig === 'always' && !isClosingCharStandingAlone ? getStandAloneQuoteDescription(lastLine) :
quoteConfig === 'never' && isClosingCharStandingAlone ? getNonStandAloneQuoteDescription(lastLine) :
'';
if (message) {
const lineBreak = node.getRoot().tree.originalLineBreak;
const line = node
.getRoot()
.toString()
.split(lineBreak)[lineNumber - 1];
result.push({
quoteChar : attribute.quoteChar,
line : line,
lineNumber : lineNumber,
columnNumber : columnNumber,
globalPos : globalPos,
length : attribute.quoteChar.length,
message : message
});
}
}
}
return result;
};
Rule.check = function(node, data) {
const ruleConfig = this.getConfigs();
const typeArray = ['script', 'iscomment'];
let occurrenceList = [];
if (node.isRoot() || !node.parent.isOneOfTypes(typeArray)) {
occurrenceList = this.checkChildren(node, data);
const errorGlobalPos = node.globalPos - getActualIndentation(node);
const attributeErrorList = getAttributeErrorList(node);
// Checks node value;
if (this.isBroken(node)) {
const configIndentSize = ruleConfig.indent;
const expectedIndentation = getExpectedIndentation(node, configIndentSize);
const actualIndentation = getActualIndentation(node);
const nodeHead = node.head.trim();
const occurrenceLength = actualIndentation === 0 ?
nodeHead.length + ParseUtils.getLineBreakQty(nodeHead) :
getActualIndentation(node);
const error = this.getError(
node.head.trim(),
node.lineNumber,
node.columnNumber,
errorGlobalPos,
occurrenceLength,
getOccurrenceDescription(expectedIndentation, actualIndentation)
);
occurrenceList.push(error);
}
if (attributeErrorList.length > 0) {
for (let i = 0; i < attributeErrorList.length; i++) {
const attributeError = attributeErrorList[i];
occurrenceList.push(attributeError);
}
}
// Checks node tail value;
if (node.tail && this.isTailBroken(node)) {
const configIndentSize = ruleConfig.indent;
const expectedIndentation = getExpectedIndentation(node, configIndentSize);
const actualIndentation = getActualTailIndentation(node);
const nodeTail = node.tail.trim();
const occurrenceLength = actualIndentation === 0 ?
nodeTail.length + ParseUtils.getLineBreakQty(nodeTail) :
getActualTailIndentation(node);
const tailGlobalPos = node.tailGlobalPos - getActualTailIndentation(node);
const error = this.getError(
node.tail.trim(),
node.tailLineNumber,
node.tailColumnNumber,
tailGlobalPos,
occurrenceLength,
getOccurrenceDescription(expectedIndentation, actualIndentation)
);
occurrenceList.push(error);
}
const quoteOccurrenceList = this.isQuoteClosingCharBroken(node);
occurrenceList.push(...quoteOccurrenceList);
const checkResult = this.isClosingCharBroken(node);
if (checkResult.isBroken) {
if (node.isSelfClosing()) {
const closingChar = '/>';
const globalPos = node.globalPos
+ node.head.trim().lastIndexOf(closingChar);
const lineList = node.head.split(Constants.EOL);
const columnNumber = lineList[lineList.length - 1].lastIndexOf(closingChar) + 1;
const message = checkResult.config === 'always' ?
getStandAloneCharDescription(node.getType(), closingChar) :
getNonStandAloneCharDescription(node.getType(), closingChar);
const error = this.getError(
node.head.trim(),
node.endLineNumber,
columnNumber,
globalPos,
closingChar.length,
message
);
occurrenceList.push(error);
} else {
const closingChar = '>';
const globalPos = node.globalPos
+ node.head.trim().length - closingChar.length;
const lineList = node.head.trim().split(Constants.EOL);
const columnNumber = lineList[lineList.length - 1].lastIndexOf(closingChar) + 1;
const message = checkResult.config === 'always' ?
getStandAloneCharDescription(node.getType(), closingChar) :
getNonStandAloneCharDescription(node.getType(), closingChar);
const error = this.getError(
node.head.trim(),
node.endLineNumber,
columnNumber,
globalPos,
closingChar.length,
message
);
occurrenceList.push(error);
}
}
}
return this.return(node, occurrenceList, ruleConfig);
};
Rule.getFixedContent = node => {
if (node.isRoot() || !node.parent.isOfType('script')) {
removeAllIndentation(node);
addCorrectIndentation(node);
}
return node.toString();
};
/**
* PRIVATE FUNCTIONS
*/
const getAttributeValueErrorList = function(node, attribute) {
if (!attribute.value) {
return [];
}
const configIndentSize = Rule.getConfigs().indent;
const configAttributeOffsetSize = Rule.getConfigs().attributeOffset;
const result = [];
if (attribute.hasMultilineValue) {
const expectedIndentation = node.depth * configIndentSize + configAttributeOffsetSize;
const attributeValueList = attribute.value
.split(Constants.EOL)
.filter( attr => attr.trim() && ['{', '}'].indexOf(attr.trim()) === -1);
for (let i = 0; i < attributeValueList.length; i++) {
if (i > 0) {
const partialResult = getAttributeNestedValueError(attribute, attributeValueList, i, expectedIndentation, configAttributeOffsetSize);
if (partialResult.error) {
result.push(partialResult.error);
}
if (partialResult.shouldContinueLoop) {
continue;
}
}
const error = getAttributeValueError(attribute, attributeValueList, i, expectedIndentation);
if (error) {
result.push(error);
}
}
}
return result;
};
const getAttributeErrorList = function(node) {
const configIndentSize = Rule.getConfigs().indent;
const attributeList = node.getAttributeList();
const result = [];
for (let i = 0; i < attributeList.length; i++) {
const attribute = attributeList[i];
if (!attribute.isInSameLineAsTagName && attribute.isFirstInLine) {
const expectedIndentation = node.depth * configIndentSize;
if (attribute.columnNumber - 1 !== expectedIndentation) {
const occurrenceGlobalPos = attribute.globalPos + node.lineNumber - attribute.columnNumber;
const occurrenceLength = attribute.columnNumber === 1 ?
attribute.length + ParseUtils.getLineBreakQty(attribute.value) :
attribute.columnNumber - 1;
const error = Rule.getError(
attribute.fullContent,
attribute.lineNumber,
attribute.columnNumber,
occurrenceGlobalPos,
occurrenceLength,
getOccurrenceDescription(expectedIndentation, attribute.columnNumber - 1)
);
result.push(error);
}
}
const attributeErrorList = getAttributeValueErrorList(node, attribute);
result.push(...attributeErrorList);
}
return result;
};
const removeIndentation = content => {
const startingPos = ParseUtils.getNextNonEmptyCharPos(content);
const endingPos = content.length - ParseUtils.getNextNonEmptyCharPos(content.split('').reverse().join(''));
const fullLeadingContent = content.substring(0, startingPos);
const actualContent = content.substring(startingPos, endingPos);
const preLineBreakContent = fullLeadingContent.substring(0, fullLeadingContent.lastIndexOf(Constants.EOL) + 1);
const fullTrailingContent = content.substring(endingPos);
const lastLineBreakPos = fullTrailingContent.lastIndexOf(Constants.EOL);
const trimmedTrailingContent = lastLineBreakPos === -1 ?
fullTrailingContent :
fullTrailingContent.substring(0, lastLineBreakPos + 1);
// Removes indentation from node attributes;
const indentlessContent = actualContent
.split(Constants.EOL)
.map(line => line.trimStart())
.join(Constants.EOL);
return preLineBreakContent + indentlessContent + trimmedTrailingContent;
};
const addIndentationToText = node => {
const content = node.head;
const lineArray = content
.split(Constants.EOL)
.filter( (line, i) => !(i === 0 && line === ''))
.map(line => line.trimStart());
const formattedLineArray = [];
const startingPos = ParseUtils.getNextNonEmptyCharPos(content);
const fullLeadingContent = content.substring(0, startingPos);
const preLineBreakContent = fullLeadingContent.substring(0, fullLeadingContent.lastIndexOf(Constants.EOL) + 1);
const correctIndentation = node.isInSameLineAsParent() ? '' : Rule.getIndentation(node.depth - 1);
for (let i = 0; i < lineArray.length; i++) {
let formattedLine = lineArray[i];
if (lineArray[i].length !== 0) {
formattedLine = correctIndentation + lineArray[i];
}
if (!(i === 0 && formattedLine.length === 0)) {
formattedLineArray.push(formattedLine);
}
}
return preLineBreakContent + formattedLineArray.join(Constants.EOL);
};
const getIndentedNestedIsmlContent = (attribute, nodeIndentation, attributeOffset) => {
const attributeRootNode = TreeBuilder.parse(attribute.fullContent, null, null, true);
const fixedContent = Rule.getFixedContent(attributeRootNode);
return fixedContent
.split(Constants.EOL)
.map( (line, i) => {
const shouldAddLineBreak = i === 0 && attribute.isFirstInLine && !attribute.isInSameLineAsTagName && attribute.index !== 0;
if (attribute.isInSameLineAsPreviousAttribute) {
return ' ' + line;
}
return (shouldAddLineBreak ? Constants.EOL : '')
+ nodeIndentation
+ attributeOffset
+ line;
})
.join(Constants.EOL);
};
const getTreeBuiltAttributeIndentedValue = (attribute, nodeIndentation, attributeOffset) => {
const attributeRootNode = TreeBuilder.parse(attribute.value, null, null, true);
const fixedContent = Rule.getFixedContent(attributeRootNode);
return fixedContent
.split(Constants.EOL)
.map( (line, i) => {
// If line contains only blank spaces;
if (!line.trim()) {
return '';
}
if (i === 0 && attribute.isFirstInLine && !attribute.isInSameLineAsTagName && attribute.index !== 0) {
return Constants.EOL + nodeIndentation + attributeOffset + line;
}
if (i === 0 && attribute.isFirstValueInSameLineAsAttributeName) {
return line;
}
return nodeIndentation + attributeOffset + line;
})
.filter( (line, i, list) => {
if (i === 0 && attribute.isFirstValueInSameLineAsAttributeName && line === '') {
return false;
}
if (i === list.length - 1 && line === '') {
return false;
}
return i === 0 || line;
})
.join(Constants.EOL);
};
const getAttributeIndentedValues = (attribute, nodeIndentation, attributeOffset) => {
let result = '';
if (attribute.value) {
if (attribute.value.indexOf('<is') >= 0) {
result = '=' + attribute.quoteChar + getTreeBuiltAttributeIndentedValue(attribute, nodeIndentation, attributeOffset + attributeOffset);
} else {
result = '=' + attribute.quoteChar + attribute.value
.split(Constants.EOL)
.filter( value => value )
.map( (value, i) => {
if (i === 0) {
return attribute.isFirstValueInSameLineAsAttributeName ?
value :
Constants.EOL + nodeIndentation + attributeOffset + attributeOffset + value;
}
return nodeIndentation + attributeOffset + attributeOffset + value;
})
.join(Constants.EOL);
}
}
return result;
};
const indentAttribute = (attributeList, index, nodeIndentation, attributeOffset) => {
const attribute = attributeList[index];
let result = '';
const isInSameLineAsPreviousAttribute = index > 0 && attributeList[index - 1].lineNumber === attribute.lineNumber;
if (attribute.hasMultilineValue) {
if (attribute.isNestedIsmlTag) {
result += getIndentedNestedIsmlContent(attribute, nodeIndentation, attributeOffset);
} else {
let attributePrefix = '';
if (attribute.isFirstInLine) {
if (index > 0) {
attributePrefix += Constants.EOL;
}
attributePrefix += nodeIndentation + attributeOffset;
}
const formattedAttributeName = attribute.isExpressionAttribute ?
getExpressionAttributeIndentation(attribute.name, nodeIndentation, attributeOffset) :
attribute.name;
const formattedAttributeValue = getAttributeIndentedValues(attribute, nodeIndentation, attributeOffset);
const closingQuote = shouldAddIndentationToClosingQuote(attribute) ?
Constants.EOL + nodeIndentation + attributeOffset + attribute.quoteChar :
attribute.quoteChar;
const valueList = attributePrefix
+ (index > 0 && attribute.isInSameLineAsTagName ? ' ' : '')
+ formattedAttributeName
+ formattedAttributeValue
+ (attribute.isExpressionAttribute ? '' : closingQuote);
result += valueList;
}
} else {
if (isInSameLineAsPreviousAttribute) {
result += ' ';
} else if (!attribute.isInSameLineAsTagName) {
if (index !== 0) {
result += Constants.EOL;
}
result += nodeIndentation + attributeOffset;
}
result += attribute.fullContent;
}
return result;
};
const getExpressionAttributeIndentation = (attributeValue, nodeIndentation, attributeOffset) => {
return attributeValue
.split(Constants.EOL)
.map( (line, i) => {
return nodeIndentation + attributeOffset + (i > 0 ? attributeOffset : '') + line;
})
.join(Constants.EOL)
.trimStart();
};
const getClosingChars = node => {
const nodeHead = node.head.trim();
if (nodeHead.endsWith(' />')) {
return ' />';
} else if (nodeHead.endsWith('/>')) {
return ' />';
} else if (nodeHead.endsWith(' >')) {
return ' >';
} else if (nodeHead.endsWith('>')) {
return '>';
}
return '';
};
const shouldChangeIndentation = node => {
return !node.isRoot() &&
!node.isContainer() &&
!node.parent.isOneOfTypes(IGNORED_TAGS);
};
const addIndentation = (node, isOpeningTag) => {
const content = isOpeningTag ? node.head : node.tail;
const startingPos = ParseUtils.getNextNonEmptyCharPos(content);
const endingPos = content.length - ParseUtils.getNextNonEmptyCharPos(content.split('').reverse().join(''));
const fullLeadingContent = content.substring(0, startingPos);
const preLineBreakContent = fullLeadingContent.substring(0, fullLeadingContent.lastIndexOf(Constants.EOL) + 1);
const fullTrailingContent = content.substring(endingPos);
const nodeIndentation = node.isInSameLineAsParent() && isOpeningTag ? '' : Rule.getIndentation(node.depth - 1);
const attributeOffset = Rule.getAttributeIndentationOffset();
const attributeList = node.getAttributeList();
const leadingLineBreakQty = ParseUtils.getLeadingLineBreakQty(node.head);
let contentResult = '';
if (isOpeningTag) {
const shouldAddIndentationToClosingChar = shouldAddIndentationToClosingChars(node);
const closingChars = getClosingChars(node);
const tagNameEndPos = leadingLineBreakQty
+ ParseUtils.getFirstEmptyCharPos(node.head.trim()) + 1;
if (ParseUtils.getLineBreakQty(node.head.trim()) > 0 || shouldAddIndentationToClosingChar && node.isSelfClosing()) {
contentResult = attributeList.length > 0 ?
node.head.trimStart().substring(0, tagNameEndPos - leadingLineBreakQty).trimStart() :
node.head.trimStart();
for (let i = 0; i < attributeList.length; i++) {
contentResult += indentAttribute(attributeList, i, nodeIndentation, attributeOffset);
}
if (attributeList.length > 0) {
contentResult += shouldAddIndentationToClosingChar ?
Constants.EOL + nodeIndentation + closingChars.trimStart() :
closingChars;
}
} else {
contentResult = node.head.trim();
}
if (node.isOfType('html_conditional_comment')) {
contentResult = contentResult
.split(Constants.EOL)
.map((line, i) => i > 0 ? nodeIndentation + line : line)
.join(Constants.EOL);
}
} else {
contentResult = node.tail.trim();
}
return preLineBreakContent + nodeIndentation + contentResult + fullTrailingContent;
};
const removeAllIndentation = node => {
if (shouldChangeIndentation(node)) {
const shouldRemoveHeadIndentation = node.head && !node.isInSameLineAsPreviousSibling() && !node.isInSameLineAsParent() && !(node.lineNumber === node.parent.endLineNumber);
const shouldRemoveTailIndentation = !!(node.tail && !(node.hasChildren() && node.getLastChild().lineNumber === node.tailLineNumber));
if (shouldRemoveHeadIndentation) {
node.head = removeIndentation(node.head);
}
if (shouldRemoveTailIndentation) {
node.tail = removeIndentation(node.tail);
}
}
for (let i = 0; i < node.children.length; i++) {
removeAllIndentation(node.children[i]);
}
};
const addCorrectIndentation = node => {
if (shouldChangeIndentation(node)) {
if (node.parent.isOfType('iscomment')) {
const shouldAddIndentationToText = checkIfShouldAddIndentationToHead(node);
if (shouldAddIndentationToText) {
node.head = addIndentationToText(node);
}
} else {
const shouldAddIndentationToHead = checkIfShouldAddIndentationToHead(node);
const shouldAddIndentationToTail = checkIfShouldAddIndentationToTail(node);
if (shouldAddIndentationToHead) {
node.head = addIndentation(node, true);
}
if (shouldAddIndentationToTail) {
node.tail = addIndentation(node, false);
}
}
}
for (let i = 0; i < node.children.length; i++) {
addCorrectIndentation(node.children[i]);
}
};
const shouldAddIndentationToClosingChars = node => {
const closingCharsConfigs = Rule.getConfigs().standAloneClosingChars;
if (node.isSelfClosing()) {
if (closingCharsConfigs.selfClosingTag === 'always') {
return true;
} else if (closingCharsConfigs.selfClosingTag === 'never') {
return false;
}
} else {
if (closingCharsConfigs.nonSelfClosingTag === 'always') {
return true;
} else if (closingCharsConfigs.nonSelfClosingTag === 'never') {
return false;
}
}
const lineList = node.head.split(Constants.EOL);
const lastLine = lineList[lineList.length - 1];
return ['/>', '>'].indexOf(lastLine) >= 0;
};
const shouldAddIndentationToClosingQuote = attribute => {
const closingCharsConfigs = Rule.getConfigs().standAloneClosingChars;
if (!attribute.value || !closingCharsConfigs.quote || closingCharsConfigs.quote === 'always') {
return true;
} else if (closingCharsConfigs.quote === 'never') {
return false;
}
const lineList = attribute.value.split(Constants.EOL);
const lastLine = lineList[lineList.length - 1];
return lastLine.trim().length === 0;
};
const checkIfShouldAddIndentationToHead = node => {
const previousSibling = node.getPreviousSibling();
const isInSameLineAsPrevSiblingLastLine = !node.isRoot() &&
previousSibling &&
node.lineNumber === previousSibling.getLastLineNumber();
const isInSameLineAsParentHeadEnd = node.parent.endLineNumber === node.lineNumber && !node.parent.isContainer();
const shouldAdd = !node.isRoot() &&
!isInSameLineAsPrevSiblingLastLine &&
!isInSameLineAsParentHeadEnd &&
(node.isFirstChild() || previousSibling && node.lineNumber !== previousSibling.lineNumber) &&
node.head && node.lineNumber !== node.parent.endLineNumber;
return shouldAdd;
};
const checkIfShouldAddIndentationToTail = node => {
const hasTail = !!node.tail;
const isLastClause = !!node.parent && node.parent.isContainer() && !node.isLastChild();
const isInSameLineAsChild = !node.hasChildren() || node.getLastChild().tailLineNumber === node.tailLineNumber;
const isTailInSameLineAsChild = !node.hasChildren() || node.tailLineNumber === node.getLastChild().getLastLineNumber();
const isBrokenIntoMultipleLines = !node.hasChildren() && node.tailLineNumber && node.lineNumber !== node.tailLineNumber;
const isInSameLineAsOpeningTag = !node.hasChildren() && node.tailLineNumber && node.endLineNumber === node.tailLineNumber;
const shouldAdd = hasTail &&
!isTailInSameLineAsChild &&
!isInSameLineAsChild &&
!isLastClause
// TODO Works for a specific case only. Might have side effects;
||
node.isOfType('iscomment') && !isTailInSameLineAsChild
||
isBrokenIntoMultipleLines;
// TODO Merge this condition into the above ones;
if (isInSameLineAsOpeningTag) {
return false;
}
return shouldAdd;
};
// TODO This a workaround, it should be handled directly in ParseUtils.getElementList();
const getEslintChildTrailingSpaces = node => {
if (node.isOfType('isscript')) {
const child = node.getLastChild();
const trailingSpacesQty = child.head
.replace(/\r\n/g, '_')
.split('')
.reverse()
.join('')
.search(/\S/);
return trailingSpacesQty - 1;
}
return 0;
};
const getAttributeNestedValueError = (attribute, attributeValueList, i, expectedIndentation, configAttributeOffsetSize) => {
const tagList = ['isif', 'iselse', 'iselseif'];
let shouldContinueLoop = false;
for (let j = 0; j < tagList.length; j++) {
const element = tagList[j];
const previousAttribute = attributeValueList[i - 1];
if (previousAttribute.indexOf(element) >= 0 && previousAttribute.indexOf('</isif>') === -1) {
const error = getAttributeValueError(attribute, attributeValueList, i, expectedIndentation + configAttributeOffsetSize);
shouldContinueLoop = true;
if (error) {
return {
error,
shouldContinueLoop
};
}
}
}
return {
shouldContinueLoop
};
};
const getAttributeValueError = (attribute, attributeValueList, i, expectedIndentation) => {
const attributeValue = attributeValueList[i];
const valueColumnNumber = ParseUtils.getLeadingEmptyChars(attributeValue).length;
const attributeValueStartPos = attribute.value.indexOf(attributeValue);
const attributeValuePrefix = attribute.value.substring(0, attributeValueStartPos);
const isValueInSameLineAsAttributeName = ParseUtils.getLineBreakQty(attributeValuePrefix) === 0;
if (!isValueInSameLineAsAttributeName && valueColumnNumber !== expectedIndentation) {
const attributeValueFullPrefix = attribute.fullContent.substring(0, attribute.fullContent.indexOf(attributeValue.trim()));
const lineBreakQty = ParseUtils.getLineBreakQty(attributeValueFullPrefix);
const occurrenceGlobalPos = attribute.globalPos + attribute.fullContent.indexOf(attributeValueList[i]) + attribute.lineNumber - 1;
const lineNumber = attribute.lineNumber + lineBreakQty;
const occurrenceColumnNumber = valueColumnNumber === 0 ?
valueColumnNumber :
0;
const occurrenceLength = valueColumnNumber === 0 ?
attributeValue.length :
valueColumnNumber;
const error = Rule.getError(
attributeValue.trim(),
lineNumber,
occurrenceColumnNumber,
occurrenceGlobalPos,
occurrenceLength,
getOccurrenceDescription(expectedIndentation, valueColumnNumber)
);
return error;
}
return null;
};
const getStandAloneQuoteDescription = lastValue => `Closing quote should not be in the same line as "${lastValue}"`;
const getNonStandAloneQuoteDescription = lastValue => `Closing quote should be in the same line as "${lastValue}"`;
const getStandAloneCharDescription = (tagName, closingChars) => `"${closingChars}" should not be in the same line as <${tagName}> tag last attribute`;
const getNonStandAloneCharDescription = (tagName, closingChars) => `"${closingChars}" should be in the same line as <${tagName}> tag last attribute`;
const getOccurrenceDescription = (expected, actual) => `Expected indentation of ${expected} spaces but found ${actual}`;
const getExpectedIndentation = (node, configIndentSize) => (node.depth - 1) * configIndentSize;
const getActualIndentation = node => node.getIndentationSize();
const getActualTailIndentation = node => node.getTailIndentationSize() + getEslintChildTrailingSpaces(node);
module.exports = Rule;