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 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 16import {ListUtil} from '../../../src/utils/ListUtil'; 17import {describe, it} from 'mocha'; 18import {assert} from 'chai'; 19import * as fs from 'fs'; 20import sinon from 'sinon'; 21import {TypeUtils} from '../../../src/utils/TypeUtils'; 22import { 23 Label, 24 Scope, 25 ScopeKind, 26 ScopeManager, 27 createLabel, 28 createScopeManager, 29 isClassScope, 30 isEnumScope, 31 isFunctionScope, 32 isGlobalScope, 33 isInterfaceScope, 34 isObjectLiteralScope 35} from '../../../src/utils/ScopeAnalyzer'; 36import { ScriptTarget, SourceFile, createSourceFile, isSourceFile, Symbol, __String, Declaration, JSDocTagInfo, 37 SymbolDisplayPart, SymbolFlags, TypeChecker, LabeledStatement, Identifier, SyntaxKind, 38 isIdentifier, 39 factory, 40 VariableStatement} from 'typescript'; 41 42describe('ScopeAnalyzer ut', function () { 43 let sourceFile: SourceFile; 44 45 before('init parameters', function () { 46 const sourceFileContent = ` 47 //This is a comment 48 class Demo{ 49 constructor(public title: string, public content: string, public mark: number) { 50 this.title = title 51 this.content = content 52 this.mark = mark 53 } 54 } 55 `; 56 57 sourceFile = createSourceFile('sourceFile.ts', sourceFileContent, ScriptTarget.ES2015, true); 58 }); 59 60 describe('unit test for isGlobalScope', function () { 61 it('is Global Scope', function () { 62 let curScope = new Scope('curScope', sourceFile, ScopeKind.GLOBAL); 63 assert.isTrue(isGlobalScope(curScope)); 64 }); 65 66 it('is not Global Scope', function () { 67 let curScope = new Scope('curScope', sourceFile, ScopeKind.MODULE); 68 assert.isFalse(isGlobalScope(curScope)); 69 }); 70 }); 71 72 describe('unit test for isFunctionScope', function () { 73 it('is Function Scope', function () { 74 let curScope = new Scope('curScope', sourceFile, ScopeKind.FUNCTION); 75 assert.isTrue(isFunctionScope(curScope)); 76 }); 77 78 it('is not Function Scope', function () { 79 let curScope = new Scope('curScope', sourceFile, ScopeKind.MODULE); 80 assert.isFalse(isFunctionScope(curScope)); 81 }); 82 }); 83 84 describe('unit test for isClassScope', function () { 85 it('is Class Scope', function () { 86 let curScope = new Scope('curScope', sourceFile, ScopeKind.CLASS); 87 assert.isTrue(isClassScope(curScope)); 88 }); 89 90 it('is not Class Scope', function () { 91 let curScope = new Scope('curScope', sourceFile, ScopeKind.MODULE); 92 assert.isFalse(isClassScope(curScope)); 93 }); 94 }); 95 96 describe('unit test for isInterfaceScope', function () { 97 it('is Interface Scope', function () { 98 let curScope = new Scope('curScope', sourceFile, ScopeKind.INTERFACE); 99 assert.isTrue(isInterfaceScope(curScope)); 100 }); 101 102 it('is not Interface Scope', function () { 103 let curScope = new Scope('curScope', sourceFile, ScopeKind.MODULE); 104 assert.isFalse(isInterfaceScope(curScope)); 105 }); 106 }); 107 108 describe('unit test for isEnumScope', function () { 109 it('is Enum Scope', function () { 110 let curScope = new Scope('curScope', sourceFile, ScopeKind.ENUM); 111 assert.isTrue(isEnumScope(curScope)); 112 }); 113 114 it('is not Enum Scope', function () { 115 let curScope = new Scope('curScope', sourceFile, ScopeKind.MODULE); 116 assert.isFalse(isEnumScope(curScope)); 117 }); 118 }); 119 120 describe('unit test for isObjectLiteralScope', function () { 121 it('is ObjectLiteral Scope', function () { 122 let curScope = new Scope('curScope', sourceFile, ScopeKind.OBJECT_LITERAL); 123 assert.isTrue(isObjectLiteralScope(curScope)); 124 }); 125 126 it('is not ObjectLiteral Scope', function () { 127 let curScope = new Scope('curScope', sourceFile , ScopeKind.MODULE); 128 assert.isFalse(isObjectLiteralScope(curScope)); 129 }); 130 }); 131 132 describe('unit test for Scope', function () { 133 describe('constructor', function () { 134 it('init', function () { 135 let curScope = new Scope('curScope', sourceFile, ScopeKind.GLOBAL); 136 assert.equal(curScope.name, 'curScope'); 137 assert.equal(curScope.kind, ScopeKind.GLOBAL); 138 assert.equal(curScope.block, sourceFile); 139 assert.equal(curScope.parent, undefined); 140 assert.deepEqual(curScope.children, []); 141 assert.deepEqual(curScope.defs, new Set()); 142 assert.deepEqual(curScope.labels, []); 143 assert.deepEqual(curScope.importNames, new Set()); 144 assert.deepEqual(curScope.exportNames, new Set()); 145 assert.deepEqual(curScope.mangledNames, new Set()); 146 assert.equal(curScope.loc, 'curScope'); 147 }); 148 }); 149 150 describe('addChild', function () { 151 it('push', function () { 152 let parentScope = new Scope('parentScope', sourceFile, ScopeKind.FUNCTION); 153 let childScope = new Scope('childScope', sourceFile, ScopeKind.BLOCK, false, parentScope); 154 assert.include(parentScope.children, childScope); 155 }); 156 }); 157 158 describe('addDefinition', function () { 159 it('should add definition symbol to the current scope', function () { 160 let curScope = new Scope('curScope', sourceFile, ScopeKind.GLOBAL); 161 let symbol: Symbol = Object(Symbol('testSymbol')); 162 163 curScope.addDefinition(symbol, false); 164 assert.isTrue(curScope.defs.has(symbol)); 165 }); 166 167 it('should mark symbol as obfuscateAsProperty if required', function () { 168 let curScope = new Scope('curScope', sourceFile, ScopeKind.GLOBAL); 169 let symbol: Symbol = Object(Symbol('testSymbol')); 170 171 curScope.addDefinition(symbol, true); 172 assert.isTrue(Reflect.get(symbol, 'obfuscateAsProperty')); 173 }); 174 }); 175 176 describe('addLabel', function () { 177 it('should add a label to the current scope', function () { 178 let curScope = new Scope('curScope', sourceFile, ScopeKind.GLOBAL); 179 let label: Label = {name: 'testLabel', locInfo: 'locInfo', refs: [], parent: undefined, children: [], scope: curScope}; 180 181 curScope.addLabel(label); 182 assert.include(curScope.labels, label); 183 }); 184 }); 185 186 describe('getSymbolLocation', function () { 187 it('should return the correct location if symbol exists', function () { 188 let curScope = new Scope('curScope', sourceFile, ScopeKind.GLOBAL); 189 let symbol: Symbol = { 190 flags: 0, 191 escapedName: 'testSymbol' as __String, 192 declarations: [], 193 valueDeclaration: undefined, 194 members: undefined, 195 exports: undefined, 196 globalExports: undefined, 197 exportSymbol: undefined, 198 name: 'testSymbol', 199 getFlags: function (): SymbolFlags { 200 throw new Error('Function not implemented.'); 201 }, 202 getEscapedName: function (): __String { 203 throw new Error('Function not implemented.'); 204 }, 205 getName: function (): string { 206 throw new Error('Function not implemented.'); 207 }, 208 getDeclarations: function (): Declaration[] | undefined { 209 throw new Error('Function not implemented.'); 210 }, 211 getDocumentationComment: function (typeChecker: TypeChecker | undefined): SymbolDisplayPart[] { 212 throw new Error('Function not implemented.'); 213 }, 214 getJsDocTags: function (checker?: TypeChecker): JSDocTagInfo[] { 215 throw new Error('Function not implemented.'); 216 } 217 }; 218 curScope.addDefinition(symbol); 219 assert.equal(curScope.getSymbolLocation(symbol), 'testSymbol'); 220 }); 221 222 it('should return an empty string if symbol does not exist', function () { 223 let curScope = new Scope('curScope', sourceFile, ScopeKind.GLOBAL); 224 let symbol: Symbol = Object(Symbol('nonExistentSymbol')); 225 226 assert.equal(curScope.getSymbolLocation(symbol), ''); 227 }); 228 }); 229 230 describe('getLabelLocation', function () { 231 it('should return the correct location if label exists', function () { 232 let curScope = new Scope('curScope', sourceFile, ScopeKind.GLOBAL); 233 let label: Label = {name: 'testLabel', locInfo: 'locInfo', refs: [], parent: undefined, children: [], scope: curScope}; 234 235 curScope.addLabel(label); 236 assert.equal(curScope.getLabelLocation(label), 'testLabel'); 237 }); 238 239 it('should return an empty string if label does not exist', function () { 240 let curScope = new Scope('curScope', sourceFile, ScopeKind.GLOBAL); 241 let label: Label = {name: 'nonExistentLabel', locInfo: 'locInfo', refs: [], parent: undefined, children: [], scope: curScope}; 242 243 assert.equal(curScope.getLabelLocation(label), ''); 244 }); 245 }); 246 }); 247 248 describe('unit test for createLabel', function () { 249 const sourceFileContent = ` 250 label: console.log('This is a labeled statement'); 251 `; 252 sourceFile = createSourceFile('test.ts', sourceFileContent, ScriptTarget.ES2015, true); 253 let labeledStatement: LabeledStatement = sourceFile.statements[0] as LabeledStatement; 254 let curScope = new Scope('curScope', sourceFile, ScopeKind.GLOBAL); 255 256 it('should create a label and add it to the scope', function () { 257 const label = createLabel(labeledStatement, curScope); 258 259 assert.equal(label.name, labeledStatement.label.text); 260 assert.equal(label.locInfo, `$0_${labeledStatement.label.text}`); 261 assert.deepEqual(label.refs, [labeledStatement.label]); 262 assert.equal(label.parent, undefined); 263 assert.deepEqual(label.children, []); 264 assert.equal(label.scope, curScope); 265 assert.include(curScope.labels, label); 266 }); 267 268 it('should create a label with a parent and add it to the parent\'s children', function () { 269 const parentLabel: Label = { 270 name: 'parentLabel', 271 locInfo: 'parentLocInfo', 272 refs: [], 273 parent: undefined, 274 children: [], 275 scope: curScope 276 }; 277 const label = createLabel(labeledStatement, curScope, parentLabel); 278 279 assert.equal(label.name, labeledStatement.label.text); 280 assert.equal(label.locInfo, `$1_${labeledStatement.label.text}`); 281 assert.deepEqual(label.refs, [labeledStatement.label]); 282 assert.equal(label.parent, parentLabel); 283 assert.deepEqual(label.children, []); 284 assert.equal(label.scope, curScope); 285 assert.include(curScope.labels, label); 286 assert.include(parentLabel.children, label); 287 }); 288 }); 289 290 describe('unit test for createScopeManager', function () { 291 let scopeManager: ScopeManager; 292 let sourceFile: SourceFile; 293 let checker: TypeChecker; 294 295 function InitScopeManager(newFilePath: string): void { 296 const fileContent = fs.readFileSync(newFilePath, 'utf8'); 297 sourceFile = createSourceFile(newFilePath, fileContent, ScriptTarget.ES2015, true); 298 checker = TypeUtils.createChecker(sourceFile); 299 scopeManager = createScopeManager(); 300 scopeManager.analyze(sourceFile, checker, false); 301 } 302 303 describe('analyze', function () { 304 describe('analyzeModule', function () { 305 let filePath = 'test/ut/utils/ScopeAnalyzer/analyzeModule.ts'; 306 InitScopeManager(filePath); 307 it('getReservedNames', function () { 308 const reservedNames = scopeManager.getReservedNames(); 309 assert.strictEqual(reservedNames.size === 0, true); 310 }); 311 it('getRootScope', function () { 312 const rootScope = scopeManager.getRootScope(); 313 assert.strictEqual(rootScope.fileExportNames?.size !== 0, true); 314 }); 315 describe('getScopeOfNode', function () { 316 it('node is not identifier', function () { 317 const node: SourceFile = sourceFile; 318 const scope = scopeManager.getScopeOfNode(node); 319 assert.strictEqual(scope, undefined); 320 }); 321 322 it('node with no symbol', function () { 323 const node: Identifier = factory.createIdentifier('noSymbolIdentifier'); 324 const scope = scopeManager.getScopeOfNode(node); 325 assert.strictEqual(scope, undefined); 326 }); 327 328 it('get the scope of node', function () { 329 const classDeclaration = sourceFile.statements.find(node => node && node.kind === SyntaxKind.ClassDeclaration); 330 if (!classDeclaration) throw new Error('ClassDeclaration not found'); 331 const node: Identifier = (classDeclaration as any)?.name; 332 const scope = scopeManager.getScopeOfNode(node); 333 assert.strictEqual(scope?.name, ''); 334 }); 335 336 it('get no scope of node', function () { 337 const anotherSourceFile = createSourceFile('another.ts', 'let y = 2;', ScriptTarget.ES2015, true); 338 const node = (anotherSourceFile.statements[0] as VariableStatement).declarationList.declarations[0].name; 339 const scope = scopeManager.getScopeOfNode(node); 340 assert.strictEqual(scope, undefined); 341 }); 342 }); 343 }); 344 345 describe('analyzeFunctionLike', function () { 346 let filePath = 'test/ut/utils/ScopeAnalyzer/analyzeFunctionLike.ts'; 347 InitScopeManager(filePath); 348 it('getReservedNames', function () { 349 const reservedNames = scopeManager.getReservedNames(); 350 assert.strictEqual(reservedNames.size === 0, true); 351 }); 352 it('getRootScope', function () { 353 const rootScope = scopeManager.getRootScope(); 354 assert.strictEqual(rootScope.fileExportNames?.size !== 0, true); 355 }); 356 describe('getScopeOfNode', function () { 357 it('node is not identifier', function () { 358 const node: SourceFile = sourceFile; 359 const scope = scopeManager.getScopeOfNode(node); 360 assert.strictEqual(scope, undefined); 361 }); 362 363 it('node with no symbol', function () { 364 const node: Identifier = factory.createIdentifier('noSymbolIdentifier'); 365 const scope = scopeManager.getScopeOfNode(node); 366 assert.strictEqual(scope, undefined); 367 }); 368 369 it('get the scope of node', function () { 370 const functionDeclaration = sourceFile.statements.find(node => node && node.kind === SyntaxKind.FunctionDeclaration); 371 if (!functionDeclaration) throw new Error('FunctionDeclaration not found'); 372 const node: Identifier = (functionDeclaration as any)?.name; 373 const scope = scopeManager.getScopeOfNode(node); 374 assert.strictEqual(scope?.name, ''); 375 }); 376 377 it('get no scope of node', function () { 378 const anotherSourceFile = createSourceFile('another.ts', 'let y = 2;', ScriptTarget.ES2015, true); 379 const node = (anotherSourceFile.statements[0] as VariableStatement).declarationList.declarations[0].name; 380 const scope = scopeManager.getScopeOfNode(node); 381 assert.strictEqual(scope, undefined); 382 }); 383 }); 384 }); 385 386 describe('analyzeExportNames', function () { 387 let filePath = 'test/ut/utils/ScopeAnalyzer/analyzeExportNames.ts'; 388 InitScopeManager(filePath); 389 it('getReservedNames', function () { 390 const reservedNames = scopeManager.getReservedNames(); 391 assert.strictEqual(reservedNames.size === 0, true); 392 }); 393 it('getRootScope', function () { 394 const rootScope = scopeManager.getRootScope(); 395 assert.strictEqual(rootScope.fileExportNames?.size !== 0, true); 396 }); 397 describe('getScopeOfNode', function () { 398 it('node is not identifier', function () { 399 const node: SourceFile = sourceFile; 400 const scope = scopeManager.getScopeOfNode(node); 401 assert.strictEqual(scope, undefined); 402 }); 403 404 it('node with no symbol', function () { 405 const node: Identifier = factory.createIdentifier('noSymbolIdentifier'); 406 const scope = scopeManager.getScopeOfNode(node); 407 assert.strictEqual(scope, undefined); 408 }); 409 410 it('get the scope of node', function () { 411 const exportDeclaration = sourceFile.statements.find(node => node && node.kind === SyntaxKind.ExportDeclaration); 412 if (!exportDeclaration) throw new Error('ExportDeclaration not found'); 413 414 const exportClause = (exportDeclaration as any).exportClause; 415 if (!exportClause || !exportClause.elements) throw new Error('ExportSpecifier not found'); 416 417 const exportSpecifier = exportClause.elements[0]; 418 const node: Identifier = exportSpecifier.name; 419 420 const scope = scopeManager.getScopeOfNode(node); 421 assert.strictEqual(scope?.name, ''); 422 }); 423 424 it('get no scope of node', function () { 425 const anotherSourceFile = createSourceFile('another.ts', 'let y = 2;', ScriptTarget.ES2015, true); 426 const node = (anotherSourceFile.statements[0] as VariableStatement).declarationList.declarations[0].name; 427 const scope = scopeManager.getScopeOfNode(node); 428 assert.strictEqual(scope, undefined); 429 }); 430 }); 431 }); 432 433 describe('analyzeImportEqualsDeclaration', function () { 434 let filePath = 'test/ut/utils/ScopeAnalyzer/analyzeImportEqualsDeclaration.ts'; 435 const fileContent = fs.readFileSync(filePath, 'utf8'); 436 let sourceFile = createSourceFile(filePath, fileContent, ScriptTarget.ES2015, true); 437 let checker = TypeUtils.createChecker(sourceFile); 438 let scopeManager = createScopeManager(); 439 scopeManager.analyze(sourceFile, checker, false); 440 it('getReservedNames', function () { 441 const reservedNames = scopeManager.getReservedNames(); 442 assert.strictEqual(reservedNames.size === 0, true); 443 }); 444 it('getRootScope', function () { 445 const rootScope = scopeManager.getRootScope(); 446 assert.strictEqual(rootScope.fileExportNames?.size === 2, true); 447 assert.strictEqual(rootScope.fileExportNames?.has("a"), true); 448 assert.strictEqual(rootScope.fileExportNames?.has("b"), true); 449 }); 450 describe('getScopeOfNode', function () { 451 it('node is not identifier', function () { 452 const node: SourceFile = sourceFile; 453 const scope = scopeManager.getScopeOfNode(node); 454 assert.strictEqual(scope, undefined); 455 }); 456 457 it('node with no symbol', function () { 458 const node: Identifier = factory.createIdentifier('noSymbolIdentifier'); 459 const scope = scopeManager.getScopeOfNode(node); 460 assert.strictEqual(scope, undefined); 461 }); 462 463 it('get the scope of node', function () { 464 const moduleDeclaration = sourceFile.statements.find(node => node && node.kind === SyntaxKind.ModuleDeclaration); 465 if (!moduleDeclaration) throw new Error('ModuleDeclaration not found'); 466 const moduleBlock = (moduleDeclaration as any).body; 467 if (!moduleBlock) throw new Error('ModuleBlock not found'); 468 const importEqualsDeclaration = moduleBlock.statements.find(node => node && node.kind === SyntaxKind.ImportEqualsDeclaration); 469 if (!importEqualsDeclaration) throw new Error('ImportEqualsDeclaration not found'); 470 const node: Identifier = (importEqualsDeclaration as any)?.name; 471 const scope = scopeManager.getScopeOfNode(node); 472 assert.strictEqual(scope?.name, 'ns111'); 473 }); 474 475 it('get no scope of node', function () { 476 const anotherSourceFile = createSourceFile('another.ts', 'let y = 2;', ScriptTarget.ES2015, true); 477 const node = (anotherSourceFile.statements[0] as VariableStatement).declarationList.declarations[0].name; 478 const scope = scopeManager.getScopeOfNode(node); 479 assert.strictEqual(scope, undefined); 480 }); 481 }); 482 }); 483 }); 484 }); 485}); 486 487 488