chameleon-linter
Version:
cml规范校验工具
289 lines (267 loc) • 9.32 kB
JavaScript
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const groupBy = require('lodash.groupby');
const filter = require('lodash.filter');
const map = require('lodash.map');
const cliUtils = require('chameleon-tool-utils');
const config = require('./config');
const Message = require('./classes/message');
let isCmlComponent = (templatePath, usingPath) => {
let currentWorkspace = config.getCurrentWorkspace();
let interfaceInfo = cliUtils.findInterfaceFile(currentWorkspace, templatePath, usingPath);
let componentInfo = cliUtils.lintHandleComponentUrl(currentWorkspace, templatePath, usingPath);
return !!interfaceInfo.filePath || (componentInfo && componentInfo.isCml);
}
let toSrcPath = (filePath = '') => {
return (filePath && path.isAbsolute(filePath)) ? path.relative(config.getCurrentWorkspace(), filePath) : filePath;
}
/**
* 转换成驼峰写法
*
* @param {string} variable 变量名称
* @return {string} 变量驼峰命名
*/
let toCamelCase = variable => variable.replace(/-(\w)/g, (all, letter) => letter.toUpperCase());
/**
*
* @param {String} str target string
*/
const toDash = (str) => {
return str.replace(/([A-Z])/g, (match, caps) => {
return '-' + caps.toLowerCase();
});
};
/**
* 获取全部的部分
*
* @param {string} content 内容
* @param {string} platform 平台
* @return {Array} 数组
*/
let getCmlFileParts = (filepath, platform) => {
let content = fs.readFileSync(filepath, 'utf8');
let parts = cliUtils.splitParts({content});
let result = {};
map(parts, (values, key) => {
switch (key) {
case 'script':
if (values.length) {
values.forEach(value => {
// interface | json
if (value.attrs && value.cmlType) {
result[value.cmlType] = value;
result[value.cmlType].type = value.cmlType;
}
else {
result[key] = value;
}
});
}
break;
default:
if (values.length) {
result[key] = values[0];
}
break;
}
map(result, (value, type) => {
let params = {};
map(result[type].attrs, (attr, key) => {
params[toCamelCase(key)] = attr;
});
Object.assign(result[type], {
params: params,
line: result[type].startLine,
file: filepath,
platformType: platform,
rawContent: result[type].tagContent
});
});
});
return result;
};
/**
* 获取所有的片段
*
* @param {string} filepath 文件路径
* @return {Objhect} 文件片段表
*/
let getCmlParts = filepath => {
let parts = {};
let platforms = config.getPlatforms();
let platform;
let result = new RegExp('([^/]*?)\.(' + platforms.join('|') + ')\.cml$', 'g').exec(filepath);
if (result) {
let interfaceFile = filepath.replace(new RegExp('\.(' + platforms.join('|') + ')\.cml$', 'ig'), '.interface');
platform = result[2];
if (fs.existsSync(interfaceFile)) {
Object.assign(parts, getCmlFileParts(interfaceFile));
}
}
Object.assign(parts, getCmlFileParts(filepath, platform));
return parts;
};
let getInterfaceParts = filepath => {
let _result = {
parts: {},
messages: []
};
let lastFilePath = filepath;
_retrieveParts(filepath);
function _retrieveParts(interfaceFilePath) {
// terminate condition
if (!fs.existsSync(interfaceFilePath)) {
_result.messages.push(new Message({
msg: `The file: "${toSrcPath(interfaceFilePath)}" referenced by file: "${toSrcPath(lastFilePath)}" was not found`
}));
return;
}
const content = fs.readFileSync(interfaceFilePath, 'utf8');
const parts = cliUtils.splitParts({content});
// search parts.script array for interface defination and platform specific definations.
if (parts.script) {
parts.script.forEach(item => {
let errMsg = null;
let extraPartInfo = {
params: {},
line: item.startLine,
file: interfaceFilePath,
rawContent: item.tagContent,
platformType: item.cmlType
};
// for interface portion we should keep the origin filepath
if (item.cmlType === 'interface') {
extraPartInfo.file = filepath;
}
// check src references for platform definations
if (item.cmlType != 'interface' && item.attrs && item.attrs.src) {
const targetScriptPath = path.resolve(path.dirname(interfaceFilePath), item.attrs.src);
// the referenced source is a js file
if (/.js$/.test(item.attrs.src)) {
if (!fs.existsSync(targetScriptPath)) {
errMsg = new Message({
line: item.line,
column: item.tagContent.indexOf(item.attrs.src) + 1,
token: item.attrs.src,
msg: `The javascript file: "${toSrcPath(targetScriptPath)}" specified with attribute src was not found`
});
} else {
extraPartInfo.content = extraPartInfo.rawContent = extraPartInfo.tagContent = fs.readFileSync(targetScriptPath, 'utf8');
extraPartInfo.file = targetScriptPath;
}
}
// the referenced source is a cml file
if (/.cml$/.test(item.attrs.src)) {
if (!fs.existsSync(targetScriptPath)) {
errMsg = new Message({
line: item.line,
column: item.tagContent.indexOf(item.attrs.src) + 1,
token: item.attrs.src,
msg: `The cml file: "${toSrcPath(targetScriptPath)}" specified with attribute src was not found`
});
} else {
const cmlFileContent = fs.readFileSync(targetScriptPath, 'utf8');
const cmlParts = cliUtils.splitParts({content: cmlFileContent});
const scriptPart = cmlParts.script ? cmlParts.script.filter(part => {
return part.type === 'script';
}) : null;
if (scriptPart && scriptPart.length) {
extraPartInfo.content = extraPartInfo.rawContent = extraPartInfo.tagContent = scriptPart[0].content;
extraPartInfo.file = targetScriptPath;
item.cmlType = 'script';
} else {
errMsg = new Message({
line: item.line,
column: item.tagContent.indexOf(item.attrs.src) + 1,
token: item.attrs.src,
msg: `The referenced file: "${toSrcPath(targetScriptPath)}" may not has a script portion`
});
}
}
}
}
// previous cmlType defination has a higher priority.
if (!errMsg && !_result.parts[item.cmlType]) {
_result.parts[item.cmlType] = {...item, ...extraPartInfo};
}
if (errMsg) {
_result.messages.push(errMsg);
}
});
}
// search parts.customBlocks array for include defination which may contains another interface file.
let include = null;
if (parts.customBlocks) {
parts.customBlocks.forEach(item => {
if (item.type === 'include') {
include = item;
}
});
}
if (include && include.attrs && include.attrs.src) {
let newFilePath = path.resolve(path.dirname(interfaceFilePath), include.attrs.src);
lastFilePath = interfaceFilePath;
return _retrieveParts(newFilePath);
}
return;
}
return _result;
}
/**
* 输出出错信息
*
* @param {Object} result 出错信息对象
* @param {string} filepath 文件路径
*/
let outputWarnings = (result) => {
let flag = false;
result = filter(Object.values(result), (item) => {
if (!item.messages || (item.messages && item.messages.length == 0)) {
return false;
}
flag = true;
return true;
});
result = groupBy(result, 'file');
for (let key of Object.keys(result)) {
if (key !== 'undefined') {
console.log(chalk.yellow('[file]') + ': ' + key);
}
result[key]
.sort((a, b) => {
return a.start - b.start;
})
.forEach((item) => {
if (item.type) {
console.log(chalk.red(item.type + (item.platform ? (' [' + item.platform + ']') : '') + ' errors:'));
}
item.messages
.sort((preMsg, nextMsg) => {
if (preMsg.line == undefined || preMsg.column == undefined || nextMsg.line == undefined || nextMsg.column == undefined) {
return 0;
}
return (preMsg.line - nextMsg.line) * 10000 + (preMsg.column - nextMsg.column);
})
.forEach((message) => {
if (message.line !== undefined && item.start !== undefined && message.column !== undefined) {
console.log('[' + chalk.cyan(message.line + item.start - 1) + ' (line), ' + chalk.cyan(message.column) + ' (column)]' + ' ' + message.msg);
}
else {
console.log(message.msg);
}
});
});
// 保留!!!需要输出一个空行
console.log('');
}
return flag;
};
module.exports = {
getCmlParts,
getInterfaceParts,
outputWarnings,
toDash,
isCmlComponent,
toSrcPath
};