未验证 提交 bf6762e1 编写于 作者: B beatles-chameleon 提交者: GitHub

Merge pull request #222 from didi/0.3.x-alpha-kevin

0.3.x alpha kevin
......@@ -2,7 +2,7 @@
- 支持web weex可配置是否包裹组件
## [0.3.3-alpha.2]
### Bug Fixes
### Bug Fixes
* 修复 weex dev模式liveload失效
老项目如果修复,还需要升级项目中两个npm包如下:
......
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'],
WX: ['wx', 'global'],
BAIDU: ['swan', 'global'],
ALIPAY: ['my', 'global'],
WEB: [
'postMessage', 'blur', 'focus', 'close', 'frames', 'self',
'window', 'parent', 'opener', 'top', 'length', 'closed',
'location', 'document', 'origin', 'name', 'history',
'locationbar', 'menubar', 'personalbar', 'scrollbars',
'statusbar', 'toolbar', 'status', 'frameElement', 'navigator',
'customElements', 'external', 'screen', 'innerWidth',
'innerHeight', 'scrollX', 'pageXOffset', 'scrollY',
'pageYOffset', 'screenX', 'screenY', 'outerWidth', 'outerHeight',
'devicePixelRatio', 'clientInformation', 'screenLeft',
'screenTop', 'defaultStatus', 'defaultstatus', 'styleMedia',
'onanimationend', 'onanimationiteration', 'onanimationstart',
'onsearch', 'ontransitionend', 'onwebkitanimationend',
'onwebkitanimationiteration', 'onwebkitanimationstart',
'onwebkittransitionend', 'isSecureContext', 'onabort', 'onblur',
'oncancel', 'oncanplay', 'oncanplaythrough', 'onchange', 'onclick',
'onclose', 'oncontextmenu', 'oncuechange', 'ondblclick', 'ondrag',
'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart',
'ondrop', 'ondurationchange', 'onemptied', 'onended', 'onerror',
'onfocus', 'oninput', 'oninvalid', 'onkeydown', 'onkeypress',
'onkeyup', 'onload', 'onloadeddata', 'onloadedmetadata',
'onloadstart', 'onmousedown', 'onmouseenter', 'onmouseleave',
'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel',
'onpause', 'onplay', 'onplaying', 'onprogress', 'onratechange', 'onreset',
'onresize', 'onscroll', 'onseeked', 'onseeking', 'onselect', 'onstalled',
'onsubmit', 'onsuspend', 'ontimeupdate', 'ontoggle', 'onvolumechange',
'onwaiting', 'onwheel', 'onauxclick', 'ongotpointercapture',
'onlostpointercapture', 'onpointerdown', 'onpointermove',
'onpointerup', 'onpointercancel', 'onpointerover', 'onpointerout',
'onpointerenter', 'onpointerleave', 'onafterprint', 'onbeforeprint',
'onbeforeunload', 'onhashchange', 'onlanguagechange', 'onmessage',
'onmessageerror', 'onoffline', 'ononline', 'onpagehide', 'onpageshow',
'onpopstate', 'onrejectionhandled', 'onstorage', 'onunhandledrejection',
'onunload', 'performance', 'stop', 'open', 'alert', 'confirm', 'prompt',
'print', 'requestAnimationFrame', 'cancelAnimationFrame', 'requestIdleCallback',
'cancelIdleCallback', 'captureEvents', 'releaseEvents', 'getComputedStyle',
'matchMedia', 'moveTo', 'moveBy', 'resizeTo', 'resizeBy', 'getSelection', 'find',
'webkitRequestAnimationFrame', 'webkitCancelAnimationFrame', 'fetch',
'btoa', 'atob', 'createImageBitmap', 'scroll', 'scrollTo', 'scrollBy',
'onappinstalled', 'onbeforeinstallprompt', 'crypto', 'ondevicemotion',
'ondeviceorientation', 'ondeviceorientationabsolute', 'indexedDB',
'webkitStorageInfo', 'sessionStorage', 'localStorage', 'chrome',
'visualViewport', 'speechSynthesis', 'webkitRequestFileSystem',
'webkitResolveLocalFileSystemURL', 'openDatabase', 'applicationCache',
'caches', 'whichAnimationEvent', 'animationendEvent', 'infinity',
'SETTING', 'AppView', 'ExtensionOptions', 'ExtensionView', 'WebView',
'iconPath', '_app', '_ZOOM_', 'Feed', 'md5', '$', 'jQuery', 'Search',
'windmill', 'Lethargy', 'alertTimeOut', 'supportApps', 'lethargyX',
'lethargyY', 'iView', 'onModuleResLoaded', 'iEditDelete', 'infinityDrag',
'i', 'array', 'TEMPORARY', 'PERSISTENT', 'addEventListener',
'removeEventListener', 'dispatchEvent'
]
};
// 这个path是否有需要校验的token
const needCheck = function (path, tokenList) {
let flag = false;
for (let i = 0; i < tokenList.length; i++) {
let token = tokenList[i];
// 判断一个token是否是一个变量
if (checkToken(path, token)) {
// 如果是对象中的key是需要忽略的
flag = token;
break;
}
}
return flag;
};
// 是变量,并且不是对象的key值
function checkToken(path, token) {
if (path.isIdentifier({ name: token })) {
// 是对象的key值
// const api = {
// name: weex,
// };
let isObjectKey = path.parent.type === 'ObjectProperty' && path.parentKey === 'key'
// 对象成员
/**
* var a = {
* weex: 'a'
* }
* console.log(weex.a);
*/
let isObjectMember = path.parent.type === 'MemberExpression' && path.parentKey === 'property'
return !(isObjectKey || isObjectMember);
}
else {
return false;
}
}
const checkGlobal = function (ast, type = 'ALL') {
let tokensMap = DEFAULT_TOKENS_MAP;
const TOKENS_MAP = tokensMap;
type = type.toUpperCase();
let tokenList = [];
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++;
}
}
}
tokenList = uniq(tokenList);
const messages = [];
traverse(ast, {
enter: (path) => {
// path是一个上下文
// 需要校验的变量值
let token = needCheck(path, tokenList);
// 如果存在该token
if (token) {
let globalVariable = true;
// 当前作用域
let next = path.scope;
while (next) {
// 如果当前作用域存在该变量 就不是全局变量
if (next.hasOwnBinding(token)) {
globalVariable = false;
break;
}
next = next.parent;
}
if (globalVariable && path.parent.type != 'ObjectMethod' && path.parent.type != 'ClassMethod') {
messages.push({
line: path.node.loc.start.line,
column: path.node.loc.start.column,
token: token,
msg: 'global variable [' + token + '] should not used in this file'
});
}
}
}
});
return messages;
}
const utils = require('../utils');
/**
* 获取接口定义
......@@ -178,13 +8,20 @@ const checkGlobal = function (ast, type = 'ALL') {
* @return {Object} 分析结果
*/
const getInterfaces = (ast) => {
let result = {};
let result = {
name: '',
properties: {}
};
ast.program.body.forEach(function (node) {
if (node.type == 'InterfaceDeclaration') {
let interfaceName = node.id.name;
result[interfaceName] = {};
result.name = interfaceName;
result.loc = {
line: node.id.loc.start.line,
column: node.id.loc.start.column
};
node.body.properties.map((property) => {
result[interfaceName][property.key.name] = {
result.properties[property.key.name] = {
type: property.value.type.replace(/TypeAnnotation/g, ''),
line: property.key.loc.start.line,
column: property.key.loc.start.column
......@@ -197,11 +34,89 @@ 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 = [];
let clazz = {
interfaces: [],
properties: [],
events: [],
methods: []
};
traverse(ast, {
ClassDeclaration(path) {
// 接口
if (path.node['implements']) {
path.node['implements'].forEach(implament => {
clazz.interfaces.push(implament.id.name);
});
}
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.key.name == 'methods') {
define.value.properties.filter(property => {
return property.type === 'ObjectMethod';
}).forEach(property => {
clazz.methods.push(property.key.name);
});
}
});
classes.push(clazz);
},
MemberExpression(path) {
if (!path.node.computed && path.get('object').isThisExpression() && path.get('property').isIdentifier()) {
if (path.node.property.name === '$cmlEmit') {
let parentNode = path.findParent(path => path.isCallExpression());
if (parentNode && parentNode.get('arguments')) {
let event = null;
let nameArg = parentNode.get('arguments')[0];
if (nameArg.isStringLiteral()) {
event = {
event: nameArg.node.value,
line: nameArg.node.loc.start.line,
column: nameArg.node.loc.start.column
};
} else if (nameArg.isIdentifier()) {
let argBinding = nameArg.scope.getBinding(nameArg.node.name);
let possibleInit = argBinding ? argBinding.path.node.init : null;
// For now, we only check just one jump along its scope chain.
if (possibleInit && possibleInit.type === 'StringLiteral') {
event = {
event: possibleInit.value,
line: nameArg.node.loc.start.line,
column: nameArg.node.loc.start.column
};
}
}
if (event) {
clazz.methods.push(event.event);
clazz.events.push(event);
}
}
}
}
}
});
return classes;
}
function getInterfacePortionClassDef(ast) {
let classes = [];
traverse(ast, {
......@@ -223,43 +138,12 @@ 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);
});
if (define.type == 'ClassProperty') {
clazz.properties.push(define.key.name);
}
else if (define.type == 'ClassMethod') {
clazz.methods.push(define.key.name);
}
else {
deepTraverse(define)
.nodes()
.filter(function (item) {
if (item && item.type == 'CallExpression') {
return true;
}
})
.forEach(function (item) {
if (item.callee.property && item.callee.property.name == '$cmlEmit') {
let event = {};
item.arguments.map((arg) => {
if (arg.value) {
event.line = arg.loc.start.line;
event.column = arg.loc.start.column;
event.event = arg.value;
}
else if (arg.properties) {
event.arguments = arg.properties.map(property => {
return property.key.name;
});
}
});
clazz.events.push(event);
}
});
}
});
classes.push(clazz);
......@@ -268,7 +152,8 @@ const getClass = (ast) => {
});
return classes;
};
}
/**
* 校验接口与脚本
......@@ -277,76 +162,66 @@ 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 && (!~['json', 'template', 'style', 'script'].indexOf(platform));
})
.filter(platform => {
return platform && (platform != 'interface');
});
// add a script type for multi-file components.
result['interface'] && validPlatforms.concat('script').forEach(platform => {
let script;
let isComp = (platform === '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) {
if (result['interface'] && result['interface'].ast && script && script.ast) {
const interfaceDefine = getInterfaces(result['interface'].ast);
const classDefines = getClass(script.ast, isComp);
classDefines.forEach(clazz => {
let define = null;
clazz.interfaces.forEach(interfaceName => {
define = interfaceDefine.name === interfaceName ? interfaceDefine.properties : null;
if (!define) {
result['interface'].messages.push({
line: define[key].line,
column: define[key].column,
token: key,
msg: 'property [' + key + '] is not found in file [' + script.file + ']'
msg: `The implement class name: "${interfaceName}" used in file: "${utils.toSrcPath(script.file)}" doesn\'t match the name defined in it\'s interface file: "${utils.toSrcPath(result['interface'].file)}"`
});
return;
}
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 + ']'
});
}
});
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 ${script.platform} in file "${utils.toSrcPath(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 ${script.platform} in file "${utils.toSrcPath(script.file)}"`
});
}
}
}
});
clazz.events.forEach(event => {
define && 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 + ']'
msg: 'event "' + event.event + '" is not defined in interface file "' + utils.toSrcPath(result['interface'].file) + '"'
});
}
});
});
});
if (script.platform) {
let messages = checkGlobal(script.ast, script.platform);
script.messages = script.messages.concat(messages);
}
}
});
};
......
......@@ -58,7 +58,6 @@ class TemplateChecker {
.join('|');
usingProps = `|${usingProps}|`;
usingEvents = `|${usingEvents}|`;
debugger
props.filter((prop) => usingProps.indexOf('|' + prop.name + '|') === -1).forEach((prop) => {
issues.push({
line: prop.pos[0],
......
......@@ -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',
......
......@@ -75,6 +75,14 @@ module.exports.parseSingleExpression = function(expressinoStr = '') {
nodes.push(getVarFromIdentifier(path.node.right, 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));
}
},
SequenceExpression(path) {
path.get('expressions').forEach((ele) => {
if (ele.isIdentifier()) {
......
const globalVars = {
WEEX: ['weex', 'global'],
WX: ['wx', 'global'],
BAIDU: ['swan', 'global'],
ALIPAY: ['my', 'global'],
QQ: ['qq', 'global'],
WEB: [
'postMessage', 'blur', 'focus', 'close', 'frames', 'self',
'window', 'parent', 'opener', 'top', 'length', 'closed',
'location', 'document', 'origin', 'name', 'history',
'locationbar', 'menubar', 'personalbar', 'scrollbars',
'statusbar', 'toolbar', 'status', 'frameElement', 'navigator',
'customElements', 'external', 'screen', 'innerWidth',
'innerHeight', 'scrollX', 'pageXOffset', 'scrollY',
'pageYOffset', 'screenX', 'screenY', 'outerWidth', 'outerHeight',
'devicePixelRatio', 'clientInformation', 'screenLeft',
'screenTop', 'defaultStatus', 'defaultstatus', 'styleMedia',
'onanimationend', 'onanimationiteration', 'onanimationstart',
'onsearch', 'ontransitionend', 'onwebkitanimationend',
'onwebkitanimationiteration', 'onwebkitanimationstart',
'onwebkittransitionend', 'isSecureContext', 'onabort', 'onblur',
'oncancel', 'oncanplay', 'oncanplaythrough', 'onchange', 'onclick',
'onclose', 'oncontextmenu', 'oncuechange', 'ondblclick', 'ondrag',
'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart',
'ondrop', 'ondurationchange', 'onemptied', 'onended', 'onerror',
'onfocus', 'oninput', 'oninvalid', 'onkeydown', 'onkeypress',
'onkeyup', 'onload', 'onloadeddata', 'onloadedmetadata',
'onloadstart', 'onmousedown', 'onmouseenter', 'onmouseleave',
'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel',
'onpause', 'onplay', 'onplaying', 'onprogress', 'onratechange', 'onreset',
'onresize', 'onscroll', 'onseeked', 'onseeking', 'onselect', 'onstalled',
'onsubmit', 'onsuspend', 'ontimeupdate', 'ontoggle', 'onvolumechange',
'onwaiting', 'onwheel', 'onauxclick', 'ongotpointercapture',
'onlostpointercapture', 'onpointerdown', 'onpointermove',
'onpointerup', 'onpointercancel', 'onpointerover', 'onpointerout',
'onpointerenter', 'onpointerleave', 'onafterprint', 'onbeforeprint',
'onbeforeunload', 'onhashchange', 'onlanguagechange', 'onmessage',
'onmessageerror', 'onoffline', 'ononline', 'onpagehide', 'onpageshow',
'onpopstate', 'onrejectionhandled', 'onstorage', 'onunhandledrejection',
'onunload', 'performance', 'stop', 'open', 'alert', 'confirm', 'prompt',
'print', 'requestAnimationFrame', 'cancelAnimationFrame', 'requestIdleCallback',
'cancelIdleCallback', 'captureEvents', 'releaseEvents', 'getComputedStyle',
'matchMedia', 'moveTo', 'moveBy', 'resizeTo', 'resizeBy', 'getSelection', 'find',
'webkitRequestAnimationFrame', 'webkitCancelAnimationFrame', 'fetch',
'btoa', 'atob', 'createImageBitmap', 'scroll', 'scrollTo', 'scrollBy',
'onappinstalled', 'onbeforeinstallprompt', 'crypto', 'ondevicemotion',
'ondeviceorientation', 'ondeviceorientationabsolute', 'indexedDB',
'webkitStorageInfo', 'sessionStorage', 'localStorage', 'chrome',
'visualViewport', 'speechSynthesis', 'webkitRequestFileSystem',
'webkitResolveLocalFileSystemURL', 'openDatabase', 'applicationCache',
'caches', 'whichAnimationEvent', 'animationendEvent', 'infinity',
'SETTING', 'AppView', 'ExtensionOptions', 'ExtensionView', 'WebView',
'iconPath', '_app', '_ZOOM_', 'Feed', 'md5', '$', 'jQuery', 'Search',
'windmill', 'Lethargy', 'alertTimeOut', 'supportApps', 'lethargyX',
'lethargyY', 'iView', 'onModuleResLoaded', 'iEditDelete', 'infinityDrag',
'i', 'array', 'TEMPORARY', 'PERSISTENT', 'addEventListener',
'removeEventListener', 'dispatchEvent'
]
};
module.exports = globalVars;
......@@ -22,6 +22,7 @@ module.exports = {
'checkbox',
'page',
'router-view',
'cover-view',
'slot',
'aside',
'col',
......
......@@ -200,3 +200,4 @@ const checkFileSpecification = async (filepath, filetype) => {
module.exports = checkFileSpecification;
module.exports.lintCmlFile = lintCmlFile;
module.exports.checkFile = checkFile;
const parse = require('@babel/parser').parse;
const config = require('../config/parser-config');
const traverse = require('@babel/traverse')["default"];
const traverse = require('@babel/traverse')['default'];
const globalVars = require('../config/globalVars');
const uniq = require('lodash.uniq');
const Map = {
watch: 'watcher',
......@@ -8,7 +10,37 @@ const Map = {
methods: 'method'
};
const handleProperty = function (property, propertyName, messages) {
function checkArrowFun(path) {
let messages = [];
if (path.node) {
let properties = path.get('value').get('properties');
switch (path.node.key.name) {
case 'watch':
case 'computed':
case 'methods':
if (properties.forEach) {
(properties || []).forEach(property => {
messages = messages.concat(handleProperty(property, path.node.key.name));
});
}
break;
case 'beforeCreate':
case 'created':
case 'beforeMount':
case 'mounted':
case 'beforeDestroy':
case 'destroyed':
messages = messages.concat(handleProperty(path, path.node.key.name));
break;
default:
break;
}
}
return messages;
}
function handleProperty(property, propertyName) {
let messages = [];
if (property.get('value').isArrowFunctionExpression()) {
let node = property.get('key').node;
let name = node.name;
......@@ -19,9 +51,44 @@ const handleProperty = function (property, propertyName, messages) {
? (Map[propertyName] + ' "' + name + '" cannot be used as an arrow function')
: ('lifecycle hook "' + name + '" cannot be used as an arrow function')
});
}
};
return messages;
}
function getForbiddenGlobalTokens(platform = 'all') {
let tokenList = [];
platform = platform.toUpperCase();
Object.keys(globalVars).forEach(key => {
if (platform === 'ALL' || key !== platform) {
tokenList = tokenList.concat(globalVars[key]);
}
});
tokenList = uniq(tokenList);
return tokenList;
}
function checkGlobal(path, tokenList) {
let messages = [];
let programScope = path.scope;
Object.keys(programScope.globals).forEach(tokenName => {
if (~tokenList.indexOf(tokenName)) {
messages.push({
line: programScope.globals[tokenName].loc.start.line,
column: programScope.globals[tokenName].loc.start.column,
token: tokenName,
msg: 'global variable: "' + tokenName + '" should not be used in this file'
});
}
});
return messages;
}
/**
* 校验语法
......@@ -30,9 +97,10 @@ const handleProperty = function (property, propertyName, messages) {
* @return {Object} 语法检查结果
*/
const checkSyntax = function (part) {
const messages = [];
let messages = [];
const opts = config.script;
let ast;
let tokenList = [];
try {
ast = parse(part.content, opts);
}
......@@ -44,33 +112,15 @@ const checkSyntax = function (part) {
});
}
try {
tokenList = getForbiddenGlobalTokens(part.platformType || 'all');
traverse(ast, {
enter(path) {
if (path.isClassProperty() && path.node) {
switch (path.node.key.name) {
case 'watch':
case 'computed':
case 'methods':
let properties = path.get('value').get('properties');
if (properties.forEach) {
(properties || []).forEach(property => {
handleProperty(property, path.node.key.name, messages);
});
}
break;
case 'beforeCreate':
case 'created':
case 'beforeMount':
case 'mounted':
case 'beforeDestroy':
case 'destroyed':
handleProperty(path, path.node.key.name, messages);
break;
default:
break;
}
}
// check arrow function: we do not allow arrow functions in life cycle hooks.
ClassProperty(path) {
messages = messages.concat(checkArrowFun(path));
},
// check global variables: we shall never use any platform specified global variables such as wx, global, window etc.
Program(path) {
messages = messages.concat(checkGlobal(path, tokenList));
}
});
}
......
......@@ -5,7 +5,7 @@
"main": "index.js",
"scripts": {
"test": "nyc ./node_modules/mocha/bin/mocha ./test/**/*.test.js --timeout=3000",
"non-report-test": "./node_modules/mocha/bin/mocha ./test/template/linter/**/*.test.js",
"non-report-test": "./node_modules/mocha/bin/mocha ./test/cml.test.js",
"break-non-report": "./node_modules/mocha/bin/mocha --inspect-brk ./test/template/linter/**/*.test.js"
},
"bin": {
......
<script cml-type="interface">
interface IntgInterface {
platform: String;
getMsg(msg: String): String;
}
</script>
<script cml-type="web">
class Method implements IntgInterface {
platform = 'web';
getMsg(msg) {
return msg;
}
}
export default new Method();
</script>
<script cml-type="weex">
class Method implements IntgInterface {
getMsg(msg) {
return msg;
}
}
export default new Method();
</script>
<script cml-type="wx">
class Method implements IntgInterface {
platform = 'wx';
getMsg(msg) {
return msg;
}
}
export default new Method();
</script>
<script cml-type="interface">
/*
定义一个inteface用于描述组件的属性和事件
1、 如果区分组件属性和事件?
通过类型来区分,事件为函数类型,属性为非函数类型
2、 如何定义组件属性
给interface添加同名的属性即可,指定类型
3、 如何定义组件事件
以事件名称为key值给interface定义属性,该属性是一个函数类型,返回值为void,
定义函数的第一个参数为自定义事件传递的detail对象类型
*/
//定义事件detail对象的参数
type changeStatusDetail = {
index: Number,
status: Boolean
}
type indexDetail = {
index: Number
}
interface CTodoitemInterface {
index: Number,
completed: Boolean,
text: String,
delete(eventDetail: indexDetail): void;
top(eventDetail: indexDetail): void;
}
</script>
<template>
<view class="border">
</view>
</template>
<script>
const eventName = 'refOne';
class CTodoitem implements CTodoitemInterface {
props = {
index: {
type: Number
},
completed: {
type: Boolean
},
text: {
type: String
}
}
data = {
}
computed = {
}
watch = {
}
methods = {
}
beforeCreate() {
this.$cmlEmit('delete');
}
created() {
this.$cmlEmit('create');
}
beforeMount() {
this.$cmlEmit(eventName);
}
mounted() {
}
beforeDestroy() {
}
destroyed() {
}
}
export default new CTodoitem();
</script>
<style scoped>
.border {
border-bottom: 1px solid #ccc;
height: 100px;
}
</style>
<script cml-type="json">
{
"base": {
"usingComponents": {
}
}
}
}
</script>
<script cml-type="interface">
/*
定义一个inteface用于描述组件的属性和事件
1、 如果区分组件属性和事件?
通过类型来区分,事件为函数类型,属性为非函数类型
2、 如何定义组件属性
给interface添加同名的属性即可,指定类型
3、 如何定义组件事件
以事件名称为key值给interface定义属性,该属性是一个函数类型,返回值为void,
定义函数的第一个参数为自定义事件传递的detail对象类型
*/
//定义事件detail对象的参数
type changeStatusDetail = {
index: Number,
status: Boolean
}
type indexDetail = {
index: Number
}
interface CTodoitemInterface {
index: Number,
completed: Boolean,
text: String,
delete(eventDetail: indexDetail): void;
top(eventDetail: indexDetail): void;
}
</script>
<template>
<view class="border">
</view>
</template>
<script>
class CTodoitem implements CTodoitemInterface {
props = {
index: {
type: Number
},
completed: {
type: Boolean
}
}
data = {
}
computed = {
}
watch = {
}
methods = {
}
beforeCreate() {
this.$cmlEmit('delete');
}
created() {
this.$cmlEmit('top');
}
beforeMount() {
}
mounted() {
}
beforeDestroy() {
}
destroyed() {
}
}
export default new CTodoitem();
</script>
<style scoped>
.border {
border-bottom: 1px solid #ccc;
height: 100px;
}
</style>
<script cml-type="json">
{
"base": {
"usingComponents": {
}
}
}
}
</script>
......@@ -19,7 +19,6 @@ class ApplyRecord {
pageData:{
type:Object,
default(){
window.location.href = 'xxx';
return {}
}
}
......
......@@ -7,12 +7,10 @@ const utils = require('../utils');
const fileSpec = require('../file-spec');
const chai = require('chai');
const assert = chai.assert;
const should = chai.should;
const expect = chai.expect;
const path = require('path');
describe('cml', function() {
......@@ -128,9 +126,9 @@ describe('cml', function() {
const result = await styleLinter(parts.style);
expect(result.messages).to.deep.equal([
{
"column": 3,
"line": 7,
"msg": "expected \"indent\", got \";\""
'column': 3,
'line': 7,
'msg': 'expected "indent", got ";"'
}
]);
});
......@@ -186,14 +184,14 @@ describe('cml', function() {
expect(result.messages).to.deep.equal(
[
{
"column": 5,
"line": 19,
"msg": "computed property \"hasApplyList\" cannot be used as an arrow function"
'column': 5,
'line': 19,
'msg': 'computed property "hasApplyList" cannot be used as an arrow function'
},
{
"column": 3,
"line": 30,
"msg": "lifecycle hook \"mounted\" cannot be used as an arrow function"
'column': 3,
'line': 30,
'msg': 'lifecycle hook "mounted" cannot be used as an arrow function'
}
]
);
......@@ -335,6 +333,10 @@ describe('cml', function() {
});
});
describe('check-script', function () {
before(function() {
const projectRoot = path.resolve(__dirname, './checker/cml/script');
config.init(projectRoot);
});
it('standard', async function () {
const cmlPath = path.resolve(__dirname, './checker/cml/script/standard/standard.wx.cml');
const parts = utils.getCmlParts(cmlPath);
......@@ -343,13 +345,6 @@ describe('cml', function() {
expect(result.script.messages).to.deep.equal([]);
});
it('no-interface', async function () {
const cmlPath = path.resolve(__dirname, './checker/cml/script/nointerface/nonstandard.wx.cml');
const parts = utils.getCmlParts(cmlPath);
const result = await fileSpec.lintCmlFile(parts);
checkers.script(result);
assert.equal(result['interface'].messages.length, 1);
});
it('global-variable', async function () {
const cmlPath = path.resolve(__dirname, './checker/cml/script/global-variable/standard.wx.cml');
const parts = utils.getCmlParts(cmlPath);
......@@ -371,5 +366,51 @@ describe('cml', function() {
checkers.script(result);
assert.equal(result.script.messages.length, 0);
});
it('property-not-defined', async function() {
const cmlPath = path.resolve(__dirname, './checker/cml/script/properties-methods/property-not-defined.wx.cml');
const parts = utils.getCmlParts(cmlPath);
let result = await fileSpec.lintCmlFile(parts);
checkers.script(result);
assert.equal(result['interface'].messages.length, 1);
expect(result['interface'].messages[0]).to.include({
line: 24,
column: 2,
token: 'text'
});
});
it('event-not-defined', async function() {
const cmlPath = path.resolve(__dirname, './checker/cml/script/properties-methods/event-not-defined.web.cml');
const parts = utils.getCmlParts(cmlPath);
let result = await fileSpec.lintCmlFile(parts);
checkers.script(result);
assert.equal(result['interface'].messages.length, 1);
expect(result['interface'].messages[0]).to.include({
line: 26,
column: 2,
token: 'top'
});
assert.equal(result.script.messages.length, 2);
expect(result.script.messages).to.deep.equal([{
line: 33,
column: 18,
token: 'create',
msg: 'event "create" is not defined in interface file "properties-methods/event-not-defined.interface"'
}, {
line: 37,
column: 18,
token: 'refOne',
msg: 'event "refOne" is not defined in interface file "properties-methods/event-not-defined.interface"'
}]);
});
it('interface-prop-not-defined', async function() {
const cmlPath = path.resolve(__dirname, './checker/cml/script/interfaces/prop-not-defined.interface');
const result = await fileSpec(cmlPath, 'interface');
expect(result['interface'].messages).to.have.lengthOf(1);
expect(result['interface'].messages[0]).to.include({
line: 3,
column: 2,
token: 'platform'
});
});
});
});
......@@ -71,6 +71,26 @@ describe('template lib template', function() {
method: false,
variable: true,
pos: [8, 46]
}, {
name: 'logicalLeft',
method: false,
variable: true,
pos: [9, 17]
}, {
name: 'logicalRight',
method: false,
variable: true,
pos: [9, 32]
}, {
name: 'eleOne',
method: false,
variable: true,
pos: [10, 18]
}, {
name: 'eleTwo',
method: false,
variable: true,
pos: [10, 26]
}], 'variables mismatch');
expect(results.customizedComponents[0]['show-scroller']).to.have.deep.property('props', [{
event: false,
......
......@@ -6,6 +6,8 @@
<view data-attr="{{childView ? child : 'strChild'}}"></view>
</view>
<show-scroller c-if="{{true}}" scroll-x="{{disX}}" c-bind:tap="onTap" c-bind:onscroll="onScroll"></show-scroller>
<view c-if="{{logicalLeft || logicalRight}}"></view>
<view c-if="{{[eleOne, eleTwo]}}"></view>
</template>
<script>
......
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const groupBy = require('lodash.groupby');
const filter = require('lodash.filter');
......@@ -13,6 +14,10 @@ let isCmlComponent = (templatePath, usingPath) => {
return !!interfaceInfo.filePath || (componentInfo && componentInfo.isCml);
}
let toSrcPath = (filePath = '') => {
return (filePath && path.isAbsolute(filePath)) ? path.relative(config.getCurrentWorkspace(), filePath) : filePath;
}
/**
* 转换成驼峰写法
*
......@@ -96,7 +101,6 @@ 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');
......@@ -187,5 +191,6 @@ module.exports = {
getInterfaceParts,
outputWarnings,
toDash,
isCmlComponent
isCmlComponent,
toSrcPath
};
......@@ -150,11 +150,25 @@ module.exports.containerPathVisitor = function(path) {
if (path.node.property.name === '$cmlEmit') {
let parentNode = path.findParent(path => path.isCallExpression());
if (parentNode && parentNode.get('arguments')) {
parentNode.get('arguments')[0].isStringLiteral() && results.events.push({
name: parentNode.get('arguments')[0].node.value,
paramsNum: -1,
params: []
});
let nameArg = parentNode.get('arguments')[0];
if (nameArg.isStringLiteral()) {
results.events.push({
name: parentNode.get('arguments')[0].node.value,
paramsNum: -1,
params: []
});
} else if (nameArg.isIdentifier()) {
let argBinding = nameArg.scope.getBinding(nameArg.node.name);
let possibleInit = argBinding ? argBinding.path.node.init : null;
// For now, we only check just one jump along its scope chain.
if (possibleInit && possibleInit.type === 'StringLiteral') {
results.events.push({
name: possibleInit.value,
paramsNum: -1,
params: []
});
}
}
}
}
}
......
......@@ -6,6 +6,8 @@
import { mapGetters } from 'chameleon-store';
import { gettersShow, getterHide } from 'getter-types';
const tapEvent = 'topevent';
export default {
props: {
propOne:{
......@@ -41,6 +43,7 @@
},
methods: {
onTap() {
this.$cmlEmit(tapEvent);
return true;
},
onClick: function(e) {
......
......@@ -31,6 +31,8 @@ describe('parser check', function() {
}], 'failed at checking props');
expect(results).to.have.deep.property('events', [{
name: 'eventOne', paramsNum: -1, params: []
}, {
name: 'topevent', paramsNum: -1, params: []
}]);
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册