mirror of
https://github.com/openharmony/developtools_ace-js2bundle.git
synced 2026-07-19 18:23:34 -04:00
6d2eb39678
Signed-off-by: lizhouze <lizhouze@huawei.com>
297 lines
9.2 KiB
JavaScript
297 lines
9.2 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 relativePath = replaceAll(path.sep, '/', path.relative(
|
|
process.env.aceModuleRoot || process.cwd(), filePath)).replace(path.parse(filePath).ext, '');
|
|
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 }
|