UNPKG

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:

284 lines (226 loc) 9.96 kB
const fs = require('fs'); const IsmlNode = require('./IsmlNode'); const ParseUtils = require('./ParseUtils'); const MaskUtils = require('./MaskUtils'); const ContainerNode = require('./ContainerNode'); const ExceptionUtils = require('../util/ExceptionUtils'); const GeneralUtils = require('../util/GeneralUtils'); const parse = (content, templatePath, isCrlfLineBreak, isEmbeddedNode) => { const elementList = ParseUtils.getElementList(content, templatePath, isCrlfLineBreak); const rootNode = new IsmlNode(undefined, undefined, undefined, undefined, isEmbeddedNode); let currentParent = rootNode; for (let i = 0; i < elementList.length; i++) { const element = elementList[i]; validateNodeHead(element, templatePath); const newNode = new IsmlNode(element.value, element.lineNumber, element.columnNumber, element.globalPos, isEmbeddedNode); const containerResult = parseContainerElements(element, currentParent, newNode, templatePath); currentParent = containerResult.currentParent; if (containerResult.shouldContinueLoop) { continue; } currentParent = parseNonContainerElements(element, currentParent, newNode, templatePath); } ParseUtils.checkBalance(rootNode, templatePath); rootNode.tree = { originalLineBreak : GeneralUtils.getFileLineBreakStyle(content), }; return rootNode; }; const parseContainerElements = (element, currentParent, newNode, templatePath) => { if (!currentParent.isContainerChild() && ['iselse', 'iselseif'].indexOf(element.tagType) >= 0) { throw ExceptionUtils.unbalancedElementError( currentParent.getType(), currentParent.lineNumber, currentParent.globalPos, currentParent.head.trim().length, templatePath ); } if (currentParent.isContainerChild() && ['iselse', 'iselseif'].indexOf(element.tagType) >= 0 && element.isClosingTag) { throw ExceptionUtils.unexpectedClosingElementError( element.tagType, element.lineNumber, element.globalPos, element.value.trim().length, templatePath ); } if (element.tagType === 'isif' && !element.isClosingTag) { const containerNode = new ContainerNode(); currentParent.addChild(containerNode); containerNode.addChild(newNode); currentParent = newNode; return { shouldContinueLoop : true, currentParent : currentParent }; } else if (element.tagType === 'iselse' || element.tagType === 'iselseif') { currentParent = currentParent.parent; currentParent.addChild(newNode); currentParent = newNode; return { shouldContinueLoop : true, currentParent : currentParent }; } else if (element.tagType === 'isif' && element.isClosingTag) { if (!currentParent.isOfType('iselseif') && !currentParent.isOfType('iselse') && currentParent.getType() !== element.tagType ) { throw ExceptionUtils.unbalancedElementError( currentParent.getType(), currentParent.lineNumber, currentParent.globalPos, currentParent.head.trim().length, templatePath ); } currentParent.setTail(element.value, element.lineNumber, element.columnNumber, element.globalPos); currentParent = currentParent.parent.parent; return { shouldContinueLoop : true, currentParent : currentParent }; } return { shouldContinueLoop : false, currentParent : currentParent }; }; const parseNonContainerElements = (element, currentParent, newNode, templatePath) => { if (element.isSelfClosing) { if (element.isClosingTag && element.isVoidElement) { currentParent.getLastChild().setTail(element.value, element.lineNumber, element.columnNumber, element.globalPos); } else { currentParent.addChild(newNode); } } else if (!element.isClosingTag && element.tagType !== 'isif') { currentParent.addChild(newNode); if (!element.isSelfClosing) { currentParent = newNode; } } else if (element.isClosingTag) { const parentLastChild = currentParent.getLastChild(); if (element.tagType === currentParent.getType()) { currentParent.setTail(element.value, element.lineNumber, element.columnNumber, element.globalPos); } else if (element.isCustomTag && element.tagType === parentLastChild.getType()) { parentLastChild.setTail(element.value, element.lineNumber, element.columnNumber, element.globalPos); currentParent = parentLastChild; } else if (element.isClosingTag && currentParent.isRoot()) { throw ExceptionUtils.unbalancedElementError( element.tagType, element.lineNumber, element.globalPos, element.value.trim().length, templatePath ); } else if (element.isClosingTag) { throw ExceptionUtils.unexpectedClosingElementError( element.tagType, element.lineNumber, element.globalPos, element.value.trim().length, templatePath ); } else { throw ExceptionUtils.unbalancedElementError( currentParent.getType(), currentParent.lineNumber, currentParent.globalPos, currentParent.value.trim().length, templatePath ); } currentParent = currentParent.parent; if (element.tagType === 'isif' && element.isClosingTag) { currentParent = currentParent.parent; } } return currentParent; }; const postProcess = (node, data = {}) => { for (let i = 0; i < node.children.length; i++) { const child = node.children[i]; if (child.head.indexOf('template="util/modules"') >= 0) { data.moduleDefinition = { value : child.head, lineNumber : child.lineNumber, globalPos : child.globalPos, length : child.head.trim().length }; } if (child.isCustomIsmlTag()) { data.customModuleArray = data.customModuleArray || []; data.customModuleArray.push({ value : child.head, lineNumber : child.lineNumber, globalPos : child.globalPos, length : child.head.trim().length }); } rectifyNodeIndentation(node, child); postProcess(child, data); } return data; }; const build = (templatePath, content, isCrlfLineBreak) => { const ParseStatus = require('../enums/ParseStatus'); const templateContent = content || fs.readFileSync(templatePath, 'utf-8'); const result = { originalLineBreak : GeneralUtils.getFileLineBreakStyle(templateContent), templatePath, status : ParseStatus.NO_ERRORS }; try { const formattedTemplateContent = templateContent; result.rootNode = parse(formattedTemplateContent, templatePath, isCrlfLineBreak); result.data = postProcess(result.rootNode); result.rootNode.tree = result; } catch (e) { result.rootNode = null; result.status = ParseStatus.INVALID_DOM; result.exception = e.type === ExceptionUtils.types.UNKNOWN_ERROR ? e.message : e; } return result; }; /** * In the main part of tree build, a node A might hold the next node B's indentation in the last part of * A, be it in its value or tail value. This function removes that trailing indentation from A and * adds it to B as a leading indentation; */ const rectifyNodeIndentation = (node, child) => { const previousSibling = child.getPreviousSibling(); if (child.isContainer()) { child = child.children[0]; } if (previousSibling && previousSibling.isOfType('text')) { const trailingLineBreakQty = ParseUtils.getTrailingEmptyCharsQty(previousSibling.head); previousSibling.head = previousSibling.head.substring(0, previousSibling.head.length - trailingLineBreakQty); child.head = ParseUtils.getBlankSpaceString(trailingLineBreakQty) + child.head; } if (child.isLastChild() && child.isOfType('text')) { let trailingLineBreakQty = 0; trailingLineBreakQty = ParseUtils.getTrailingEmptyCharsQty(child.head); child.head = child.head.substring(0, child.head.length - trailingLineBreakQty); node.tail = ParseUtils.getBlankSpaceString(trailingLineBreakQty) + node.tail; } }; const validateNodeHead = (element, templatePath) => { if (element.type !== 'text') { const trimmedElement = element.value.trim(); const maskedElement = MaskUtils.maskQuoteContent(trimmedElement); if (maskedElement.endsWith('_')) { throw ExceptionUtils.unbalancedQuotesError( element.tagType, element.lineNumber, element.globalPos, trimmedElement.length, templatePath ); } } }; module.exports.build = build; module.exports.parse = parse;