提交 e55aab4c 编写于 作者: K kevinluohuan

interface check support include

上级 69ae5e1f
#!/usr/bin/env node
// --inspect-brk
const program = require('commander');
const packageJson = require('../package.json');
......
const traverse = require('@babel/traverse')['default'];
const deepTraverse = require('traverse');
const uniq = require('lodash.uniq');
const config = require('../config');
const DEFAULT_TOKENS_MAP = {
WEEX: ['weex', 'global'],
......@@ -62,6 +61,7 @@ const DEFAULT_TOKENS_MAP = {
]
};
// 这个path是否有需要校验的token
const needCheck = function (path, tokenList) {
let flag = false;
......@@ -103,37 +103,20 @@ function checkToken(path, token) {
}
const checkGlobal = function (ast, type = 'ALL') {
let tokensMap = DEFAULT_TOKENS_MAP;
const TOKENS_MAP = tokensMap;
type = type.toUpperCase();
const TOKENS_MAP = DEFAULT_TOKENS_MAP;
let tokenList = [];
let messages = [];
type = type.toUpperCase();
if (type === 'ALL') {
// 都要校验
tokenList = TOKENS_MAP[type];
}
else {
Object.keys(TOKENS_MAP).forEach(key => {
// 把自身的和All的去掉,其他端的token放进去
if (key !== type && key !== 'ALL') {
tokenList = tokenList.concat(TOKENS_MAP[key]);
}
})
// 然后要把自身的全局变量去掉
for (let i = 0;i < tokenList.length;) {
if (~TOKENS_MAP[type].indexOf(tokenList[i])) {
tokenList.splice(i, 1);
}
else {
i++;
}
Object.keys(TOKENS_MAP).forEach(key => {
if (type === 'ALL' || key !== type) {
tokenList = tokenList.concat(TOKENS_MAP[key]);
}
}
});
tokenList = uniq(tokenList);
const messages = [];
traverse(ast, {
tokenList.length && traverse(ast, {
enter: (path) => {
// path是一个上下文
// 需要校验的变量值
......@@ -154,8 +137,6 @@ const checkGlobal = function (ast, type = 'ALL') {
}
next = next.parent;
}
if (globalVariable && path.parent.type != 'ObjectMethod' && path.parent.type != 'ClassMethod') {
messages.push({
line: path.node.loc.start.line,
......@@ -197,11 +178,15 @@ const getInterfaces = (ast) => {
/**
* 获取类定义
*
* @param {Object} ast ast
* @return {Object} 类定义
* @param {Object} ast ast
* @param {Object} isComp a flag indentify whether the ast is component or an interface portion
* @return {Object} 类定义
*/
const getClass = (ast) => {
const getClass = (ast, isComp) => {
return isComp ? getCompClassDef(ast) : getInterfacePortionClassDef(ast);
};
function getCompClassDef(ast) {
let classes = [];
traverse(ast, {
......@@ -221,15 +206,18 @@ const getClass = (ast) => {
});
}
// 参数
path.node.body.body.forEach(define => {
if (define.key.name == 'props') {
define.value.properties.forEach(property => {
clazz.properties.push(property.key.name);
});
}
else if (define.type == 'ClassMethod') {
clazz.methods.push(define.key.name);
else if (define.key.name == 'methods') {
define.value.properties.filter(property => {
return property.type === 'ObjectMethod';
}).forEach(property => {
clazz.methods.push(property.key.name);
});
}
else {
deepTraverse(define)
......@@ -268,7 +256,45 @@ const getClass = (ast) => {
});
return classes;
};
}
function getInterfacePortionClassDef(ast) {
let classes = [];
traverse(ast, {
enter(path) {
if (path.node.type == 'ClassDeclaration') {
let clazz = {
interfaces: [],
properties: [],
events: [],
methods: []
};
// 接口
if (path.node['implements']) {
path.node['implements'].forEach(implament => {
clazz.interfaces.push(implament.id.name);
});
}
// 参数
path.node.body.body.forEach(define => {
if (define.type == 'ClassProperty') {
clazz.properties.push(define.key.name);
}
else if (define.type == 'ClassMethod') {
clazz.methods.push(define.key.name);
}
});
classes.push(clazz);
}
}
});
return classes;
}
/**
* 校验接口与脚本
......@@ -277,76 +303,62 @@ const getClass = (ast) => {
* @return {Array} 数组
*/
const checkScript = async (result) => {
let script;
let platforms = config.getPlatforms();
['script'].concat(platforms).forEach(item => {
if (result[item] && result[item].ast) {
script = result[item];
}
});
if (!result['interface'] && script) {
let interfaceFile = script.file.replace(new RegExp('\\.(' + platforms.join('|') + ')\\.cml$', 'ig'), '.interface');
if (/\.interface$/.test(interfaceFile)) {
result['interface'] = {
messages: [{
msg: 'file: [' + interfaceFile + '] was not found!'
}],
file: interfaceFile
};
let validPlatforms = Object.keys(result)
.filter(platform => {
return platform && (platform != 'interface');
});
// add a script type for multi-file components.
result['interface'] && validPlatforms.concat('script').forEach(platform => {
let script;
if (result[platform] && result[platform].ast) {
script = result[platform];
}
}
if (result['interface'] && result['interface'].ast && script && script.ast) {
const interfaceDefine = getInterfaces(result['interface'].ast);
const classDefines = getClass(script.ast);
classDefines.forEach(clazz => {
clazz.interfaces.forEach(interfaceName => {
let define = interfaceDefine[interfaceName];
for (let key of Object.keys(define)) {
if ((define[key] && define[key].type == 'Generic') && clazz.properties.indexOf(key) == -1) {
result['interface'].messages.push({
line: define[key].line,
column: define[key].column,
token: key,
msg: 'property [' + key + '] is not found in file [' + script.file + ']'
});
}
else if ((define[key] && define[key].type == 'Function') && clazz.methods.indexOf(key) == -1) {
platforms.forEach(platform => {
if (result[platform]) {
result['interface'].messages.push({
line: define[key].line,
column: define[key].column,
token: key,
msg: 'method [' + key + '] is not found in file [' + script.file + ']'
});
}
});
if (result['interface'] && result['interface'].ast && script && script.ast) {
const interfaceDefine = getInterfaces(result['interface'].ast);
const classDefines = getClass(script.ast, platform === 'script');
classDefines.forEach(clazz => {
clazz.interfaces.forEach(interfaceName => {
let define = interfaceDefine[interfaceName];
for (let key of Object.keys(define)) {
if ((define[key] && define[key].type == 'Generic') && clazz.properties.indexOf(key) == -1) {
result['interface'].messages.push({
line: define[key].line,
column: define[key].column,
token: key,
msg: `interface property [ ${key} ] is not defined for platform "${platform}" in file [ ${script.file} ]`
});
}
else if ((define[key] && define[key].type == 'Function') && clazz.methods.indexOf(key) == -1) {
result['interface'].messages.push({
line: define[key].line,
column: define[key].column,
token: key,
msg: `interface method [${key}] is not defined for platform "${platform}" in file [ ${script.file} ]`
});
}
}
}
clazz.events.forEach(event => {
if (!define[event.event] || (define[event.event] && (define[event.event].type != 'Function'))) {
script.messages.push({
line: event.line,
column: event.column,
token: event.event,
msg: 'event [' + event.event + '] is not defined in interface file [' + result['interface'].file + ']'
});
}
clazz.events.forEach(event => {
if (!define[event.event] || (define[event.event] && (define[event.event].type != 'Function'))) {
script.messages.push({
line: event.line,
column: event.column,
token: event.event,
msg: 'event [' + event.event + '] is not defined in interface file [' + result['interface'].file + ']'
});
}
});
});
});
});
if (script.platform) {
let messages = checkGlobal(script.ast, script.platform);
script.messages = script.messages.concat(messages);
if (script.platform) {
let messages = checkGlobal(script.ast, script.platform);
script.messages = script.messages.concat(messages);
}
}
}
});
};
......
/**
* A class reprents a customized component.
* A class represents a customized component.
*/
class CustomizedNode {
constructor(tag, lang = 'cml') {
......
......@@ -3,7 +3,7 @@ const Tools = require('../../tools');
const mustacheRegex = /{{(.*?)}}/g;
module.exports = {
name: 'method-node',
name: 'mustache-node',
on: ['cml', 'vue'],
filter: {
key: 'rawValue',
......
......@@ -67,6 +67,14 @@ module.exports.parseSingleExpression = function(expressinoStr = '') {
nodes.push(getVarFromIdentifier(path.node.property, isFakeBlock));
}
},
LogicalExpression(path) {
if (path.get('left').isIdentifier()) {
nodes.push(getVarFromIdentifier(path.node.left, isFakeBlock));
}
if (path.get('right').isIdentifier()) {
nodes.push(getVarFromIdentifier(path.node.right, isFakeBlock));
}
},
BinaryExpression(path) {
if (path.get('left').isIdentifier()) {
nodes.push(getVarFromIdentifier(path.node.left, isFakeBlock));
......
/**
* A class represents an error message.
*/
class Message {
constructor({ line = undefined, column = undefined, token = '', msg = '' }) {
this.line = line;
this.column = column;
this.token = token;
this.msg = msg || 'An unknown error occurred'
}
}
module.exports = Message;
......@@ -76,7 +76,6 @@ const lintCmlFile = async (parts) => {
* @return {Promise} promise
*/
const checkFile = async (lintedResult, filepath) => {
// 校验style
checkers.style(lintedResult);
......@@ -108,7 +107,6 @@ const checkFile = async (lintedResult, filepath) => {
*/
const checkFileContent = async (filepath) => {
let parts = utils.getCmlParts(filepath);
let result = await lintCmlFile(parts);
result = await checkFile(result, filepath);
......@@ -128,7 +126,6 @@ const checkCMLFileSpecification = async (filepath) => {
messages: []
}
};
if (new RegExp('([^/]*?)\.(' + platforms.join('|') + ')\.cml$', 'g').test(filepath)) {
let interfaceFile = filepath.replace(new RegExp('\.(' + platforms.join('|') + ')\.cml$', 'g'), '.interface');
if (!fs.existsSync(interfaceFile)) {
......@@ -153,10 +150,9 @@ const checkCMLFileSpecification = async (filepath) => {
* @return {Promise} promise
*/
const checkInterfaceFileSpecification = async (filepath) => {
let parts = utils.getInterfaceParts(filepath);
let {parts} = utils.getInterfaceParts(filepath);
let result = {};
let keys = Object.keys(parts);
if (keys.length > 1) {
for (let key in parts) {
......@@ -170,7 +166,6 @@ const checkInterfaceFileSpecification = async (filepath) => {
result[key].platform = part.platformType;
}
}
// 校验脚本
checkers.script(result);
return result;
......
......@@ -49,7 +49,6 @@ module.exports = async (currentWorkspace, needOutputWarnings = true) => {
console.log(chalk.red('[ERROR] ') + 'The current project is not a chameleon project!');
return;
}
let results = [];
if (config.getRuleOption('core-files-check')) {
......
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();
......@@ -111,25 +113,72 @@ let getCmlParts = filepath => {
let getInterfaceParts = filepath => {
let content = fs.readFileSync(filepath, 'utf8');
let result = {};
let parts = cliUtils.splitParts({content});
let _result = {
parts: {},
messages: []
};
if (parts.script) {
parts.script.forEach(item => {
result[item.cmlType] = item;
_retrieveParts(filepath);
Object.assign(result[item.cmlType], {
params: {},
line: item.startLine,
file: filepath,
rawContent: item.tagContent,
platformType: item.cmlType
function _retrieveParts(interfaceFilePath) {
// terminate condition
if (!fs.existsSync(interfaceFilePath)) {
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 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);
if (!fs.existsSync(targetScriptPath)) {
_result.messages.push(new Message({
line: item.line,
column: item.tagContent.indexOf(item.attrs.src) + 1,
token: item.attrs.src,
msg: `The file ${item.attrs.src} specified with src attribute was not found`
}));
}
extraPartInfo.content = extraPartInfo.rawContent = extraPartInfo.tagContent = fs.readFileSync(targetScriptPath, 'utf8');
extraPartInfo.file = targetScriptPath;
}
// previous cmlType defination has a higher priority.
if (!_result.parts[item.cmlType]) {
_result.parts[item.cmlType] = {...item, ...extraPartInfo};
}
});
});
}
// 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);
return _retrieveParts(newFilePath);
}
return;
}
return result;
return _result;
}
......@@ -165,7 +214,10 @@ let outputWarnings = (result) => {
item.messages
.sort((preMsg, nextMsg) => {
return preMsg.line - nextMsg.line;
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) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册