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:
311 lines (251 loc) • 10.6 kB
JavaScript
/*
===========================================================================
THIS FILE EXISTS ONLY TO AVOID CIRCULAR DEPENDENCY.
DUE TO A PRESSING BUG, IT WAS TEMPORARILY CREATED.
IT WILL SOON BE REMOVED.
===========================================================================
**/
const path = require('path');
const fs = require('fs');
const Constants = require('../Constants');
const TreeBuilder = require('../isml_tree/TreeBuilder');
const ConfigUtils = require('./ConfigUtils');
const lowercaseFilenameRule = require('../rules/line_by_line/lowercase-filename');
const CustomTagContainer = require('./CustomTagContainer');
const CustomModulesRule = require('../rules/tree/custom-tags');
const ConsoleUtils = require('./ConsoleUtils');
const ExceptionUtils = require('./ExceptionUtils');
const lineByLineRules = [];
const treeRules = [];
(() => {
const lineRuleFileArray = fs.readdirSync(Constants.lineByLineRulesDir);
const treeRuleFileArray = fs.readdirSync(Constants.treeRulesDir);
for (let i = 0; i < lineRuleFileArray.length; i++) {
const file = lineRuleFileArray[i];
if (file.endsWith('.js')) {
const rulePath = path.join(__dirname, '..', 'rules', 'line_by_line', file);
lineByLineRules.push(require(rulePath));
}
}
for (let i = 0; i < treeRuleFileArray.length; i++) {
const file = treeRuleFileArray[i];
if (file.endsWith('.js')) {
const rulePath = path.join(__dirname, '..', 'rules', 'tree', file);
treeRules.push(require(rulePath));
}
}
})();
const getLevelGroup = level => {
return level === 'errors' ? 'errors' :
level === 'warning' ? 'warnings' :
'info';
};
const checkCustomTag = tag => {
if (Object.prototype.hasOwnProperty.call(CustomTagContainer, tag)) {
const attrList = CustomTagContainer[tag].attrList;
for (let i = 0; i < attrList.length; i++) {
const attr = attrList[i];
if (attr !== attr.toLowerCase()) {
return {
line : '',
globalPos : 0,
length : 10,
lineNumber : 1,
columnNumber : 1,
level : CustomModulesRule.level,
rule : CustomModulesRule.id,
message : `Module properties need to be lower case: "${tag}" module has the invalid "${attr}" attribute`
};
}
}
}
};
const fixTemplateOrReportIssues = (config, ruleResult, templatePath, templateResults, rule) => {
if (config.autoFix && ruleResult.fixedContent) {
fs.writeFileSync(templatePath, ruleResult.fixedContent);
templateResults.fixed = true;
}
else if (ruleResult.occurrenceList && ruleResult.occurrenceList.length) {
const occurrenceObj = getOccurrenceObj(rule, ruleResult.occurrenceList);
templateResults.errors = Object.assign(templateResults.errors, occurrenceObj.errors);
templateResults.warnings = Object.assign(templateResults.warnings, occurrenceObj.warnings);
templateResults.info = Object.assign(templateResults.info, occurrenceObj.info);
}
};
const fixTemplateOrReportIssuesForRuleList = (ruleArray, templatePath, rootNodeOrTemplateContent, config, data) => {
const templateResults = {
fixed : false,
errors : {},
warnings : {},
info : {},
data
};
let tempRootNodeOrTemplateContent = rootNodeOrTemplateContent;
for (let i = 0; i < ruleArray.length; i++) {
const rule = ruleArray[i];
if (!rule.shouldIgnore(templatePath)) {
try {
ConsoleUtils.displayVerboseMessage(`Applying "${rule.id}" rule`, 1);
const ruleResults = rule.check(tempRootNodeOrTemplateContent, templateResults.data);
templateResults.finalContent = ruleResults.fixedContent;
if (ruleResults.fixedContent && typeof rootNodeOrTemplateContent === 'string') {
tempRootNodeOrTemplateContent = ruleResults.fixedContent;
}
fixTemplateOrReportIssues(config, ruleResults, templatePath, templateResults, rule);
} catch (error) {
throw ExceptionUtils.ruleApplianceError(rule, error, templatePath);
}
}
}
return templateResults;
};
const findNodeOfType = (node, type) => {
let result = null;
node.children.some( child => {
if (child.isOfType(type)) {
result = child;
return true;
}
result = findNodeOfType(child, type) || result;
return false;
});
return result;
};
const isTypeAmongTheFirstElements = (rootNode, type) => {
let result = false;
for (let i = 0; i < Constants.leadingElementsChecking; i++) {
result = result ||
rootNode.children[i] &&
rootNode.children[i].isOfType(type);
}
return result;
};
const getOccurrenceObj = (rule, occurrenceArray) => {
const occurrenceGroup = getLevelGroup(rule.level);
const occurrenceObj = {};
occurrenceObj[occurrenceGroup] = {};
occurrenceObj[occurrenceGroup][rule.id] = [];
for (let i = 0; i < occurrenceArray.length; i++) {
const occurrence = occurrenceArray[i];
occurrenceObj[occurrenceGroup][rule.id].push(occurrence);
}
return occurrenceObj;
};
const checkFileName = (filename, templateContent) => {
const occurrenceGroup = getLevelGroup(lowercaseFilenameRule.level);
const templateResults = {
fixed : false,
errors : {},
warnings : {},
info : {}
};
if (lowercaseFilenameRule.isEnabled()) {
const ruleResult = lowercaseFilenameRule.check(filename, templateContent);
if (ruleResult.occurrenceList.length > 0) {
const occurrenceObj = getOccurrenceObj(lowercaseFilenameRule, ruleResult.occurrenceList);
templateResults[occurrenceGroup] = Object.assign(templateResults[occurrenceGroup], occurrenceObj[occurrenceGroup]);
}
}
return templateResults;
};
const checkAndPossiblyFixTreeRules = (templatePath, templateContent, config, data) => {
if (!config.disableTreeParse) {
ConsoleUtils.displayVerboseMessage(`Building tree for "${templatePath}"`, 1);
const tree = TreeBuilder.build(templatePath, templateContent);
if (!tree.rootNode) {
throw tree.exception;
}
const ruleArray = getEnabledTreeRules();
return fixTemplateOrReportIssuesForRuleList(
ruleArray,
templatePath,
tree.rootNode,
config,
data);
}
};
const checkAndPossiblyFixLineByLineRules = (templatePath, templateContent, config, data) => {
const ruleArray = getEnabledLineRules();
return fixTemplateOrReportIssuesForRuleList(
ruleArray,
templatePath,
templateContent,
config,
data);
};
const checkCustomModules = () => {
const occurrenceGroup = getLevelGroup(CustomModulesRule.level);
const moduleResults = {
errors : [],
warnings : [],
info : []
};
if (CustomModulesRule.isEnabled()) {
for (const tag in CustomTagContainer) {
const occurrenceObj = checkCustomTag(tag);
if (occurrenceObj) {
moduleResults[occurrenceGroup].push(occurrenceObj);
}
}
}
return moduleResults;
};
const parseAndPossiblyFixTemplate = (templatePath, data, content = '', templateName = '') => {
ConsoleUtils.displayVerboseMessage(`\nChecking "${templatePath}" template`);
const config = ConfigUtils.load();
const templateContent = content || fs.readFileSync(templatePath, 'utf-8');
const lineResults = checkAndPossiblyFixLineByLineRules(templatePath, templateContent, config, data);
const treeResults = checkAndPossiblyFixTreeRules(templatePath, lineResults.finalContent, config, data) || { errors : [] };
const filenameResults = checkFileName(templateName, templateContent);
return {
fixed : lineResults.fixed || treeResults.fixed,
treeData : treeResults.data,
errors : {
...lineResults.errors,
...treeResults.errors,
...filenameResults.errors
},
warnings : {
...lineResults.warnings,
...treeResults.warnings,
...filenameResults.warnings
},
info : {
...lineResults.info,
...treeResults.info,
...filenameResults.info
}
};
};
const getAvailableRulesQty = () => treeRules.length + lineByLineRules.length;
const getEnabledLineRules = () => {
const result = [];
for (let i = 0; i < lineByLineRules.length; i++) {
const rule = lineByLineRules[i];
// TODO Do this in a better way;
rule.level = rule.getConfigs().level || 'errors';
if (rule.isEnabled() && rule.id !== 'lowercase-filename') {
result.push(rule);
}
}
return result;
};
const getEnabledTreeRules = () => {
const result = [];
for (let i = 0; i < treeRules.length; i++) {
const rule = treeRules[i];
// TODO Do this in a better way;
rule.level = rule.getConfigs().level || 'errors';
if (rule.isEnabled()) {
result.push(rule);
}
}
return result;
};
module.exports.getAllLineRules = () => lineByLineRules;
module.exports.findNodeOfType = findNodeOfType;
module.exports.isTypeAmongTheFirstElements = isTypeAmongTheFirstElements;
module.exports.parseAndPossiblyFixTemplate = parseAndPossiblyFixTemplate;
module.exports.checkCustomModules = checkCustomModules;
module.exports.getAvailableRulesQty = getAvailableRulesQty;
module.exports.getLevelGroup = getLevelGroup;