UNPKG

chameleon-linter

Version:

cml规范校验工具

208 lines (177 loc) 6.21 kB
const path = require('path'); const htmllint = require('cml-htmllint'); const config = require('../config'); const whiteListConifg = require('../config/white-list'); const builtinComponents = require('../config/built-in-components'); const tagEmbedRules = require('../config/tag-embed-rules.json'); const fakeComps = require('../config/fakeComps'); const utils = require('../utils'); const linter = new htmllint.Linter(); const CML_METHODNAME_REGEX = /(?:c-bind:|c-catch:)(\w+)/; const VUE_METHODNAME_REGEX = /(?:v-on:|v-once:|@)(\w+)/; const VUE_PROP_REGEX = /(?:v-bind:|:)((?:\w|-)+)/; const APP_ENTRANCE_FILENAME = 'app.cml'; /** * Parse json ast and retrive the custumized component names. * @param {Object} jsonAst * @returns An array contains all the component names. ej. [{name: 'view', isOrigin: true}, {name:'picker', isOrigin: false}] */ function getCustimizedTags(jsonAst, {platform = '', templatePath = ''}) { let result = []; let componentsObj = {}; if (jsonAst) { let baseJson = jsonAst.base || {}; let platformJson = {}; if (platform) { platformJson = jsonAst[platform] || {}; } Object.assign(componentsObj, baseJson.usingComponents, platformJson.usingComponents); } result = Object.entries(componentsObj) .map((infoPair) => { let [name, basePath] = infoPair; return { name: utils.toDash(name), isOrigin: !utils.isCmlComponent(path.resolve(config.getCurrentWorkspace(), templatePath), basePath) } }); return result; } /** * Getting options for html linter. * @params {Object} * platformType: 'web'|'wx'|'weex' | '' // '' stands for single file component * lang: 'cml'|'vue' * custimizedTags: tags appeding by usingComponents. */ function getLintOptions(params) { let options = { 'line-no-trailing-whitespace': false, 'line-end-style': false, 'attr-name-style': false, 'indent-width': false, 'class-style': 'none', 'template-req-lang': false, 'attr-quote-style': 'quoted', 'spec-char-escape': false, 'attr-req-value': false, 'tag-close': true // ignoring tag close detection for now, a better solution will be provide in the future. }; options['template-lang-allows'] = ['cml', 'vue']; options['template-lang'] = params.lang; options['attr-bans'] = whiteListConifg.getForbiddenAttrsByLang(params.lang); options['tag-only-allowed-names'] = whiteListConifg .getAllowedTags() .concat(params.custimizedTags .map((tag) => { return tag.name; })); // origin components that are referenced by usingComponents. options['origin-tag-list'] = params.custimizedTags .filter((tag) => { return tag.isOrigin; }) .map((tag) => { return tag.name; }); // options associated with directives. if (params.lang === 'vue') { options['directive-name-forbidden-regex'] = /^(c-(?:bind|catch)(?!\w))/; } else { options['directive-name-forbidden-regex'] = /^((?:v-(?:bind|on|once)(?!\w))|:|@)/; options['text-ignore-regex'] = /{{(.*?)}}/; } options['cml-directives'] = whiteListConifg.getForbiddenAttrsByLang('vue'); options['cml-valid-value-regex'] = /^\s*{{.*}}\s*$/; // built-in components configurations. options['component-allow-attr'] = builtinComponents.getCml(); if (params.lang === 'cml') { options['component-event-regex'] = CML_METHODNAME_REGEX; options['component-prop-regex'] = false; } else { options['component-event-regex'] = VUE_METHODNAME_REGEX; options['component-prop-regex'] = VUE_PROP_REGEX; } // built-in embed tags' rules options['tag-embed-tags'] = tagEmbedRules; // origin components rules options['origin-tag-forbiden-directive-regex'] = new RegExp('^(' + whiteListConifg.getForbiddenAttrsByLang('vue').join('|') + ')'); return options; } /** * A temporary solution for application entrance file app.cml * @param {Object} options lint options */ function addFakeComp(options, comp) { if (options['component-allow-attr']) { options['component-allow-attr'][comp.name] = comp.allowAttrs; } if (options['tag-only-allowed-names']) { options['tag-only-allowed-names'].push(comp.name); } } function isAppEntranceFile(file) { return file && path.basename(file) === APP_ENTRANCE_FILENAME; } /** * @part {Object} * line: line offset of template part. * rawContent: the whole tempalte string. * platformType:'web'|'wx'|'weex' * params: { * lang:'cml' * } * @jsonAst {Object} An ast of json part. */ module.exports = (part, jsonAst) => { return new Promise(async (resolve, reject) => { let ast = {}; let messages = []; let lintResults = []; let lintOptions = {}; let templateMatches = []; part.params.lang || (part.params.lang = 'cml'); ast = linter.parser.parse(part.rawContent); templateMatches = part.rawContent.match(/<\/?\s*?template/ig); // eslint-disable-next-line no-magic-numbers if (!templateMatches || templateMatches.length !== 2) { return resolve({ start: part.line, ast, messages: [{ line: part.line + 1, column: 0, token: 'template', msg: 'Each template can only have one group of template tags' }] }); } lintOptions = getLintOptions({ ...part.params, platformType: part.platformType || 'cml', ...{custimizedTags: getCustimizedTags(jsonAst, { templatePath: part.file, platform: part.platformType })} }); // adding fake comps const allowedFakeComps = ['component']; if (isAppEntranceFile(part.file)) { allowedFakeComps.push('app'); } fakeComps && fakeComps.forEach((comp) => { if (~allowedFakeComps.indexOf(comp.name)) { addFakeComp(lintOptions, comp); } }); lintResults = await htmllint(part.rawContent, lintOptions); lintResults && lintResults.forEach((item) => { messages.push({ line: item.line, column: item.column, token: htmllint.messages.guessToken(item), msg: htmllint.messages.renderIssue(item) }); }); resolve({ start: part.line, ast, messages }); }); };