• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use rollupObject 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
16import mocha from 'mocha';
17import fs from 'fs';
18import path from 'path';
19import { expect } from 'chai';
20import * as ts from 'typescript';
21import sinon from 'sinon';
22import proxyquire from 'proxyquire';
23
24import {
25  DEFAULT_ENTRY,
26  DEFAULT_PROJECT,
27  EXPECT_INDEX_ETS,
28  PROJECT_ROOT
29} from './mock/rollup_mock/path_config';
30import RollUpPluginMock from './mock/rollup_mock/rollup_plugin_mock';
31import {
32  addLocalPackageSet,
33  compilerOptions,
34  localPackageSet,
35  needReCheckForChangedDepUsers,
36  resetEtsCheck,
37  serviceChecker,
38  resolveModuleNames as resolveModuleNamesMain,
39  getMaxFlowDepth,
40  MAX_FLOW_DEPTH_DEFAULT_VALUE,
41  fileCache,
42  getFileContentWithHash
43} from '../../lib/ets_checker';
44import { TS_BUILD_INFO_SUFFIX } from '../../lib/pre_define';
45import {
46  globalProgram,
47  projectConfig
48} from '../../main';
49import { mkdirsSync } from '../../lib/utils';
50import {
51  arkTSEvolutionModuleMap,
52  cleanUpProcessArkTSEvolutionObj
53} from '../../lib/fast_build/ark_compiler/interop/process_arkts_evolution';
54
55mocha.describe('test ets_checker file api', function () {
56    mocha.before(function () {
57        this.rollup = new RollUpPluginMock();
58    });
59
60    mocha.after(() => {
61        delete this.rollup;
62        const cacheFile: string = path.resolve(projectConfig.cachePath, '../.ts_checker_cache');
63        if (fs.existsSync(cacheFile)) {
64            fs.unlinkSync(cacheFile);
65        }
66        const tsBuildInfoFilePath: string = path.resolve(projectConfig.cachePath, '..', TS_BUILD_INFO_SUFFIX);
67        if (fs.existsSync(tsBuildInfoFilePath)) {
68            fs.unlinkSync(tsBuildInfoFilePath);
69        }
70        const tsBuildInfoLinterFilePath: string = tsBuildInfoFilePath + '.linter';
71        if (fs.existsSync(tsBuildInfoLinterFilePath)) {
72            fs.unlinkSync(tsBuildInfoLinterFilePath);
73        }
74    });
75
76    mocha.it('1-1: test addLocalPackageSet for original ohmurl', function () {
77        this.rollup.build();
78        const rollupObject = this.rollup;
79        const rollupFileList = rollupObject.getModuleIds();
80        projectConfig.useNormalizedOHMUrl = false;
81        for (const moduleId of rollupFileList) {
82            if (fs.existsSync(moduleId)) {
83                addLocalPackageSet(moduleId, rollupObject);
84            }
85        }
86        expect(localPackageSet.has('entry')).to.be.true;
87        localPackageSet.clear();
88    });
89
90    mocha.it('1-2: test addLocalPackageSet for normalized ohmurl', function () {
91        this.rollup.build();
92        const rollupObject = this.rollup;
93        const rollupFileList = rollupObject.getModuleIds();
94        projectConfig.useNormalizedOHMUrl = true;
95        for (const moduleId of rollupFileList) {
96            const moduleInfo: Object = rollupObject.getModuleInfo(moduleId);
97            if (moduleInfo) {
98                const metaInfo: Object = moduleInfo.meta;
99                metaInfo.pkgName = 'pkgname';
100            }
101            if (fs.existsSync(moduleId)) {
102                addLocalPackageSet(moduleId, rollupObject);
103            }
104        }
105        expect(localPackageSet.has('pkgname')).to.be.true;
106    });
107
108    mocha.it('1-3: test getOrCreateLanguageService when dependency in oh-package.json change', function () {
109        this.timeout(10000);
110        this.rollup.build();
111        let rollupObject = this.rollup;
112        expect(needReCheckForChangedDepUsers).to.be.false;
113
114        interface MockCacheStore {
115            service: Object | undefined;
116            pkgJsonFileHash: string;
117            targetESVersion: number;
118        }
119        const mockServiceCache = {
120            service: undefined,
121            pkgJsonFileHash: '9f07917d395682c73a90af8f5796a2c6',
122            targetESVersion: 8
123        }
124        let mockCache = new Map<string, MockCacheStore>();
125        mockCache.set('service', mockServiceCache);
126        rollupObject.share.cache = mockCache;
127        rollupObject.share.depInfo = {enableIncre: true};
128        rollupObject.share.projectConfig.pkgJsonFileHash = '26bde0f30dda53b0afcbf39428ec9851';
129        Object.assign(projectConfig, rollupObject.share.projectConfig);
130
131        // The current hash and the hash in cache of the dependency differs, should recheck
132        serviceChecker([EXPECT_INDEX_ETS], null, null, null, rollupObject.share);
133        expect(needReCheckForChangedDepUsers).to.be.true;
134        expect(globalProgram.program != null).to.be.true;
135
136        resetEtsCheck();
137        expect(needReCheckForChangedDepUsers).to.be.false;
138        expect(globalProgram.program == null).to.be.true;
139
140        // The current hash and the hash in cache of the dependency are the same, no need to recheck
141        serviceChecker([EXPECT_INDEX_ETS], null, null, null, rollupObject.share);
142        expect(needReCheckForChangedDepUsers).to.be.false;
143        expect(globalProgram.program != null).to.be.true;
144
145        resetEtsCheck();
146        expect(needReCheckForChangedDepUsers).to.be.false;
147        expect(globalProgram.program == null).to.be.true;
148    });
149
150    mocha.it('1-4: test getOrCreateLanguageService when paths in compilerOptions change', function () {
151        this.timeout(10000);
152        this.rollup.build();
153        let rollupObject = this.rollup;
154        process.env.compileTool = 'rollup';
155
156        Object.assign(projectConfig, rollupObject.share.projectConfig);
157        serviceChecker([EXPECT_INDEX_ETS], null, null, null, rollupObject.share);
158        expect(JSON.stringify(compilerOptions.paths) === '{"*":["*","../../../../*","../*"]}').to.be.true;
159        expect(needReCheckForChangedDepUsers).to.be.false;
160        expect(globalProgram.program != null).to.be.true;
161        expect(compilerOptions.skipPathsInKeyForCompilationSettings).to.be.true;
162
163        resetEtsCheck();
164        expect(needReCheckForChangedDepUsers).to.be.false;
165        expect(globalProgram.program == null).to.be.true;
166        expect(compilerOptions.skipPathsInKeyForCompilationSettings).to.be.true;
167
168        interface MockCacheStore {
169            service: Object | undefined;
170            pkgJsonFileHash: string;
171            targetESVersion: number;
172        }
173        const mockServiceCache = {
174            service: undefined,
175            pkgJsonFileHash: '9f07917d395682c73a90af8f5796a2c6',
176            targetESVersion: 8
177        }
178        let mockCache = new Map<string, MockCacheStore>();
179        mockCache.set('service', mockServiceCache);
180        rollupObject.share.cache = mockCache;
181        rollupObject.share.depInfo = {enableIncre: true};
182        rollupObject.share.projectConfig.pkgJsonFileHash = '26bde0f30dda53b0afcbf39428ec9851';
183
184        // The current hash of the dependency differs, and the paths in compilerOptions will change since resolveModulePaths change
185        const resolveModulePaths = ['../testdata/expect'];
186        serviceChecker([EXPECT_INDEX_ETS], null, resolveModulePaths, null, rollupObject.share);
187        expect(JSON.stringify(compilerOptions.paths) === '{"*":["*","../../../../../../../testdata/expect/*"]}').to.be.true;
188        expect(needReCheckForChangedDepUsers).to.be.true;
189        expect(globalProgram.program != null).to.be.true;
190        expect(compilerOptions.skipPathsInKeyForCompilationSettings).to.be.true;
191
192        resetEtsCheck();
193        expect(needReCheckForChangedDepUsers).to.be.false;
194        expect(globalProgram.program == null).to.be.true;
195        expect(compilerOptions.skipPathsInKeyForCompilationSettings).to.be.true;
196    });
197    mocha.it('1-5: test GetEmitHost of program', function () {
198        const compilerOptions: ts.CompilerOptions = {
199            target: ts.ScriptTarget.ES2021
200        };
201        const fileNames: string[] = ['../testdata/testfiles/testGetEmitHost.ts'];
202        let program: ts.Program = ts.createProgram(fileNames, compilerOptions);
203        expect(program.getEmitHost()).to.not.be.undefined;
204    });
205});
206
207mocha.describe('getMaxFlowDepth', () => {
208    mocha.it('1-1: test should return the default value when maxFlowDepth is undefined', () => {
209        const result = getMaxFlowDepth();
210        expect(result).to.equal(MAX_FLOW_DEPTH_DEFAULT_VALUE);
211    });
212
213    mocha.it('1-2: test should return the default value and log a warning when maxFlowDepth is less than the minimum valid value', () => {
214        const invalidMaxFlowDepth = 1999;
215        projectConfig.projectArkOption = {
216            tscConfig: {
217                maxFlowDepth: invalidMaxFlowDepth
218          }
219        }
220        const result = getMaxFlowDepth();
221        expect(result).to.equal(MAX_FLOW_DEPTH_DEFAULT_VALUE);
222      });
223
224    mocha.it('1-3: test should return the value of maxFlowDepth when it is 2000 within the valid range', () => {
225        const validMaxFlowDepth = 2000;
226        projectConfig.projectArkOption = {
227            tscConfig: {
228                maxFlowDepth: validMaxFlowDepth
229            }
230        }
231        const result = getMaxFlowDepth();
232        expect(result).to.equal(validMaxFlowDepth);
233    });
234
235    mocha.it('1-4: test should return the value of maxFlowDepth when it is 3000 within the valid range', () => {
236        const validMaxFlowDepth = 3000;
237        projectConfig.projectArkOption = {
238            tscConfig: {
239                maxFlowDepth: validMaxFlowDepth
240            }
241        }
242        const result = getMaxFlowDepth();
243        expect(result).to.equal(validMaxFlowDepth);
244    });
245
246    mocha.it('1-5: test should return the value of maxFlowDepth when it is 65535 within the valid range', () => {
247        const validMaxFlowDepth = 65535;
248        projectConfig.projectArkOption = {
249            tscConfig: {
250                maxFlowDepth: validMaxFlowDepth
251            }
252        }
253        const result = getMaxFlowDepth();
254        expect(result).to.equal(validMaxFlowDepth);
255    });
256
257    mocha.it('1-6: test should return the default value and log a warning when maxFlowDepth is greater than the maximum valid value', () => {
258        const invalidMaxFlowDepth = 65536;
259        projectConfig.projectArkOption = {
260            tscConfig: {
261                maxFlowDepth: invalidMaxFlowDepth
262            }
263        }
264        const result = getMaxFlowDepth();
265        expect(result).to.equal(MAX_FLOW_DEPTH_DEFAULT_VALUE);
266    });
267});
268
269mocha.describe('setProgramSourceFiles', () => {
270    mocha.it('1-1: test add new sourcefile to program', () => {
271        const compilerOptions: ts.CompilerOptions = {
272            target: ts.ScriptTarget.ES2021
273        };
274        const fileNames: string[] = ['../testdata/testfiles/testProgramSourceFiles.ts'];
275        let program: ts.Program = ts.createProgram(fileNames, compilerOptions);
276        const fileContent: string = `
277            let a = 1;
278        `;
279        let newSourceFile: ts.SourceFile = ts.createSourceFile('demo.ts', fileContent, ts.ScriptTarget.ES2021, true);
280        expect(program.getSourceFiles().includes(newSourceFile)).to.be.false;
281        expect(program.getSourceFile('demo.ts')).to.be.undefined;
282        const origrinFileSize: number = program.getSourceFiles().length;
283        program.setProgramSourceFiles(newSourceFile);
284        const newFileSize: number = program.getSourceFiles().length;
285        expect(program.getSourceFiles().includes(newSourceFile)).to.be.true;
286        expect(program.getSourceFile('demo.ts')).to.not.be.undefined;
287        expect(origrinFileSize === (newFileSize - 1)).to.be.true;
288    });
289
290    mocha.it('1-2: test should do nothing when adding an existed sourcefile to the program', () => {
291        const compilerOptions: ts.CompilerOptions = {
292            target: ts.ScriptTarget.ES2021
293        };
294        const fileNames: string[] = ['../testdata/testfiles/testProgramSourceFiles.ts'];
295        let program: ts.Program = ts.createProgram(fileNames, compilerOptions);
296        const fileContent1: string = `let a = 1;`;
297        let newSourceFile1: ts.SourceFile = ts.createSourceFile('demo.ts', fileContent1, ts.ScriptTarget.ES2021, true);
298        program.setProgramSourceFiles(newSourceFile1);
299        const origrinFileSize: number = program.getSourceFiles().length;
300        expect(program.getSourceFiles().includes(newSourceFile1)).to.be.true;
301        expect(program.getSourceFile('demo.ts').text).to.be.equal('let a = 1;');
302        const fileContent2: string = `let b = 1;`;
303        let newSourceFile2: ts.SourceFile = ts.createSourceFile('demo.ts', fileContent2, ts.ScriptTarget.ES2021, true);
304        program.setProgramSourceFiles(newSourceFile2);
305        const newFileSize: number = program.getSourceFiles().length;
306        expect(program.getSourceFiles().includes(newSourceFile2)).to.be.false;
307        expect(program.getSourceFile('demo.ts').text).to.be.equal('let a = 1;');
308        expect(origrinFileSize === newFileSize).to.be.true;
309    });
310});
311
312mocha.describe('initProcessingFiles and getProcessingFiles', () => {
313    mocha.it('1-1: test should return undefined when processingDefaultFiles and processingOtherFiles is undefined', () => {
314        const compilerOptions: ts.CompilerOptions = {
315            target: ts.ScriptTarget.ES2021
316        };
317        const fileNames: string[] = ['../testdata/testfiles/testProgramSourceFiles.ts'];
318        let program: ts.Program = ts.createProgram(fileNames, compilerOptions);
319        let processingFiles: ts.SourceFile[] | undefined = program.getProcessingFiles();
320        expect(processingFiles === undefined).to.be.true;
321    });
322
323    mocha.it('1-2: test should return array when processingDefaultFiles and processingOtherFiles is not undefined', () => {
324        const compilerOptions: ts.CompilerOptions = {
325            target: ts.ScriptTarget.ES2021
326        };
327        const fileNames: string[] = ['../testdata/testfiles/testProgramSourceFiles.ts'];
328        let program: ts.Program = ts.createProgram(fileNames, compilerOptions);
329        program.initProcessingFiles();
330        let processingFiles: ts.SourceFile[] | undefined = program.getProcessingFiles();
331        expect(processingFiles === undefined).to.be.false;
332    });
333
334});
335
336mocha.describe('refreshTypeChecker', () => {
337    mocha.it('1-1: test should recreate typeChecker and linterTypeChecker', () => {
338        const compilerOptions: ts.CompilerOptions = {
339            target: ts.ScriptTarget.ES2021
340        };
341        const fileNames: string[] = ['../testdata/testfiles/testProgramSourceFiles.ts'];
342        let program: ts.Program = ts.createProgram(fileNames, compilerOptions);
343        const fileContent: string = `
344            let a = 1;
345            let b = x; // Error!
346        `;
347        let newSourceFile: ts.SourceFile = ts.createSourceFile('demo.ts', fileContent, ts.ScriptTarget.ES2021, true);
348        newSourceFile.originalFileName = newSourceFile.fileName;
349        newSourceFile.resolvedPath = 'demo.ts';
350        newSourceFile.path = 'demo.ts';
351        program.processImportedModules(newSourceFile);
352        program.setProgramSourceFiles(newSourceFile);
353        program.refreshTypeChecker();
354        const diagnostics: readonly ts.Diagnostic[] = program.getSemanticDiagnostics(newSourceFile);
355        expect(diagnostics[0].messageText === `Cannot find name 'x'.`);
356        expect(diagnostics.length === 1).to.be.true;
357    });
358});
359
360mocha.describe('deleteProgramSourceFiles', () => {
361    mocha.it('1-1: test should delete when sourcefiles are exist in program', () => {
362        const compilerOptions: ts.CompilerOptions = {
363            target: ts.ScriptTarget.ES2021
364        };
365        const fileNames: string[] = ['../testdata/testfiles/testProgramSourceFiles.ts'];
366        let program: ts.Program = ts.createProgram(fileNames, compilerOptions);
367        const fileContent1: string = `
368            let a = 1;
369        `;
370        let newSourceFile1: ts.SourceFile = ts.createSourceFile('demo1.ts', fileContent1, ts.ScriptTarget.ES2021, true);
371        program.setProgramSourceFiles(newSourceFile1);
372        expect(program.getSourceFiles().includes(newSourceFile1)).to.be.true;
373        expect(program.getSourceFile('demo1.ts')).to.not.be.undefined;
374        const fileContent2: string = `
375            let b = 1;
376        `;
377        let newSourceFile2: ts.SourceFile = ts.createSourceFile('demo2.ts', fileContent2, ts.ScriptTarget.ES2021, true);
378        program.setProgramSourceFiles(newSourceFile2);
379        expect(program.getSourceFiles().includes(newSourceFile2)).to.be.true;
380        expect(program.getSourceFile('demo2.ts')).to.not.be.undefined;
381        const origrinFileSize: number = program.getSourceFiles().length;
382        const deleteFileNames: string[] = ['demo1.ts', 'demo2.ts'];
383        program.deleteProgramSourceFiles(deleteFileNames);
384        expect(program.getSourceFiles().includes(newSourceFile1)).to.be.false;
385        expect(program.getSourceFile('demo1.ts')).to.be.undefined;
386        expect(program.getSourceFiles().includes(newSourceFile2)).to.be.false;
387        expect(program.getSourceFile('demo2.ts')).to.be.undefined;
388        const newFileSize: number = program.getSourceFiles().length;
389        expect(origrinFileSize === (newFileSize + 2)).to.be.true;
390    });
391
392    mocha.it('1-2: test should do nothing when sourcefiles are not exist in program', () => {
393        const compilerOptions: ts.CompilerOptions = {
394            target: ts.ScriptTarget.ES2021
395        };
396        const fileNames: string[] = ['../testdata/testfiles/testProgramSourceFiles.ts'];
397        let program: ts.Program = ts.createProgram(fileNames, compilerOptions);
398        const fileContent1: string = `
399            let a = 1;
400        `;
401        let newSourceFile1: ts.SourceFile = ts.createSourceFile('demo1.ts', fileContent1, ts.ScriptTarget.ES2021, true);
402        program.setProgramSourceFiles(newSourceFile1);
403        expect(program.getSourceFiles().includes(newSourceFile1)).to.be.true;
404        expect(program.getSourceFile('demo1.ts')).to.not.be.undefined;
405        const fileContent2: string = `
406            let b = 1;
407        `;
408        let newSourceFile2: ts.SourceFile = ts.createSourceFile('demo2.ts', fileContent2, ts.ScriptTarget.ES2021, true);
409        program.setProgramSourceFiles(newSourceFile2);
410        expect(program.getSourceFiles().includes(newSourceFile2)).to.be.true;
411        expect(program.getSourceFile('demo2.ts')).to.not.be.undefined;
412        const origrinFileSize: number = program.getSourceFiles().length;
413        const deleteFileNames: string[] = ['demo3.ts', 'demo4.ts'];
414        program.deleteProgramSourceFiles(deleteFileNames);
415        expect(program.getSourceFiles().includes(newSourceFile1)).to.be.true;
416        expect(program.getSourceFile('demo1.ts')).to.not.be.undefined;
417        expect(program.getSourceFiles().includes(newSourceFile2)).to.be.true;
418        expect(program.getSourceFile('demo2.ts')).to.not.be.undefined;
419        const newFileSize: number = program.getSourceFiles().length;
420        expect(origrinFileSize === newFileSize).to.be.true;
421    });
422});
423
424mocha.describe('optimize createProgram', () => {
425    const testFileName = 'test.txt';
426    const testFileContent = 'Hello, world!';
427    let readFileSyncStub: sinon.SinonStub;
428
429    mocha.beforeEach(() => {
430      fileCache.clear();
431      readFileSyncStub = sinon.stub(fs, 'readFileSync').returns(Buffer.from(testFileContent));
432    });
433
434    mocha.afterEach(() => {
435      sinon.restore();
436    });
437
438    mocha.it('1-1: test should return file content when file exists ', () => {
439      const result = getFileContentWithHash(testFileName);
440      expect(result).to.equal(testFileContent);
441    });
442
443    mocha.it('1-2: test should cache the file content after first read', () => {
444        // First call - should read from file system
445        const firstResult = getFileContentWithHash(testFileName);
446        expect(fileCache.has(testFileName)).to.be.true;
447        expect(fileCache.get(testFileName)).to.equal(testFileContent);
448
449        // Second call - should use cache
450        readFileSyncStub.resetHistory();
451        const secondResult = getFileContentWithHash(testFileName);
452        expect(readFileSyncStub.called).to.be.false;
453        expect(secondResult).to.equal(firstResult);
454    });
455});