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});