diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f47ae3392ee32f701f3cfc41bd9a94d1767290f..00d439678f2daada46f00deb386ef2ad853d0a65 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,7 @@
- 支持web weex可配置是否包裹组件
## [0.3.3-alpha.2]
-### Bug Fixes
+### Bug Fixes
* 修复 weex dev模式liveload失效
老项目如果修复,还需要升级项目中两个npm包如下:
diff --git a/packages/chameleon-linter/checkers/script.js b/packages/chameleon-linter/checkers/script.js
index 617dfe866c55e49cb9a38a993fd64173b1df0bb9..ad9b8b5ad8e9961043e67ecc0dd39efcbe443ef9 100644
--- a/packages/chameleon-linter/checkers/script.js
+++ b/packages/chameleon-linter/checkers/script.js
@@ -1,175 +1,5 @@
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);
}
- }
+ });
};
diff --git a/packages/chameleon-linter/checkers/template/index.js b/packages/chameleon-linter/checkers/template/index.js
index eaa1f11699c889c506216d2431ef97ad55bf92c5..4e2df315d0af4a15babdc115fb8d7862d770b2fa 100644
--- a/packages/chameleon-linter/checkers/template/index.js
+++ b/packages/chameleon-linter/checkers/template/index.js
@@ -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],
diff --git a/packages/chameleon-linter/checkers/template/lib/template-ast-parser/rules/template/mustache-node.js b/packages/chameleon-linter/checkers/template/lib/template-ast-parser/rules/template/mustache-node.js
index 5545300ecb0a5c2fea574a4ed32bf22668e5053d..12b1edf54f09f26882f5e35e9f42dfd6e2df5eaf 100644
--- a/packages/chameleon-linter/checkers/template/lib/template-ast-parser/rules/template/mustache-node.js
+++ b/packages/chameleon-linter/checkers/template/lib/template-ast-parser/rules/template/mustache-node.js
@@ -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',
diff --git a/packages/chameleon-linter/checkers/template/lib/template-ast-parser/tools/parse-single-expression.js b/packages/chameleon-linter/checkers/template/lib/template-ast-parser/tools/parse-single-expression.js
index 8cd762b9f6f2ae4cfb88f3ff5f0a57061657a0dd..509ec0c59abf596319455b1310c7eaaa47cdbfa5 100644
--- a/packages/chameleon-linter/checkers/template/lib/template-ast-parser/tools/parse-single-expression.js
+++ b/packages/chameleon-linter/checkers/template/lib/template-ast-parser/tools/parse-single-expression.js
@@ -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()) {
diff --git a/packages/chameleon-linter/config/globalVars.js b/packages/chameleon-linter/config/globalVars.js
new file mode 100644
index 0000000000000000000000000000000000000000..d5160974e9c58e06745a2e35c4a9b598b88cdc4a
--- /dev/null
+++ b/packages/chameleon-linter/config/globalVars.js
@@ -0,0 +1,61 @@
+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;
diff --git a/packages/chameleon-linter/config/white-list/cml-white-list.js b/packages/chameleon-linter/config/white-list/cml-white-list.js
index 51916e8e900f3d8793dc46b6d22d87dce290050e..39b886031bc3363e54403eeeb7619029d29776d4 100644
--- a/packages/chameleon-linter/config/white-list/cml-white-list.js
+++ b/packages/chameleon-linter/config/white-list/cml-white-list.js
@@ -22,6 +22,7 @@ module.exports = {
'checkbox',
'page',
'router-view',
+ 'cover-view',
'slot',
'aside',
'col',
diff --git a/packages/chameleon-linter/file-spec.js b/packages/chameleon-linter/file-spec.js
index 2dfb1c4124e46301edc93da4426f79f052851465..91f7d9bfadbbcaeff06d9a1d6a797bf9d35b8341 100644
--- a/packages/chameleon-linter/file-spec.js
+++ b/packages/chameleon-linter/file-spec.js
@@ -200,3 +200,4 @@ const checkFileSpecification = async (filepath, filetype) => {
module.exports = checkFileSpecification;
module.exports.lintCmlFile = lintCmlFile;
+module.exports.checkFile = checkFile;
diff --git a/packages/chameleon-linter/linters/script.js b/packages/chameleon-linter/linters/script.js
index f9290afc4e20723a2d1da63965499505274782be..12a78cf047dd50f19379eb60a4665f18ad25f5a1 100644
--- a/packages/chameleon-linter/linters/script.js
+++ b/packages/chameleon-linter/linters/script.js
@@ -1,6 +1,8 @@
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));
}
});
}
diff --git a/packages/chameleon-linter/package.json b/packages/chameleon-linter/package.json
index b6e27eab9072917ffff904af02c83cbe848ba4da..7019be326ab37335d182a423524e9efee91bd794 100644
--- a/packages/chameleon-linter/package.json
+++ b/packages/chameleon-linter/package.json
@@ -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": {
diff --git a/packages/chameleon-linter/test/checker/cml/script/interfaces/prop-not-defined.interface b/packages/chameleon-linter/test/checker/cml/script/interfaces/prop-not-defined.interface
new file mode 100644
index 0000000000000000000000000000000000000000..78e60f82b97d91b1dafea075b363b1d2ad287902
--- /dev/null
+++ b/packages/chameleon-linter/test/checker/cml/script/interfaces/prop-not-defined.interface
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/chameleon-linter/test/checker/cml/script/properties-methods/event-not-defined.interface b/packages/chameleon-linter/test/checker/cml/script/properties-methods/event-not-defined.interface
new file mode 100644
index 0000000000000000000000000000000000000000..fb37b8b43aeb158b9b44e8e7977b6c29f60cf94a
--- /dev/null
+++ b/packages/chameleon-linter/test/checker/cml/script/properties-methods/event-not-defined.interface
@@ -0,0 +1,28 @@
+
diff --git a/packages/chameleon-linter/test/checker/cml/script/properties-methods/event-not-defined.web.cml b/packages/chameleon-linter/test/checker/cml/script/properties-methods/event-not-defined.web.cml
new file mode 100644
index 0000000000000000000000000000000000000000..bf5349fb6719750238fabde64dad98d37e44773a
--- /dev/null
+++ b/packages/chameleon-linter/test/checker/cml/script/properties-methods/event-not-defined.web.cml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
diff --git a/packages/chameleon-linter/test/checker/cml/script/properties-methods/property-not-defined.interface b/packages/chameleon-linter/test/checker/cml/script/properties-methods/property-not-defined.interface
new file mode 100644
index 0000000000000000000000000000000000000000..fb37b8b43aeb158b9b44e8e7977b6c29f60cf94a
--- /dev/null
+++ b/packages/chameleon-linter/test/checker/cml/script/properties-methods/property-not-defined.interface
@@ -0,0 +1,28 @@
+
diff --git a/packages/chameleon-linter/test/checker/cml/script/properties-methods/property-not-defined.wx.cml b/packages/chameleon-linter/test/checker/cml/script/properties-methods/property-not-defined.wx.cml
new file mode 100644
index 0000000000000000000000000000000000000000..e614002ce7674b9e1f3c592b4d2f7b219bf8f206
--- /dev/null
+++ b/packages/chameleon-linter/test/checker/cml/script/properties-methods/property-not-defined.wx.cml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
diff --git a/packages/chameleon-linter/test/checker/cml/script/standard.cml b/packages/chameleon-linter/test/checker/cml/script/standard.cml
index cffbe76682e878392cae59c3dc7a95212e5f2764..717e29919f8d10b71029b364ff6c0a4e3bfced00 100644
--- a/packages/chameleon-linter/test/checker/cml/script/standard.cml
+++ b/packages/chameleon-linter/test/checker/cml/script/standard.cml
@@ -19,7 +19,6 @@ class ApplyRecord {
pageData:{
type:Object,
default(){
- window.location.href = 'xxx';
return {}
}
}
diff --git a/packages/chameleon-linter/test/cml.test.js b/packages/chameleon-linter/test/cml.test.js
index f0960ec08ce70ab324f00bc1208914d16867a925..707e814e06895ee570d07d0e0611d916da808a1c 100644
--- a/packages/chameleon-linter/test/cml.test.js
+++ b/packages/chameleon-linter/test/cml.test.js
@@ -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'
+ });
+ });
});
});
diff --git a/packages/chameleon-linter/test/template/checker/template-lib-template.test.js b/packages/chameleon-linter/test/template/checker/template-lib-template.test.js
index bba78ff28ce4fd6aa567307488812bde1ffbd231..0b08df231412999dc141743f22ed0f5dc5b6d08b 100644
--- a/packages/chameleon-linter/test/template/checker/template-lib-template.test.js
+++ b/packages/chameleon-linter/test/template/checker/template-lib-template.test.js
@@ -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,
diff --git a/packages/chameleon-linter/test/template/docs/check/success/index-lib-template-cml.cml b/packages/chameleon-linter/test/template/docs/check/success/index-lib-template-cml.cml
index 53e1e5ede96100972816cd53335ee3704da4230d..2fd1d4209ebbb244fb902d1559a76c7c907dbf0c 100644
--- a/packages/chameleon-linter/test/template/docs/check/success/index-lib-template-cml.cml
+++ b/packages/chameleon-linter/test/template/docs/check/success/index-lib-template-cml.cml
@@ -6,6 +6,8 @@
+
+