Files
developtools_ace-js2bundle/ace-loader/plugin/templater/index.js
T
houhaoyu e73398d464 houhaoyu@huawei.com
Signed-off-by: houhaoyu <houhaoyu@huawei.com>
Change-Id: I25cb3ea67d45402dfcc6c7814e2e53ea27eb01e0
2021-11-25 19:30:24 +08:00

299 lines
9.3 KiB
JavaScript

/*
* Copyright (c) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const componentValidator = require('./component_validator')
const Parser = require('../parse/parser/index')
const path = require('path')
let compileResult
const EVENT_START_REGEXP = /^(on:|on|@|grab:)/
const REGEXP_TEXT = /^#/
const REGEXP_DATA = /^data-/
/**
* Compile html file into ast object.
* @param {String} source Hml file content.
* @param {Function} operate The second number.
* @param {String} filePath File resource path.
*/
function parse(source, operate, filePath) {
const pathParse = path.parse(filePath)
let relativePath = pathParse.dir.replace(process.env.aceModuleRoot ||
process.cwd(), '') + '/' + pathParse.base.replace(pathParse.ext, '')
relativePath = replaceAll(path.sep, '/', relativePath).replace('/', '')
const result = { jsonTemplate: {}, deps: [], log: [] }
compileResult = result
const template = hmlParse(source, {
sourceCodeLocationInfo: true,
})
if (checkNullNode(template, operate) || checkRootNode(template, operate, relativePath)) {
return
}
const rootArray = template.childNodes.filter(function(currentValue) {
return currentValue.nodeName.indexOf('#') === -1
})
let rootIndex = 0
rootArray.forEach((root, index) => {
if(root.tagName !== 'element') {
rootIndex = index
}
})
generate(rootArray[rootIndex], filePath, undefined, relativePath)
operate(null, compileResult)
}
/**
* Use parse5 to get html conversion results.
* @param {String} code Hml file content.
* @param {Object} config parse5 parseFragment function config.
* @return {Object} html conversion results.
*/
function hmlParse(code, config) {
const parse = new Parser({ sourceCodeLocationInfo: true, componentValidator, compileResult })
const res = parse.parseFragment(code, config)
return res
}
/**
* Check if the hml file does not contain nodes.
* @param {String} template hml conversion results.
* @return {Boolean} Check result.
*/
function checkNullNode(template, operate) {
let errorFlag = false
if (template.childNodes.length === 0) {
compileResult.log.push({ reason: 'ERROR: parsing hml file failed' })
operate(null, compileResult)
errorFlag = true
}
return errorFlag
}
/**
* Check if the root node is legal.
* @param {String} template hml conversion results.
* @return {Boolean} Check result.
*/
function checkRootNode(template, operate, relativePath) {
let errorFlag = false
const rootArray = template.childNodes.filter(function(currentValue) {
return currentValue.nodeName.indexOf('#') === -1
})
let rootNum = 0
rootArray.forEach(root => {
if (root.nodeName !== 'element') {
rootNum ++
} else if (root.attrs && root.attrs.length) {
for (let index = 0; index < root.attrs.length; index++) {
const element = root.attrs[index];
if (element.name === 'name') {
componentValidator.elementNames[relativePath] = componentValidator.elementNames[relativePath] || []
componentValidator.elementNames[relativePath].push(element.value)
break
}
}
}
})
if (rootNum !== 1) {
if (!rootNum) {
compileResult.log.push({
reason: 'ERROR: need a legal root node',
line: 1,
column: 1,
})
}
if (rootNum > 1) {
compileResult.log.push({
reason: 'ERROR: there can only be one root node',
line: 1,
column: 1,
})
}
operate(null, compileResult)
errorFlag = true
}
return errorFlag
}
/**
* Recursively parse every node.
* @param {Object} node Nodes that need to be resolved.
* @param {String} filePath File resource path.
* @param {Object} preNode the previous node.
*/
function generate(node, filePath, preNode, relativePath) {
componentValidator.validateTagName(node, compileResult, relativePath)
if (node.attrs && node.attrs.length !== 0) {
checkNodeAttrs(node, filePath, preNode, relativePath)
}
if (node.childNodes && node.childNodes.length !== 0) {
checkNodeChildren(node, filePath, relativePath)
}
}
/**
* Parse node properties.
* @param {Object} node Nodes that need to be resolved.
* @param {String} filePath File resource path.
* @param {Object} preNode the previous node.
*/
function checkNodeAttrs(node, filePath, preNode, relativePath) {
const attributes = node.attrs
const pos = {
line: node.sourceCodeLocation.startLine,
col: node.sourceCodeLocation.endLine,
}
attributes.forEach((attr, index) => {
const attrName = attr.name
const attrValue = attr.value
switch (attrName) {
case 'style':
componentValidator.validateStyle(attrValue, compileResult, pos, relativePath)
break
case 'class':
componentValidator.validateClass(attrValue, compileResult, pos, relativePath)
break
case 'id':
componentValidator.validateId(attrValue, compileResult, pos, relativePath)
break
case 'for':
checkAttrFor(node, attributes, pos);
componentValidator.validateFor(attrValue, compileResult, pos, relativePath)
break
case 'if':
componentValidator.validateIf(attrValue, compileResult, false, pos, relativePath)
break
case 'elif':
componentValidator.validateAttrElif(preNode, attrValue, attributes, index,
compileResult, pos, relativePath)
break
case 'else':
componentValidator.validateAttrElse(preNode, compileResult, pos, relativePath)
break
case 'append':
componentValidator.validateAppend(attrValue, compileResult, pos, relativePath)
break
default:
if (EVENT_START_REGEXP.test(attrName)) {
componentValidator.validateEvent(attrName, attrValue, compileResult, pos, relativePath)
} else if (REGEXP_DATA.test(attrName)) {
componentValidator.parseDataAttr(attrName, attrValue, compileResult, pos, relativePath)
} else {
componentValidator.validateAttr(
filePath,
attrName,
attrValue,
compileResult,
node.tagName,
pos,
relativePath
)
}
}
})
}
function checkAttrFor(node, attributes, pos) {
if (process.env.DEVICE_LEVEL === 'card') {
let tidExists = false
let ifExists = false
attributes.forEach(element => {
if(element.name === 'tid') {
tidExists = true
}
if(element.name === 'if' || element.name === 'elif' || element.name === 'else') {
ifExists = true
}
})
if (!tidExists) {
compileResult.log.push({
line: pos.line || 1,
column: pos.col || 1,
reason: `WARNING: The 'tid' is recommended here.`,
})
}
if (ifExists) {
compileResult.log.push({
line: pos.line || 1,
column: pos.col || 1,
reason: `ERROR: The 'for' and 'if' cannot be used in the same node.`,
})
}
let isParentFor = false
if (node && node.parentNode && node.parentNode.attrs) {
node.parentNode.attrs.forEach(element => {
if (element.name === 'for') {
isParentFor = true
}
})
}
if (isParentFor) {
compileResult.log.push({
line: pos.line || 1,
column: pos.col || 1,
reason: `ERROR: The nested 'for' is not supported.`,
})
}
}
}
function replaceAll(find, replace, str) {
find = find.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
return str.replace(new RegExp(find, 'g'), replace);
}
/**
* Parse node children.
* @param {Object} node Nodes that need to be resolved.
* @param {String} filePath File resource path.
* @param {Object} preNode the previous node.
*/
function checkNodeChildren(node, filePath, relativePath) {
const children = node.childNodes.filter((child) => {
return (
(child.nodeName === '#text' && child.value.trim()) ||
!REGEXP_TEXT.test(child.nodeName)
)
})
const temp=compileResult.jsonTemplate
for (let i = 0; i < children.length; i++) {
const child = children[i]
let preNode
if (i > 0) {
preNode = children[i - 1]
}
if (!REGEXP_TEXT.test(child.nodeName)) {
compileResult.jsonTemplate={}
temp.children=temp.children||[]
temp.children.push(compileResult.jsonTemplate)
generate(child, filePath, preNode, relativePath)
} else {
const pos = {
line: child.sourceCodeLocation.startLine,
col: child.sourceCodeLocation.endLine,
}
if (node.nodeName === 'option') {
componentValidator.validateAttr(filePath, 'content', child.value,
compileResult, child.tagName, pos, relativePath)
continue
}
componentValidator.validateAttr(filePath, 'value', child.value,
compileResult, child.tagName, pos, relativePath)
}
}
compileResult.jsonTemplate=temp
}
module.exports = { parse: parse }