.6-浅析webpack源码之validateSchema模块

Posted QH-Jimmy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.6-浅析webpack源码之validateSchema模块相关的知识,希望对你有一定的参考价值。

validateSchema模块

 

  首先来看错误检测:

    const webpackOptionsValidationErrors = validateSchema(webpackOptionsSchema, options);
    if(webpackOptionsValidationErrors.length) {
        throw new WebpackOptionsValidationError(webpackOptionsValidationErrors);
    }

  可以注意到,这里传了两个参数,其实第一个参数来源于一个JSON文件:

    const webpackOptionsSchema = require("../schemas/webpackOptionsSchema.json");

  这个JSON文件非常大,可以观察一下部分内容:

{
    "plugins": {
    "description": "Add additional plugins to the compiler.",
    "type": "array"
    },
    "resolve": {
    "description": "Options for the resolver",
    "anyOf": [
        {
        "$ref": "#/definitions/resolve"
        }
    ]
    },
    "required": [
      "entry"
    ],
    "type": "object"
}

  从描述可以猜测,这里的key对应options中的key,value就是检测方式。

  比如说entry放到required代表是必须的,plugins的type为array代表这个键必须是一个数组,而$ref代表一个路径映射,即#/definnitions/resolve。

  在JSON文件的开头有一个definitions,其中resolve对应的内容如下:

{
    "definitions": {
        "resolve": {
            "additionalProperties": false,
            "properties": {
                "alias": {
                    // ...
                }
                // ...
            },
            "type": "object"
        }
    }
}

  简单明了,就不多解释了。

 

  下面进入validateSchema模块,流程如图:

  

  内部代码简化如下:

const Ajv = require("ajv");
const ajv = new Ajv({
    errorDataPath: "configuration",
    allErrors: true,
    verbose: true
});
require("ajv-keywords")(ajv, ["instanceof"]);
require("../schemas/ajv.absolutePath")(ajv);

function validateSchema(schema, options) {
    // 仍然是多配置与单配置
    if(Array.isArray(options)) { /*...*/ } 
    else {
        return validateObject(schema, options);
    }
}

function validateObject(schema, options) {
    // 转换JSON文件
    const validate = ajv.compile(schema);
    // 检测配置对象
    const valid = validate(options);
    // 返回错误对象
    return valid ? [] : filterErrors(validate.errors);
}

function filterErrors(errors) { /*...*/ }
module.exports = validateSchema;

  这里引入的ajv模块是一个工具,就像之前的JSON5一样,作用是将一个JSON配置文件转换成一个对象,用于检测对象的合法性。

  在github上,该工具的Getting started如图所示:

  

  简直跟源码中的使用过程一模一样,所以有兴趣的可以自己去看看教程学一下。

  

  由于JSON十分巨大,所以编译后的对象也十分巨大,这里根据vue脚手架中的配置,只看常规的参数是如何进行检测的,比如说devServer、devtool、entry、module、output、plugins、resolve,测试代码如下:

