1/* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16const ts = require('typescript'); 17const path = require('path'); 18const chai = require('chai'); 19const mocha = require('mocha'); 20const expect = chai.expect; 21const { 22 processUISyntax, 23 transformLog 24} = require('../lib/process_ui_syntax'); 25const { 26 validateUISyntax, 27 preprocessExtend, 28 resetComponentCollection, 29 componentCollection 30} = require('../lib/validate_ui_syntax'); 31const { 32 componentInfo, 33 readFile, 34 storedFileInfo 35} = require('../lib/utils'); 36const { 37 BUILD_ON, 38 OHOS_PLUGIN, 39 NATIVE_MODULE, 40 SYSTEM_PLUGIN 41} = require('../lib/pre_define'); 42const { 43 partialUpdateConfig, 44 projectConfig 45} = require('../main'); 46 47projectConfig.projectPath = path.resolve(process.cwd()); 48 49function expectActual(name, filePath, checkError = false) { 50 const content = require(filePath); 51 const source = content.source; 52 process.env.compiler = BUILD_ON; 53 storedFileInfo.setCurrentArkTsFile(); 54 const afterProcess = sourceReplace(source); 55 validateUISyntax(source, afterProcess.content, `${name}.ets`); 56 const compilerOptions = ts.readConfigFile( 57 path.resolve(__dirname, '../tsconfig.json'), ts.sys.readFile).config.compilerOptions; 58 Object.assign(compilerOptions, { 59 'sourceMap': false 60 }); 61 const result = ts.transpileModule(afterProcess.content, { 62 compilerOptions: compilerOptions, 63 fileName: `${name}.ets`, 64 transformers: { before: [processUISyntax(null, true)] } 65 }); 66 componentInfo.id = 0; 67 componentCollection.customComponents.clear(); 68 resetComponentCollection(); 69 if (checkError) { 70 assertError(name); 71 transformLog.errors = []; 72 } else { 73 expect(result.outputText).eql(content.expectResult); 74 } 75} 76 77mocha.describe('compiler', () => { 78 let utPath = path.resolve(__dirname, './ut'); 79 if (process.argv.includes('--partialUpdate')) { 80 partialUpdateConfig.partialUpdateMode = true; 81 utPath = path.resolve(__dirname, './utForPartialUpdate'); 82 } else if (process.argv.includes('--assertError')) { 83 partialUpdateConfig.partialUpdateMode = true; 84 utPath = path.resolve(__dirname, './utForValidate'); 85 } 86 const utFiles = []; 87 readFile(utPath, utFiles); 88 utFiles.forEach((item) => { 89 const fileName = path.basename(item, '.ts'); 90 mocha.it(fileName, () => { 91 if (process.argv.includes('--assertError')) { 92 expectActual(fileName, item, true); 93 } else { 94 expectActual(fileName, item); 95 } 96 }); 97 }); 98}); 99 100function sourceReplace(source) { 101 let content = source; 102 const log = []; 103 content = preprocessExtend(content); 104 content = processSystemApi(content); 105 return { 106 content: content, 107 log: log 108 }; 109} 110 111function processSystemApi(content) { 112 const REG_SYSTEM = 113 /import\s+(.+)\s+from\s+['"]@(system|ohos)\.(\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)/g; 114 const REG_LIB_SO = 115 /import\s+(.+)\s+from\s+['"]lib(\S+)\.so['"]|import\s+(.+)\s*=\s*require\(\s*['"]lib(\S+)\.so['"]\s*\)/g; 116 const newContent = content.replace(REG_LIB_SO, (_, item1, item2, item3, item4) => { 117 const libSoValue = item1 || item3; 118 const libSoKey = item2 || item4; 119 return `var ${libSoValue} = globalThis.requireNapi("${libSoKey}", true);`; 120 }).replace(REG_SYSTEM, (item, item1, item2, item3, item4, item5, item6, item7) => { 121 let moduleType = item2 || item5; 122 let systemKey = item3 || item6; 123 let systemValue = item1 || item4; 124 if (NATIVE_MODULE.has(`${moduleType}.${systemKey}`)) { 125 item = `var ${systemValue} = globalThis.requireNativeModule('${moduleType}.${systemKey}')`; 126 } else if (moduleType === SYSTEM_PLUGIN) { 127 item = `var ${systemValue} = isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ? ` + 128 `globalThis.systemplugin.${systemKey} : globalThis.requireNapi('${systemKey}')`; 129 } else if (moduleType === OHOS_PLUGIN) { 130 item = `var ${systemValue} = globalThis.requireNapi('${systemKey}') || ` + 131 `(isSystemplugin('${systemKey}', '${OHOS_PLUGIN}') ? ` + 132 `globalThis.ohosplugin.${systemKey} : isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ` + 133 `? globalThis.systemplugin.${systemKey} : undefined)`; 134 } 135 return item; 136 }); 137 return newContent; 138} 139 140function assertError(fileName) { 141 switch (fileName) { 142 case '@linkInitialize': { 143 expect(transformLog.errors[0].message).to.be.equal(`The @Link property 'link' cannot be specified a default value.`); 144 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 145 break; 146 } 147 case '@objectLinkInitialize': { 148 expect(transformLog.errors[0].message).to.be.equal(`The @ObjectLink property 'objectLink' cannot be specified a default value.`); 149 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 150 break; 151 } 152 // process_component_build.ts 153 case 'rootContainerCheck': { 154 expect(transformLog.errors[0].message).to.be.equal(`There should have a root container component.`); 155 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 156 break; 157 } 158 case 'arkUIComponent': { 159 expect(transformLog.errors[0].message).to.be.equal(`Only UI component syntax can be written in build method.`); 160 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 161 break; 162 } 163 case '@BuilderParam': { 164 expect(transformLog.errors[0].message).to.be.equal( 165 `In the trailing lambda case, 'CustomContainer' must have one and only one property decorated with @BuilderParam, and its @BuilderParam expects no parameter.`); 166 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 167 break; 168 } 169 case 'forEachParamCheck': { 170 expect(transformLog.errors[0].message).to.be.equal(`There should be wrapped in curly braces in ForEach.`); 171 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 172 break; 173 } 174 case 'ifComponent': { 175 expect(transformLog.errors[0].message).to.be.equal(`Condition expression cannot be null in if statement.`); 176 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 177 expect(transformLog.errors[1].message).to.be.equal(`Then statement cannot be null in if statement.`); 178 expect(transformLog.errors[1].type).to.be.equal('ERROR'); 179 break; 180 } 181 case 'idCheck': { 182 expect(transformLog.errors[0].message).to.be.equal( 183 `The current component id "1" is duplicate with ${path.resolve(__dirname, '../idCheck.ets')}:7:21.`); 184 expect(transformLog.errors[0].type).to.be.equal('WARN'); 185 break; 186 } 187 case 'arkUIStandard': { 188 expect(transformLog.errors[0].message).to.be.equal(`'Text('Hello').onCilck' does not meet UI component syntax.`); 189 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 190 break; 191 } 192 case 'stateStyles': { 193 expect(transformLog.errors[0].message).to.be.equal(`.stateStyles doesn't conform standard.`); 194 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 195 break; 196 } 197 case 'buttonCheck': { 198 expect(transformLog.errors[0].message).to.be.equal(`The Button component with a label parameter can not have any child.`); 199 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 200 break; 201 } 202 case 'attributeCheck': { 203 expect(transformLog.errors[0].message).to.be.equal(`'ForEach(this.arr, () =>{}, this.arr[0]).h' does not meet UI component syntax.`); 204 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 205 break; 206 } 207 // process_component_class 208 case 'validateDecorators': { 209 expect(transformLog.errors[0].message).to.be.equal(`The static variable of struct cannot be used together with built-in decorators.`); 210 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 211 break; 212 } 213 case 'processComponentMethod': { 214 expect(transformLog.errors[0].message).to.be.equal(`The 'build' method can not have arguments.`); 215 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 216 break; 217 } 218 case '@StylesParamChack': { 219 expect(transformLog.errors[0].message).to.be.equal(`@Styles can't have parameters.`); 220 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 221 break; 222 } 223 case 'updateHeritageClauses': { 224 expect(transformLog.errors[0].message).to.be.equal(`The struct component is not allowed to extends other class or implements other interface.`); 225 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 226 break; 227 } 228 case 'validateBuildMethodCount': { 229 expect(transformLog.errors[0].message).to.be.equal(`struct 'Index' must be at least or at most one 'build' method.`); 230 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 231 break; 232 } 233 case 'validateHasController': { 234 expect(transformLog.errors[0].message).to.be.equal(`@CustomDialog component should have a property of the CustomDialogController type.`); 235 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 236 break; 237 } 238 // process_component_member 239 case 'processWatch': { 240 expect(transformLog.errors[0].message).to.be.equal(`Cannot find name 'onWatch' in struct 'Index'.`); 241 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 242 break; 243 } 244 case 'updateBuilderParamProperty': { 245 expect(transformLog.errors[0].message).to.be.equal(`BuilderParam property can only initialized by Builder function.`); 246 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 247 break; 248 } 249 case 'validateMultiDecorators': { 250 expect(transformLog.errors[0].message).to.be.equal(`The property 'lang' cannot have mutilate state management decorators.`); 251 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 252 break; 253 } 254 case 'validateDecoratorNonSingleKey': { 255 expect(transformLog.errors[0].message).to.be.equal(`The decorator StorageLink should have a single key.`); 256 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 257 break; 258 } 259 case 'validatePropertyNonDefaultValue': { 260 expect(transformLog.errors[0].message).to.be.equal(`The @State property 'message' must be specified a default value.`); 261 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 262 break; 263 } 264 case 'validatePropertyDefaultValue': { 265 expect(transformLog.errors[0].message).to.be.equal(`The @Link property 'message' cannot be specified a default value.`); 266 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 267 break; 268 } 269 case 'validatePropertyNonType': { 270 expect(transformLog.errors[0].message).to.be.equal(`The property 'message' must specify a type.`); 271 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 272 break; 273 } 274 case 'validateNonObservedClassType': { 275 expect(transformLog.errors[0].message).to.be.equal( 276 `The type of the @ObjectLink property 'message' can only be objects of classes decorated with @Observed class decorator in ets (not ts).`); 277 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 278 break; 279 } 280 case 'validateHasIllegalDecoratorInEntry': { 281 expect(transformLog.errors[0].message).to.be.equal(`The @Entry component 'Index' cannot have the @Prop property 'message'.`); 282 expect(transformLog.errors[0].type).to.be.equal('WARN'); 283 break; 284 } 285 case 'validateHasIllegalQuestionToken': { 286 expect(transformLog.errors[0].message).to.be.equal(`The @ObjectLink property 'message' cannot be an optional parameter.`); 287 expect(transformLog.errors[0].type).to.be.equal('WARN'); 288 break; 289 } 290 case 'validateForbiddenUseStateType': { 291 expect(transformLog.errors[0].message).to.be.equal(`The @State property 'message' cannot be a 'CustomDialogController' object.`); 292 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 293 break; 294 } 295 case 'validateDuplicateDecorator': { 296 expect(transformLog.errors[1].message).to.be.equal( 297 `The decorator '@opacity' cannot have the same name as the built-in style attribute 'opacity'.`); 298 expect(transformLog.errors[1].type).to.be.equal('ERROR'); 299 break; 300 } 301 case 'validateWatchDecorator': { 302 expect(transformLog.errors[0].message).to.be.equal(`Regular variable 'message' can not be decorated with @Watch.`); 303 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 304 break; 305 } 306 case 'validateWatchParam': { 307 expect(transformLog.errors[0].message).to.be.equal(`The parameter should be a string.`); 308 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 309 break; 310 } 311 case 'validateCustomDecorator': { 312 expect(transformLog.errors[0].message).to.be.equal(`The inner decorator @State cannot be used together with custom decorator.`); 313 expect(transformLog.errors[0].type).to.be.equal('ERROR'); 314 break; 315 } 316 } 317} 318