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