var Ajv = require(\'ajv\');
const ajv = new Ajv({
    errorDataPath: "configuration",
    allErrors: true,
    verbose: true
});
// 简化后的JSON文件
const json = require(\'./tmp.json\');
const validate = ajv.compile(json);

  打包后尝试获取生成的validate函数,整理后源码如下:

(function(self, RULES, formats, root, refVal, defaults, customRules, co, equal, ucs2length, ValidationError) {
    var refVal0 = refVal[0];
    // ...
    var refVal13 = refVal[13];
    var validate = (function(data, dataPath, parentData, parentDataProperty, rootData) {
        \'use strict\';
        // 负责收集错误信息
        var vErrors = null;
        // 负责对错误进行计数
        var errors = 0;
        if (rootData === undefined) rootData = data;
        // 这是根级对象
        if ((data && typeof data === "object" && !Array.isArray(data))) {
            var errs__0 = errors;
            var valid1 = true;
            for (var key0 in data) { /*...*/ }
            // 在这里进行检测
            // 每出现一个错误errors+1并记录vErrors中
            var data1 = data.devServer;
            if (data1 !== undefined) { /*...*/ }
            var data1 = data.devtool;
            if (data1 !== undefined) { /*...*/ }
            var data1 = data.entry;
            if (data1 === undefined) { /*...*/ } 
            else { /*...*/ }
            var data1 = data.module;
            if (data1 !== undefined) { /*...*/ }
            var data1 = data.output;
            if (data1 !== undefined) { /*...*/ }
            var data1 = data.plugins;
            if (data1 !== undefined) { /*...*/ }
            var data1 = data.resolve;
            if (data1 !== undefined) { /*...*/ }
        } else { /*...*/ }
        validate.errors = vErrors;
        // 判断是否产生错误
        return errors === 0;
    });
    return validate;
})

  调用validate(options),options在函数中就相当于那个data,validate会依次从data中取出需要校验的key,按照JSON文件中的规则进行判断。

  这里有一个比较麻烦的点,就是顶部的refVal,由于这是一个内部的IIFE,所以从这里看不出refVal数组如何定义的。凭借我的聪明才智,还是从源码中获取到了对应的定义:

  

  打开后,其实是一堆validate函数,所以就不展开看了。

  其实validate并不只有这么多,其中还可以分为特殊校验器和公共校验器,其中公共校验器不会针对特殊的键来进行校验,在这里可以先列出来。(友情警告:千万不要点开!!!)

 

refVal1

        if ((data && typeof data === "object" && !Array.isArray(data))) {
            if (Object.keys(data).length < 1) {
                var err = { keyword: \'minProperties\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf/0/minProperties\', params: { limit: 1 }, message: \'should NOT have less than 1 properties\', schema: 1, parentSchema: validate.schema.oneOf[0], data: data };
                if (vErrors === null) vErrors = [err];
                else vErrors.push(err);
                errors++;
            }
            var errs__1 = errors;
            var valid2 = true;
            for (var key1 in data) {
                var data1 = data[key1];
                var errs_2 = errors;
                var errs__2 = errors;
                var prevValid2 = false;
                var valid2 = false;
                var errs_3 = errors;
                if (typeof data1 === "string") {
                    if (ucs2length(data1) < 1) {
                        var err = { keyword: \'minLength\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/oneOf/0/additionalProperties/oneOf/0/minLength\', params: { limit: 1 }, message: \'should NOT be shorter than 1 characters\', schema: 1, parentSchema: validate.schema.oneOf[0].additionalProperties.oneOf[0], data: data1 };
                        if (vErrors === null) vErrors = [err];
                        else vErrors.push(err);
                        errors++;
                    }
                } else {
                    var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/oneOf/0/additionalProperties/oneOf/0/type\', params: { type: \'string\' }, message: \'should be string\', schema: validate.schema.oneOf[0].additionalProperties.oneOf[0].type, parentSchema: validate.schema.oneOf[0].additionalProperties.oneOf[0], data: data1 };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                }
                var valid3 = errors === errs_3;
                if (valid3) valid2 = prevValid2 = true;
                var errs_3 = errors;
                var errs__3 = errors;
                var valid3 = false;
                var errs_4 = errors;
                var errs_5 = errors;
                if (Array.isArray(data1)) {
                    if (data1.length < 1) {
                        var err = { keyword: \'minItems\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/minItems\', params: { limit: 1 }, message: \'should NOT have less than 1 items\', schema: 1, parentSchema: refVal2, data: data1 };
                        if (vErrors === null) vErrors = [err];
                        else vErrors.push(err);
                        errors++;
                    }
                    var valid5 = true;
                    if (data1.length > 1) {
                        var i = data1.length,
                            j;
                        outer: for (; i--;) { for (j = i; j--;) { if (equal(data1[i], data1[j])) { valid5 = false; break outer; } } }
                    }
                    if (!valid5) {
                        var err = { keyword: \'uniqueItems\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/uniqueItems\', params: { i: i, j: j }, message: \'should NOT have duplicate items (items ## \' + j + \' and \' + i + \' are identical)\', schema: true, parentSchema: refVal2, data: data1 };
                        if (vErrors === null) vErrors = [err];
                        else vErrors.push(err);
                        errors++;
                    }
                    var errs__5 = errors;
                    var valid5;
                    for (var i5 = 0; i5 < data1.length; i5++) {
                        var data2 = data1[i5];
                        var errs_6 = errors;
                        if (typeof data2 === "string") {
                            if (ucs2length(data2) < 1) {
                                var err = { keyword: \'minLength\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\'][\' + i5 + \']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/items/minLength\', params: { limit: 1 }, message: \'should NOT be shorter than 1 characters\', schema: 1, parentSchema: refVal2.items, data: data2 };
                                if (vErrors === null) vErrors = [err];
                                else vErrors.push(err);
                                errors++;
                            }
                        } else {
                            var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\'][\' + i5 + \']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/items/type\', params: { type: \'string\' }, message: \'should be string\', schema: refVal2.items.type, parentSchema: refVal2.items, data: data2 };
                            if (vErrors === null) vErrors = [err];
                            else vErrors.push(err);
                            errors++;
                        }
                        var valid6 = errors === errs_6;
                    }
                } else {
                    var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/type\', params: { type: \'array\' }, message: \'should be array\', schema: refVal2.type, parentSchema: refVal2, data: data1 };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                }
                var valid5 = errors === errs_5;
                var valid4 = errors === errs_4;
                valid3 = valid3 || valid4;
                if (!valid3) {
                    var err = { keyword: \'anyOf\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/oneOf/0/additionalProperties/oneOf/1/anyOf\', params: {}, message: \'should match some schema in anyOf\', schema: validate.schema.oneOf[0].additionalProperties.oneOf[1].anyOf, parentSchema: validate.schema.oneOf[0].additionalProperties.oneOf[1], data: data1 };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                } else {
                    errors = errs__3;
                    if (vErrors !== null) {
                        if (errs__3) vErrors.length = errs__3;
                        else vErrors = null;
                    }
                }
                var valid3 = errors === errs_3;
                if (valid3 && prevValid2) valid2 = false;
                else { if (valid3) valid2 = prevValid2 = true; }
                if (!valid2) {
                    var err = { keyword: \'oneOf\', dataPath: (dataPath || \'\') + \'[\\\'\' + key1 + \'\\\']\', schemaPath: \'#/oneOf/0/additionalProperties/oneOf\', params: {}, message: \'should match exactly one schema in oneOf\', schema: validate.schema.oneOf[0].additionalProperties.oneOf, parentSchema: validate.schema.oneOf[0].additionalProperties, data: data1 };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                } else {
                    errors = errs__2;
                    if (vErrors !== null) {
                        if (errs__2) vErrors.length = errs__2;
                        else vErrors = null;
                    }
                }
                var valid2 = errors === errs_2;
            }
        } else {
            var err = { keyword: \'type\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf/0/type\', params: { type: \'object\' }, message: \'should be object\', schema: validate.schema.oneOf[0].type, parentSchema: validate.schema.oneOf[0], data: data };
            if (vErrors === null) vErrors = [err];
            else vErrors.push(err);
            errors++;
        }
        var valid1 = errors === errs_1;
        if (valid1) valid0 = prevValid0 = true;
        var errs_1 = errors;
        if (typeof data === "string") {
            if (ucs2length(data) < 1) {
                var err = { keyword: \'minLength\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf/1/minLength\', params: { limit: 1 }, message: \'should NOT be shorter than 1 characters\', schema: 1, parentSchema: validate.schema.oneOf[1], data: data };
                if (vErrors === null) vErrors = [err];
                else vErrors.push(err);
                errors++;
            }
        } else {
            var err = { keyword: \'type\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf/1/type\', params: { type: \'string\' }, message: \'should be string\', schema: validate.schema.oneOf[1].type, parentSchema: validate.schema.oneOf[1], data: data };
            if (vErrors === null) vErrors = [err];
            else vErrors.push(err);
            errors++;
        }
        var valid1 = errors === errs_1;
        if (valid1 && prevValid0) valid0 = false;
        else {
            if (valid1) valid0 = prevValid0 = true;
            var errs_1 = errors;
            var errs__1 = errors;
            var valid1 = false;
            var errs_2 = errors;
            var errs_3 = errors;
            if (Array.isArray(data)) {
                if (data.length < 1) {
                    var err = { keyword: \'minItems\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/minItems\', params: { limit: 1 }, message: \'should NOT have less than 1 items\', schema: 1, parentSchema: refVal[2], data: data };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                }
                var valid3 = true;
                if (data.length > 1) {
                    var i = data.length,
                        j;
                    outer: for (; i--;) { for (j = i; j--;) { if (equal(data[i], data[j])) { valid3 = false; break outer; } } }
                }
                if (!valid3) {
                    var err = { keyword: \'uniqueItems\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/uniqueItems\', params: { i: i, j: j }, message: \'should NOT have duplicate items (items ## \' + j + \' and \' + i + \' are identical)\', schema: true, parentSchema: refVal[2], data: data };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                }
                var errs__3 = errors;
                var valid3;
                for (var i3 = 0; i3 < data.length; i3++) {
                    var data1 = data[i3];
                    var errs_4 = errors;
                    if (typeof data1 === "string") {
                        if (ucs2length(data1) < 1) {
                            var err = { keyword: \'minLength\', dataPath: (dataPath || \'\') + \'[\' + i3 + \']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/items/minLength\', params: { limit: 1 }, message: \'should NOT be shorter than 1 characters\', schema: 1, parentSchema: refVal[2].items, data: data1 };
                            if (vErrors === null) vErrors = [err];
                            else vErrors.push(err);
                            errors++;
                        }
                    } else {
                        var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'[\' + i3 + \']\', schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/items/type\', params: { type: \'string\' }, message: \'should be string\', schema: refVal[2].items.type, parentSchema: refVal[2].items, data: data1 };
                        if (vErrors === null) vErrors = [err];
                        else vErrors.push(err);
                        errors++;
                    }
                    var valid4 = errors === errs_4;
                }
            } else {
                var err = { keyword: \'type\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/definitions/common.nonEmptyArrayOfUniqueStringValues/type\', params: { type: \'array\' }, message: \'should be array\', schema: refVal[2].type, parentSchema: refVal[2], data: data };
                if (vErrors === null) vErrors = [err];
                else vErrors.push(err);
                errors++;
            }
            var valid3 = errors === errs_3;
            var valid2 = errors === errs_2;
            valid1 = valid1 || valid2;
            if (!valid1) {
                var err = { keyword: \'anyOf\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf/2/anyOf\', params: {}, message: \'should match some schema in anyOf\', schema: validate.schema.oneOf[2].anyOf, parentSchema: validate.schema.oneOf[2], data: data };
                if (vErrors === null) vErrors = [err];
                else vErrors.push(err);
                errors++;
            } else {
                errors = errs__1;
                if (vErrors !== null) {
                    if (errs__1) vErrors.length = errs__1;
                    else vErrors = null;
                }
            }
            var valid1 = errors === errs_1;
            if (valid1 && prevValid0) valid0 = false;
            else {
                if (valid1) valid0 = prevValid0 = true;
                var valid1 = true;
                if (valid1 && prevValid0) valid0 = false;
                else { if (valid1) valid0 = prevValid0 = true; }
            }
        }
        if (!valid0) {
            var err = { keyword: \'oneOf\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/oneOf\', params: {}, message: \'should match exactly one schema in oneOf\', schema: validate.schema.oneOf, parentSchema: validate.schema, data: data };
            if (vErrors === null) vErrors = [err];
            else vErrors.push(err);
            errors++;
        } else {
            errors = errs__0;
            if (vErrors !== null) {
                if (errs__0) vErrors.length = errs__0;
                else vErrors = null;
            }
        }
View Code

规则:

1.数据可以为字符串、对象、数组

2.字符串必须为非空字符串

3.数组必须是非空数组,元素必须是非空字符串且不能重复

4.如果是对象则至少要有2个键,键的规则满足2、3

 

refVal3

if ((data && typeof data === "object" && !Array.isArray(data))) {
            var errs__0 = errors;
            var valid1 = true;
            for (var key0 in data) {
                var isAdditional0 = !(false || validate.schema.properties[key0]);
                if (isAdditional0) {
                    valid1 = false;
                    var err = { keyword: \'additionalProperties\', dataPath: (dataPath || \'\') + "", schemaPath: \'#/additionalProperties\', params: { additionalProperty: \'\' + key0 + \'\' }, message: \'should NOT have additional properties\', schema: false, parentSchema: validate.schema, data: data };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                }
            }
            var data1 = data.exprContextCritical;
            if (data1 !== undefined) {
                var errs_1 = errors;
                if (typeof data1 !== "boolean") {
                    var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'.exprContextCritical\', schemaPath: \'#/properties/exprContextCritical/type\', params: { type: \'boolean\' }, message: \'should be boolean\', schema: validate.schema.properties.exprContextCritical.type, parentSchema: validate.schema.properties.exprContextCritical, data: data1 };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                }
                var valid1 = errors === errs_1;
            }
            var data1 = data.exprContextRecursive;
            if (data1 !== undefined) {
                var errs_1 = errors;
                if (typeof data1 !== "boolean") {
                    var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'.exprContextRecursive\', schemaPath: \'#/properties/exprContextRecursive/type\', params: { type: \'boolean\' }, message: \'should be boolean\', schema: validate.schema.properties.exprContextRecursive.type, parentSchema: validate.schema.properties.exprContextRecursive, data: data1 };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                }
                var valid1 = errors === errs_1;
            }
            if (data.exprContextRegExp !== undefined) { var errs_1 = errors; var valid1 = errors === errs_1; }
            var data1 = data.exprContextRequest;
            if (data1 !== undefined) {
                var errs_1 = errors;
                if (typeof data1 !== "string") {
                    var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'.exprContextRequest\', schemaPath: \'#/properties/exprContextRequest/type\', params: { type: \'string\' }, message: \'should be string\', schema: validate.schema.properties.exprContextRequest.type, parentSchema: validate.schema.properties.exprContextRequest, data: data1 };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                }
                var valid1 = errors === errs_1;
            }
            var data1 = data.loaders;
            if (data1 !== undefined) {
                var errs_1 = errors;
                var errs__1 = errors;
                var valid1 = false;
                var errs_2 = errors;
                if (!refVal4(data1, (dataPath || \'\') + \'.loaders\', data, \'loaders\', rootData)) {
                    if (vErrors === null) vErrors = refVal4.errors;
                    else vErrors = vErrors.concat(refVal4.errors);
                    errors = vErrors.length;
                }
                var valid2 = errors === errs_2;
                valid1 = valid1 || valid2;
                if (!valid1) {
                    var err = { keyword: \'anyOf\', dataPath: (dataPath || \'\') + \'.loaders\', schemaPath: \'#/properties/loaders/anyOf\', params: {}, message: \'should match some schema in anyOf\', schema: validate.schema.properties.loaders.anyOf, parentSchema: validate.schema.properties.loaders, data: data1 };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                } else {
                    errors = errs__1;
                    if (vErrors !== null) {
                        if (errs__1) vErrors.length = errs__1;
                        else vErrors = null;
                    }
                }
                var valid1 = errors === errs_1;
            }
            if (data.noParse !== undefined) { var errs_1 = errors; var valid1 = errors === errs_1; }
            if (data.rules !== undefined) {
                var errs_1 = errors;
                var errs_2 = errors;
                if (!refVal[4](data.rules, (dataPath || \'\') + \'.rules\', data, \'rules\', rootData)) {
                    if (vErrors === null) vErrors = refVal[4].errors;
                    else vErrors = vErrors.concat(refVal[4].errors);
                    errors = vErrors.length;
                }
                var valid2 = errors === errs_2;
                var valid1 = errors === errs_1;
            }
            var data1 = data.unknownContextCritical;
            if (data1 !== undefined) {
                var errs_1 = errors;
                if (typeof data1 !== "boolean") {
                    var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'.unknownContextCritical\', schemaPath: \'#/properties/unknownContextCritical/type\', params: { type: \'boolean\' }, message: \'should be boolean\', schema: validate.schema.properties.unknownContextCritical.type, parentSchema: validate.schema.properties.unknownContextCritical, data: data1 };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                }
                var valid1 = errors === errs_1;
            }
            var data1 = data.unknownContextRecursive;
            if (data1 !== undefined) {
                var errs_1 = errors;
                if (typeof data1 !== "boolean") {
                    var err = { keyword: \'type\', dataPath: (dataPath || \'\') + \'.unknownContextRecursive\', schemaPath: \'#/properties/unknownContextRecursive/type\', params: { type: \'boolean\' }, message: \'should be boolean\', schema: validate.schema.properties.unknownContextRecursive.type, parentSchema: validate.schema.properties.unknownContextRecursive, data: data1 };
                    if (vErrors === null) vErrors = [err];
                    else vErrors.push(err);
                    errors++;
                }
                var valid1 = errors === errs_1;
            }
     

以上是关于.6-浅析webpack源码之validateSchema模块的主要内容,如果未能解决你的问题,请参考以下文章

.7-浅析webpack源码之WebpackOptionsDefaulter模块

.11-浅析webpack源码之Storage模块

.12-浅析webpack源码之NodeWatchFileSystem模块总览

.32-浅析webpack源码之doResolve事件流

.5-浅析webpack源码之入口函数

.39-浅析webpack源码之parser.parse