• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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