wcc.js
Version:
Compiler for wxml and wxss files.
354 lines (336 loc) • 13.1 kB
JavaScript
const babelParser = require('@babel/parser');
const babelTraverse = require('@babel/traverse').default;
const babelGenerate = require('@babel/generator').default;
const babelTypes = require('@babel/types');
const util = require('./util');
const acorn = require("acorn");
const error = require('./error.js');
const WhiteVariableIdentifier = [
'delete',
'void',
'typeof',
'null',
'undefined',
'NaN',
'Infinity',
'var',
'if',
'else',
'true',
'false',
'require',
'this',
'function',
'arguments',
'return',
'for',
'while',
'do',
'break',
'continue',
'switch',
'case',
'default'
];
exports.parse = function (source, filePath, startLine, wcc) {
startLine = startLine || 1;
if (!source) {
return '';
}
let ast;
/**
* https://developers.weixin.qq.com/miniprogram/dev/reference/wxs/03annotation.html
* 第三种注释特殊处理
* TODO 改为不用正则表达式
*/
source = source.replace(/\*\s*\//gm, '*/');
source = source.replace(/\/\s*\*/gm, '/*');
let leftNum = 0;
let i = 0;
while (i < source.length) {
if (((i - 1 >= 0 && source[i - 1] !== '/') || i - 1 < 0) && source[i] === '/' && ((i + 1 < source.length) && source[i + 1] === '*')) {
leftNum++;
i++;
} else if (source[i] === '*' && ((i + 1 < source.length) && source[i + 1] === '/') && (i + 2 >= source.length || ((i + 2 < source.length) && source[i + 2] !== '/'))) {
leftNum = 0;
i++;
}
i++;
}
if (leftNum) {
source += '*/';
}
try {
//小程序的wxs是es5标准,babel没有es5的parse,先用acorn进行parse
ast = acorn.parse(source, {
sourceType: 'script',
sourceFile: filePath,
allowReserved: 'never', //保留字不能作为变量声明和属性
ecmaVersion: 5
});
} catch (err) {
let msg = err.message;
let idx1 = msg.lastIndexOf('(');
let msg1 = msg.substring(0, idx1);
let message = `${filePath}:${err.loc.line + startLine - 1}:${err.loc.column}: ${err.name}: ${msg1}\n`;
return (new error.WccError(error.CODE.ES5_PARSE_ERROR, message));
}
try {
//由于babel的ast跟ESTree Spec有差异,如果es5检查通过,再用bable parse解析ast
ast = babelParser.parse(source, {
sourceFilename: filePath,
// startLine: startLine,
ecmaVersion: 5
});
} catch (err) {
let msg = err.message;
let idx1 = msg.lastIndexOf('(');
let msg1 = msg.substring(0, idx1);
let message = `${filePath}:${err.loc.line + startLine - 1}:${err.loc.column}: ${err.name}: ${msg1}\n`;
return (new error.WccError(error.CODE.BABEL_PARSE_ERROR, message));
}
let wccError;
babelTraverse(ast, {
enter(path) {
if (path.node._wcc_handled) {
path.skip();
return;
}
/**
* 对象声明的属性不能是数字
* a = {0: 0} 是非法的!
*/
if (path.isObjectProperty() && babelTypes.isNumericLiteral(path.node.key)) {
let message = `${filePath}:${path.node.key.loc.start.line + startLine - 1}:${path.node.key.loc.start.column}: Unexpected token \`:\`\n`;
wccError = (new error.WccError(-1, message));
path.stop();
return;
}
/**
* 判断下是否有保留标识符
* https://developers.weixin.qq.com/miniprogram/dev/reference/wxs/02variate.html
*/
if (path.isVariableDeclaration()) {
//变量声明 var NaN = 1;
for (let i = 0; i < path.node.declarations.length; ++i) {
let idNode = path.node.declarations[i].id;
if (babelTypes.isIdentifier(idNode) && WhiteVariableIdentifier.indexOf(idNode.name) > -1) {
let message = `${filePath}:${idNode.loc.start.line + startLine - 1}:${idNode.loc.start.column}: SyntaxError: invalid reserved identifier \`${idNode.name}\`\n`;
wccError = new error.WccError(error.CODE.INVALID_RESERVED_IDENTIFIER, message);
path.stop();
return;
}
}
}
if (path.isIdentifier() && WhiteVariableIdentifier.indexOf(path.node.name) > -1 && babelTypes.isObjectProperty(path.parent)) {
//对象属性 var a = {NaN: 1}
let message = `${filePath}:${path.node.loc.start.line + startLine - 1}:${path.node.loc.start.column}: SyntaxError: invalid ObjectProperty \`${path.node.name}\`\n`;
wccError = new error.WccError(error.CODE.INVALID_OBJECTPROPERTY, message);
path.stop();
return;
}
if (path.isIdentifier() && WhiteVariableIdentifier.indexOf(path.node.name) > -1 && babelTypes.isMemberExpression(path.parent) && path.parent.property === path.node && !path.parent.computed) {
//对象属性访问 a.NaN
let message = `${filePath}:${path.node.loc.start.line + startLine - 1}:${path.node.loc.start.column}: SyntaxError: invalid MemberExpression \`${path.node.name}\`\n`;
wccError = new error.WccError(error.CODE.INVALID_MEMBEREXPRESSION, message);
path.stop();
return;
}
/**
* 判断是否有正则表达式
*/
if (path.isRegExpLiteral()) {
let message = `${filePath}:${path.node.loc.start.line + startLine - 1}:${path.node.loc.start.column}: SyntaxError: Unexpected token \`/\`\n`;
wccError = new error.WccError(error.CODE.UNEXPECTED_TOKEN, message);
path.stop();
return;
}
/**
* 变量和属性添加nv_前缀
*/
if (path.isIdentifier()) {
if (path.node.name !== 'arguments' && path.node.name !== 'Infinity' && path.node.name !== 'undefined' && path.node.name !== 'NaN' && path.node.name !== 'Math' && path.node.name !== 'Number') {
path.node.name = `nv_${path.node.name}`;
}
}
if (path.isObjectProperty() && babelTypes.isStringLiteral(path.node.key)) {
path.node.key.value = `nv_${path.node.key.value}`;
}
if (path.isMemberExpression() && babelTypes.isStringLiteral(path.node.property)) {
path.node.property.value = `nv_${path.node.property.value}`;
}
/**
* reqiure修改
* 1. 路径修改
* 2. require(a) => require(a)()
*/
if (path.isCallExpression()) {
let ce = path.get('callee');
let args = path.get('arguments');
if (ce.isIdentifier({
name: 'require'
}) && args.length === 1 && args[0].isStringLiteral() && /\.wxs$/.test(args[0].node.value)) {
ce.node.name = 'nv_' + ce.node.name;
args[0].node.value = 'p_' + util.getNormalizePath(filePath, args[0].node.value);
let newNode = babelTypes.callExpression(path.node.callee, path.node.arguments);
let newNode1 = babelTypes.callExpression(newNode, []);
path.replaceWith(newNode1);
path.skip(); // 子节点无限访问 防止无限递归traverse
}
}
/**
* this
* 合法的两种使用场景
* 1. 对象成员访问 var a = this.x;
* 2. 函数调用参数 console.log(a, b, c, this)
* 合法的场景,需要把this替换为(this.constructor === Window ? {} : this)
*/
if (path.isThisExpression()) {
if (!(babelTypes.isMemberExpression(path.parent) || babelTypes.isCallExpression(path.parent))) {
let message = `${filePath}:${path.node.loc.start.line + startLine - 1}:${path.node.loc.start.column}: SyntaxError: invalid usage of identifier \`this\`\n`;
wccError = new error.WccError(error.CODE.INVALID_THIS, message);
path.stop();
return;
} else if (!path.node._wcc_handled) {
let thisNode = babelTypes.expressionStatement(
babelTypes.conditionalExpression(
babelTypes.binaryExpression(
"===",
babelTypes.memberExpression(
path.node,
babelTypes.identifier('constructor'),
false
),
babelTypes.identifier('Window')
),
babelTypes.objectExpression([]),
path.node
)
)
path.replaceWith(thisNode);
path.skip(); // 子节点无限访问 防止无限递归traverse
}
}
/**
* arguments
* 合法的两种使用场景:
* 1. 函数内部,对象成员访问,读取arguments属性
* 2. 函数内部,函数调用参数
* 如果函数有引用arguments,需要在函数开头增加 'arguments.nv_length = arguments.length;'
*/
if (path.isIdentifier({
name: 'arguments'
})) {
let functionPath = path.findParent((path) => path.isFunctionDeclaration()) || path.findParent((path) => path.isFunctionExpression());
if (!(functionPath)) {
let message = `${filePath}:${path.node.loc.start.line + startLine - 1}:${path.node.loc.start.column}: SyntaxError: invalid usage of identifier \`arguments\`\n`;
wccError = new error.WccError(error.CODE.INVALID_ARGUMENTS, message);
path.stop();
return;
} else if (!(babelTypes.isMemberExpression(path.parent) || babelTypes.isCallExpression(path.parent))) {
let message = `${filePath}:${path.node.loc.start.line + startLine - 1}:${path.node.loc.start.column}: SyntaxError: invalid usage of identifier \`arguments\`\n`;
wccError = new error.WccError(error.CODE.INVALID_ARGUMENTS, message);
path.stop();
return;
} else {
functionPath.node._wcc_arguments_Referenced = true;
}
}
//字符串处理
if (path.isStringLiteral()) {
//特殊字符转换为unicode
let tmp = path.node.extra.raw.substring(1, path.node.extra.raw.length - 1);
tmp = util.escapeTxt(tmp);
tmp = path.node.extra.raw[0] + tmp + path.node.extra.raw[path.node.extra.raw.length - 1];
path.node.extra.raw = tmp;
}
},
exit: function (path) {
if ((path.isFunctionDeclaration() || path.isFunctionExpression()) && path.node._wcc_arguments_Referenced) {
//如果函数有引用arguments,需要在函数开头增加 'arguments.nv_length = arguments.length;'
let stateMent = babelTypes.expressionStatement(
babelTypes.assignmentExpression(
'=',
babelTypes.memberExpression(
babelTypes.identifier('arguments'),
babelTypes.identifier('nv_length'),
false
),
babelTypes.memberExpression(
babelTypes.identifier('arguments'),
babelTypes.identifier('length'),
false
)
)
);
stateMent._wcc_handled = true; // 打个标记
path.get('body').unshiftContainer('body', stateMent);
}
/**
* e['sdf' + 2] =>
* e[((nt_1 = ('sdf' + 2), null == nt_1 ? undefined : 'number' === typeof nt_1 ? nt_1 : "nv_" + nt_1))]
*/
if (path.isMemberExpression() && path.node.computed) {
let oldProperty = path.node.property;
if (!(babelTypes.isStringLiteral(oldProperty) || babelTypes.isNumericLiteral(oldProperty))) {
let tmpVar = 'nt_1';
let newProperty = babelTypes.sequenceExpression([
babelTypes.assignmentExpression(
'=',
babelTypes.identifier(tmpVar),
oldProperty
),
babelTypes.conditionalExpression(
babelTypes.binaryExpression(
'==',
babelTypes.nullLiteral(),
babelTypes.identifier(tmpVar)
),
babelTypes.identifier('undefined'),
babelTypes.conditionalExpression(
babelTypes.binaryExpression(
'===',
babelTypes.stringLiteral('number'),
babelTypes.unaryExpression(
'typeof',
babelTypes.identifier(tmpVar),
true
)
),
babelTypes.identifier(tmpVar),
babelTypes.binaryExpression(
'+',
babelTypes.stringLiteral('nv_'),
babelTypes.identifier(tmpVar)
)
)
)
]);
let oldMemberExpression = path.node;
let newMemberExpression = babelTypes.memberExpression(
oldMemberExpression.object,
newProperty,
oldMemberExpression.computed
);
newMemberExpression._wcc_handled = true;
path.replaceWith(newMemberExpression);
}
}
}
});
if (wccError) {
return wccError;
}
let res;
try {
res = babelGenerate(ast, {
comments: false
}).code || '';
} catch (e) {
let message = `${path}: generate error:${e.message}\n`;
return (new error.WccError(error.CODE.BABEL_GENERATE, message));
}
// res = res.replace(/\\\\x/gm, '\\x');
// res = util.escapeTxt(res);
return res;
};