From c5fb13004ecf63367f2656b409922f7c77074caa Mon Sep 17 00:00:00 2001 From: wuhailong Date: Wed, 18 Jun 2025 17:43:57 +0800 Subject: [PATCH] add import path expand Issue: #ICGX8X Signed-off-by: wuhailong Change-Id: Iee96c73a856412745693f9ebd936b50332fc8bbb --- .../ets_ui/rollup-plugin-ets-typescript.ts | 3 + compiler/src/import_path_expand.ts | 344 ++++++++++++++++ .../common/import_path_expand.test.ts | 377 ++++++++++++++++++ 3 files changed, 724 insertions(+) create mode 100644 compiler/src/import_path_expand.ts create mode 100644 compiler/test/ark_compiler_ut/common/import_path_expand.test.ts diff --git a/compiler/src/fast_build/ets_ui/rollup-plugin-ets-typescript.ts b/compiler/src/fast_build/ets_ui/rollup-plugin-ets-typescript.ts index d3a4238c7..f4cf60e59 100644 --- a/compiler/src/fast_build/ets_ui/rollup-plugin-ets-typescript.ts +++ b/compiler/src/fast_build/ets_ui/rollup-plugin-ets-typescript.ts @@ -117,6 +117,7 @@ import { ARKUI_SUBSYSTEM_CODE } from '../../../lib/hvigor_error_code/hvigor_erro import { ProjectCollections } from 'arkguard'; import parseIntent from '../../userIntents_parser/parseUserIntents'; import { concatenateEtsOptions, getExternalComponentPaths } from '../../external_component_map'; +import { expandAllImportPaths } from '../../import_path_expand'; let switchTsAst: boolean = true; @@ -501,6 +502,7 @@ async function transform(code: string, id: string) { { before: [ processUISyntax(null, false, eventEmit, id, this.share, metaInfo), + expandAllImportPaths(tsProgram.getTypeChecker(), this), processKitImport(id, metaInfo, eventEmit, true, lazyImportOptions), collectReservedNameForObf(this.share.arkProjectConfig?.obfuscationMergedObConfig, shouldETSOrTSFileTransformToJSWithoutRemove(id, projectConfig, metaInfo)) @@ -518,6 +520,7 @@ async function transform(code: string, id: string) { transformResult = ts.transformNodes(emitResolver, tsProgram.getEmitHost?.(), ts.factory, tsProgram.getCompilerOptions(), [targetSourceFile], [processUISyntax(null, false, eventTransformNodes, id, this.share, metaInfo), + expandAllImportPaths(tsProgram.getTypeChecker(), this), processKitImport(id, metaInfo, eventTransformNodes, false, lazyImportOptions), collectReservedNameForObf(this.share.arkProjectConfig?.obfuscationMergedObConfig, shouldETSOrTSFileTransformToJSWithoutRemove(id, projectConfig, metaInfo))], false); diff --git a/compiler/src/import_path_expand.ts b/compiler/src/import_path_expand.ts new file mode 100644 index 000000000..d869d8172 --- /dev/null +++ b/compiler/src/import_path_expand.ts @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import * as ts from 'typescript'; +import path from 'path'; + +import { toUnixPath } from './utils'; +import { + projectConfig as mainProjectConfig, + sdkConfigPrefix +} from '../main'; +import { compilerOptions } from './ets_checker'; + +interface ImportInfo { + defaultImport?: { + name: ts.Identifier; + isTypeOnly: boolean; + }; + namedImports: ts.ImportSpecifier[]; +} + +interface SymbolInfo { + filePath: string, + isDefault: boolean +} + +interface SeparatedImportInfos { + typeOnly: Map; + value: Map; +}; + +export function expandAllImportPaths(checker: ts.TypeChecker, rollupObejct: Object): Function { + const expandImportPath: Object = rollupObejct.share.projectConfig?.expandImportPath; + const modulePathMap: Map = mainProjectConfig.modulePathMap; + if (!(expandImportPath && Object.entries(expandImportPath).length !== 0) || !expandImportPath.enable) { + return () => sourceFile => sourceFile; + } + const exclude: string[] = expandImportPath?.exclude ? expandImportPath?.exclude : []; + return (context: ts.TransformationContext) => { + // @ts-ignore + const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult => { + if (ts.isImportDeclaration(node)) { + const result: ts.ImportDeclaration[] = transformImportDecl(node, checker, exclude, modulePathMap); + return result.length > 0 ? result : node; + } + return node; + }; + + return (node: ts.SourceFile): ts.SourceFile => { + return ts.visitEachChild(node, visitor, context); + }; + }; +} + +function transformImportDecl(node: ts.ImportDeclaration, checker: ts.TypeChecker, exclude: string[], + modulePathMap: Map): ts.ImportDeclaration[] { + const moduleSpecifier: ts.StringLiteral = node.moduleSpecifier as ts.StringLiteral; + const moduleRequest: string = moduleSpecifier.text; + const REG_SYSTEM_MODULE: RegExp = new RegExp(`@(${sdkConfigPrefix})\\.(\\S+)`); + const REG_LIB_SO: RegExp = /lib(\S+)\.so/; + if (moduleRequest.startsWith('.') || REG_SYSTEM_MODULE.test(moduleRequest.trim()) || + REG_LIB_SO.test(moduleRequest.trim()) || exclude.indexOf(moduleRequest) !== -1) { + return []; + } + const importClause = node.importClause; + if (!importClause) { + return []; + } + if ((importClause.namedBindings && ts.isNamespaceImport(importClause.namedBindings)) || importClause.isTypeOnly) { + return []; + } + + const importMap = new Map(); + // default import + processDefaultImport(checker, importMap, importClause, moduleSpecifier); + // named imports + processNamedImport(checker, importMap, importClause, moduleSpecifier); + if (importMap.size === 0) { + return []; + } + const { typeOnly, value }: SeparatedImportInfos = separateImportInfos(importMap); + const results: ts.ImportDeclaration[] = []; + + for (const [_, info] of typeOnly.entries()) { + results.push(createImportDeclarationFromInfo(info, node, moduleRequest)); + } + + for (const [filePath, info] of value.entries()) { + const realModuleRequest = genModuleRequst(filePath, moduleSpecifier.text, modulePathMap); + if (!realModuleRequest) { + continue; + } + results.push(createImportDeclarationFromInfo(info, node, realModuleRequest)); + } + + return results; +} + + +function processDefaultImport(checker: ts.TypeChecker, importMap: Map, importClause: ts.ImportClause, + moduleSpecifier: ts.StringLiteral): void { + if (importClause.name) { + const resolved = getRealFilePath(checker, moduleSpecifier, 'default'); + if (!resolved) { + return; + } + const filePath: string = resolved.filePath; + + if (!importMap.has(filePath)) { + importMap.set(filePath, { namedImports: [] }); + } + if (resolved.isDefault) { + importMap.get(filePath)!.defaultImport = { + name: importClause.name, + isTypeOnly: importClause.isTypeOnly + }; + } else { + // fallback: was re-exported as default, but originally named + importMap.get(filePath)!.namedImports.push( + ts.factory.createImportSpecifier(importClause.isTypeOnly, + ts.factory.createIdentifier(importClause.name.text), importClause.name) + ); + } + } +} + +function processNamedImport(checker: ts.TypeChecker, importMap: Map, importClause: ts.ImportClause, + moduleSpecifier: ts.StringLiteral): void { + if (importClause.namedBindings && ts.isNamedImports(importClause.namedBindings)) { + for (const element of importClause.namedBindings.elements) { + const name: string = element.propertyName?.text || element.name.text; + const resolved: SymbolInfo | undefined = getRealFilePath(checker, moduleSpecifier, name); + if (!resolved) { + continue; + } + + const filePath: string = resolved.filePath; + + if (!importMap.has(filePath)) { + importMap.set(filePath, { namedImports: [] }); + } + if (resolved.isDefault) { + importMap.get(filePath)!.defaultImport = { + name: element.name, + isTypeOnly: element.isTypeOnly + }; + } else { + importMap.get(filePath)!.namedImports.push( + ts.factory.createImportSpecifier(element.isTypeOnly, element.propertyName, element.name)); + } + } + } +} + +function separateImportInfos(importInfos: Map): SeparatedImportInfos { + const typeOnly = new Map(); + const value = new Map(); + + for (const [filePath, info] of importInfos.entries()) { + const typeInfo: ImportInfo = { namedImports: [] }; + const valueInfo: ImportInfo = { namedImports: [] }; + + if (info.defaultImport) { + if (info.defaultImport.isTypeOnly) { + typeInfo.defaultImport = info.defaultImport; + } else { + valueInfo.defaultImport = info.defaultImport; + } + } + + for (const spec of info.namedImports) { + if (spec.isTypeOnly) { + typeInfo.namedImports.push(spec); + } else { + valueInfo.namedImports.push(spec); + } + } + + if (typeInfo.defaultImport || typeInfo.namedImports.length > 0) { + typeOnly.set(filePath, typeInfo); + } + if (valueInfo.defaultImport || valueInfo.namedImports.length > 0) { + value.set(filePath, valueInfo); + } + } + + return { typeOnly, value }; +} + +function createImportDeclarationFromInfo(importInfo: ImportInfo, originalNode: ts.ImportDeclaration, + modulePath: string): ts.ImportDeclaration { + const importClause = ts.factory.createImportClause(false, importInfo.defaultImport?.name, + importInfo.namedImports.length > 0 ? ts.factory.createNamedImports(importInfo.namedImports) : undefined); + + // @ts-ignore + importClause.isLazy = originalNode.importClause?.isLazy; + + return ts.factory.updateImportDeclaration(originalNode, originalNode.modifiers, importClause, + ts.factory.createStringLiteral(modulePath), originalNode.assertClause); +} + +function genModuleRequst(filePath: string, moduleRequest: string, modulePathMap: Map): string { + const unixFilePath: string = toUnixPath(filePath); + for (const [_, moduleRootPath] of Object.entries(modulePathMap)) { + const unixModuleRootPath: string = toUnixPath(moduleRootPath); + if (unixFilePath.startsWith(unixModuleRootPath + '/')) { + return unixFilePath.replace(unixModuleRootPath, moduleRequest).replace(/\.(d\.ets|ets|d\.ts|ts|js)$/, ''); + } + } + return ''; +} + +function getRealFilePath(checker: ts.TypeChecker, moduleSpecifier: ts.StringLiteral, + exportName: string): SymbolInfo | undefined { + const symbol: ts.Symbol | undefined = resolveImportedSymbol(checker, moduleSpecifier, exportName); + if (!symbol) { + return undefined; + } + + const finalSymbol: ts.Symbol = resolveAliasedSymbol(symbol, checker); + if (!finalSymbol || !finalSymbol.declarations || finalSymbol.declarations.length === 0) { + return undefined; + } + + const decl: ts.Declaration = finalSymbol.declarations?.[0]; + const filePath = path.normalize(decl.getSourceFile().fileName); + const isDefault: boolean = isActuallyDefaultExport(finalSymbol); + + return { filePath, isDefault }; +} + +function resolveImportedSymbol(checker: ts.TypeChecker, moduleSpecifier: ts.StringLiteral, + exportName: string): ts.Symbol | undefined { + const moduleSymbol: ts.Symbol = checker.getSymbolAtLocation(moduleSpecifier); + if (!moduleSymbol) { + return undefined; + } + + const exports: ts.Symbol[] = checker.getExportsOfModule(moduleSymbol); + if (!exports) { + return undefined; + } + + for (const sym of exports) { + const name: string = sym.escapedName.toString(); + if (name === exportName) { + return sym; + } + } + return undefined; +} + +function resolveAliasedSymbol(symbol: ts.Symbol, checker: ts.TypeChecker): ts.Symbol { + const visited = new Set(); + let finalSymbol: ts.Symbol | undefined = symbol; + + while (finalSymbol && finalSymbol.flags & ts.SymbolFlags.Alias) { + if (visited.has(finalSymbol)) { + break; + } + visited.add(finalSymbol); + const aliased = checker.getAliasedSymbol(finalSymbol); + if (!aliased) { + break; + } + finalSymbol = aliased; + } + + // fallback: skip symbols with no declarations + while (finalSymbol && (!finalSymbol.declarations || finalSymbol.declarations.length === 0)) { + if (visited.has(finalSymbol)) { + break; + } + visited.add(finalSymbol); + const aliased = checker.getAliasedSymbol(finalSymbol); + if (!aliased || aliased === finalSymbol) { + break; + } + finalSymbol = aliased; + } + + return finalSymbol; +} + +function isActuallyDefaultExport(symbol: ts.Symbol): boolean { + const decl = symbol.valueDeclaration ?? symbol.declarations?.[0]; + if (!decl) { + return false; + } + const sourceFile = decl.getSourceFile(); + for (const stmt of sourceFile.statements) { + if (isDefaultExportAssignment(stmt, symbol)) { + return true; + } + if (isNamedDefaultExport(stmt, symbol)) { + return true; + } + if (isNamedDefaultDecl(stmt, decl, symbol)) { + return true; + } + } + return false; +} + +function isDefaultExportAssignment(stmt: ts.Statement, symbol: ts.Symbol): boolean { + return ts.isExportAssignment(stmt) && + !stmt.isExportEquals && + ts.isIdentifier(stmt.expression) && + stmt.expression.text === symbol.name; +} + +function isNamedDefaultExport(stmt: ts.Statement, symbol: ts.Symbol): boolean { + if (!ts.isExportDeclaration(stmt) || !stmt.exportClause || !ts.isNamedExports(stmt.exportClause)) { + return false; + } + for (const specifier of stmt.exportClause.elements) { + if (specifier.name.text === 'default' && specifier.propertyName?.text === symbol.name) { + return true; + } + if (specifier.name.text === 'default' && !specifier.propertyName) { + return false; + } + } + return false; +} + +function isNamedDefaultDecl(stmt: ts.Statement, decl: ts.Declaration, symbol: ts.Symbol): boolean { + return symbol.name === 'default' && + (ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && + stmt.modifiers?.some(m => m.kind === ts.SyntaxKind.DefaultKeyword) && + stmt.name?.text === decl.name?.getText(); +} \ No newline at end of file diff --git a/compiler/test/ark_compiler_ut/common/import_path_expand.test.ts b/compiler/test/ark_compiler_ut/common/import_path_expand.test.ts new file mode 100644 index 000000000..b6a90d836 --- /dev/null +++ b/compiler/test/ark_compiler_ut/common/import_path_expand.test.ts @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use rollupObject file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import mocha from 'mocha'; +import { expect } from 'chai'; +import path from 'path'; +import * as ts from 'typescript'; + +import { expandAllImportPaths } from '../../../lib/import_path_expand'; +import { projectConfig } from '../../../main'; + +const compilerOptions = ts.readConfigFile( + path.resolve(__dirname, '../../../tsconfig.json'), ts.sys.readFile).config.compilerOptions; +compilerOptions['moduleResolution'] = 'nodenext'; +compilerOptions['module'] = 'es2020'; + +function createMultiSymbolProgram(testContent: string, symbolToFileMap: Record, + indexContent: string, rootDir: string = '/TestProject'): { program: ts.Program; testSourceFile: ts.SourceFile } { + const moduleRootPath: string = `${rootDir}/testPkg`; + const fileMap = new Map(); + + for (const [relativePath, code] of Object.entries(symbolToFileMap)) { + const fullFilePath = `${moduleRootPath}/${relativePath}`; + const sourceFile = ts.createSourceFile(fullFilePath, code, ts.ScriptTarget.ES2020, true); + Object.defineProperty(sourceFile, 'fileName', { value: fullFilePath }); + fileMap.set(fullFilePath, sourceFile); + } + + const testFileName = `${rootDir}/test/testFile.ets`; + const testSourceFile = ts.createSourceFile(testFileName, testContent, ts.ScriptTarget.ES2020, true); + Object.defineProperty(testSourceFile, 'fileName', { value: testFileName }); + + const compilerHost = ts.createCompilerHost({ target: ts.ScriptTarget.ES2020 }); + const allSourceFiles = new Map([...fileMap.entries(), [testFileName, testSourceFile]]); + + compilerHost.getSourceFile = (fileName) => allSourceFiles.get(fileName); + + compilerHost.resolveModuleNames = (moduleNames: string[], containingFile: string) => { + return moduleNames.map(name => { + if (name === 'testPkg') { + return { + resolvedFileName: `${moduleRootPath}/Index.ts`, + isExternalLibraryImport: false + }; + } + const candidate = path.resolve(path.dirname(containingFile), name); + const resolved = [...fileMap.keys()].find(filePath => + filePath === candidate || filePath === candidate + '.ts'); + + if (resolved) { + return { + resolvedFileName: resolved, + isExternalLibraryImport: false + }; + } + return undefined; + }); + }; + + const indexFilePath = `${moduleRootPath}/Index.ts`; + const indexSourceFile = ts.createSourceFile(indexFilePath, indexContent, ts.ScriptTarget.ESNext, true); + Object.defineProperty(indexSourceFile, 'fileName', { value: indexFilePath }); + allSourceFiles.set(indexFilePath, indexSourceFile); + + return { + program: ts.createProgram({ + rootNames: [...allSourceFiles.keys()], + options: compilerOptions, + host: compilerHost, + }), + testSourceFile + }; +} + +const CASE_1_1_TEST = `import def, { A as AA, B, type C } from 'testPkg'; +def(); +let a: AA = new AA(); +B(); +const c: C = 'c';`; +const CASE_1_1_FILES = { + 'testDir/test.ts': ` + function def() {} + export class A {} + export function B() {} + export type C = string; + export default def; + ` +}; +const CASE_1_1_INDEX = ` +import def from './testDir/test'; +export { A, B, type C } from './testDir/test'; +export default def; +`; +const EXPECT_1_1 = `import { type C } from "testPkg"; +import def, { A as AA, B } from "testPkg/testDir/test"; +def(); +let a: AA = new AA(); +B(); +const c: C = 'c'; +`; + + +const CASE_1_2_TEST = `import { A, B, C } from 'testPkg'; +A(); +B(); +C();`; +const CASE_1_2_FILES = { + 'a.ts': `export function A() {}`, + 'b.ts': `export function B() {}`, + 'c.ts': `export function C() {}` +}; +const CASE_1_2_INDEX = ` +export { A } from './a'; +export { B } from './b'; +export { C } from './c'; +`; +const EXPECT_1_2 = `import { A } from "testPkg/a"; +import { B } from "testPkg/b"; +import { C } from "testPkg/c"; +A(); +B(); +C(); +`; + +const CASE_1_3_TEST = `import { x, y as y1 } from 'testPkg'; +console.log(x, y1);`; +const CASE_1_3_FILES = { + 'final.ts': `const x = 1; export const y = 2; export default x;` +}; +const CASE_1_3_INDEX = `import x, { y } from './final'; +export { x, y };`; +const EXPECT_1_3 = `import x, { y as y1 } from "testPkg/final"; +console.log(x, y1); +`; + +const CASE_1_4_TEST = `import { y as y1 } from 'excludePkg'; +import { x } from 'testPkg'; +console.log(x, y1);`; +const CASE_1_4_FILES = { + 'final.ts': `const x = 1; export default x;` +}; +const CASE_1_4_INDEX = `import x from './final'; +export { x };`; +const EXPECT_1_4 = `import { y as y1 } from 'excludePkg'; +import x from "testPkg/final"; +console.log(x, y1); +`; + +const CASE_1_5_TEST = `import fallback from 'testPkg'; +console.log(fallback);`; +const CASE_1_5_FILES = { + 'test.ts': `export const fallback = 1;` +}; +const CASE_1_5_INDEX = `export { fallback as default } from './test';`; +const EXPECT_1_5 = `import { fallback as fallback } from "testPkg/test"; +console.log(fallback); +`; + +const CASE_1_6_TEST = `import type { T1 } from 'testPkg'; +const a: T1 = 'a';`; +const CASE_1_6_FILES = { + 'test.ts': `export type T1 = string;` +}; +const CASE_1_6_INDEX = `export type { T1 } from './test';`; +const EXPECT_1_6 = `import type { T1 } from 'testPkg'; +const a: T1 = 'a'; +`; + +const CASE_2_1_TEST = `import { x } from 'testPkg'; +console.log(x);`; +const CASE_2_1_FILES = { + 'test.ts': `export const x = 1;` +}; +const CASE_2_1_INDEX = `export { x } from './test';`; +const EXPECT_2_1 = `import { x } from 'testPkg'; +console.log(x); +`; + +const CASE_2_2_TEST = `import { def } from 'testPkg'; +def();`; +const CASE_2_2_FILES = { + 'test.ts': `export default function def() {};` +}; +const CASE_2_2_INDEX = ` import def from './test'; +export { def };`; +const EXPECT_2_2 = `import def from "testPkg/test"; +def(); +`; + +const CASE_3_1_TEST = `import * as test from 'testPkg'; +test.fn();`; +const CASE_3_1_FILES = { + 'test.ts': `export function fn() {}` +}; +const CASE_3_1_INDEX = `export * from './test';`; +const EXPECT_3_1 = `import * as test from 'testPkg'; +test.fn(); +`; + +const CASE_3_2_IMPORT = `import 'testPkg';`; +const CASE_3_2_FILES = { + 'test.ts': `console.log('loaded');` +}; +const CASE_3_2_INDEX = `export * from './test';`; +const EXPECT_3_2 = `import 'testPkg'; +`; + +const baseConfig = { enable: true, exclude: [] }; +const rollupObejct = { share: { projectConfig: { expandImportPath: baseConfig } } }; + +mocha.describe('test import_path_expand file api', () => { + mocha.it('1-1: split default + named + type imports', () => { + const tmpModulePathMap = projectConfig.modulePathMap; + projectConfig.modulePathMap = { + 'testPkg': '/TestProject/testPkg' + }; + const { program, testSourceFile } = createMultiSymbolProgram(CASE_1_1_TEST, CASE_1_1_FILES, CASE_1_1_INDEX); + const transformed = ts.transform(testSourceFile, [expandAllImportPaths(program.getTypeChecker(), rollupObejct)], + program.getCompilerOptions()).transformed[0]; + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result = printer.printFile(transformed); + + expect(result === EXPECT_1_1).to.be.true; + projectConfig.modulePathMap = tmpModulePathMap; + }); + + mocha.it('1-2: resolve multi-symbol to different files', () => { + const tmpModulePathMap = projectConfig.modulePathMap; + projectConfig.modulePathMap = { + 'testPkg': '/TestProject/testPkg' + }; + const { program, testSourceFile } = createMultiSymbolProgram(CASE_1_2_TEST, CASE_1_2_FILES, CASE_1_2_INDEX); + const transformed = ts.transform(testSourceFile, [expandAllImportPaths(program.getTypeChecker(), rollupObejct)], + program.getCompilerOptions()).transformed[0]; + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result = printer.printFile(transformed); + + expect(result === EXPECT_1_2).to.be.true; + projectConfig.modulePathMap = tmpModulePathMap; + }); + + mocha.it('1-3: resolve re-export chain to final file', () => { + const tmpModulePathMap = projectConfig.modulePathMap; + projectConfig.modulePathMap = { + 'testPkg': '/TestProject/testPkg' + }; + const { program, testSourceFile } = createMultiSymbolProgram(CASE_1_3_TEST, CASE_1_3_FILES, CASE_1_3_INDEX); + const transformed = ts.transform(testSourceFile, [expandAllImportPaths(program.getTypeChecker(), rollupObejct)], + program.getCompilerOptions()).transformed[0]; + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result = printer.printFile(transformed); + + expect(result === EXPECT_1_3).to.be.true; + projectConfig.modulePathMap = tmpModulePathMap; + }); + + mocha.it('1-4: exclude import path from transform', () => { + const tmpModulePathMap = projectConfig.modulePathMap; + projectConfig.modulePathMap = { + 'testPkg': '/TestProject/testPkg' + }; + const { program, testSourceFile } = createMultiSymbolProgram(CASE_1_4_TEST, CASE_1_4_FILES, CASE_1_4_INDEX); + rollupObejct.share.projectConfig.expandImportPath.exclude = ['excludePkg']; + const transformed = ts.transform(testSourceFile, [expandAllImportPaths(program.getTypeChecker(), rollupObejct)], + program.getCompilerOptions()).transformed[0]; + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result = printer.printFile(transformed); + + expect(result === EXPECT_1_4).to.be.true; + projectConfig.modulePathMap = tmpModulePathMap; + rollupObejct.share.projectConfig.expandImportPath.exclude = []; + }); + + mocha.it('1-5: fallback default import to named', () => { + const tmpModulePathMap = projectConfig.modulePathMap; + projectConfig.modulePathMap = { + 'testPkg': '/TestProject/testPkg' + }; + const { program, testSourceFile } = createMultiSymbolProgram(CASE_1_5_TEST, CASE_1_5_FILES, CASE_1_5_INDEX); + const transformed = ts.transform(testSourceFile, [expandAllImportPaths(program.getTypeChecker(), rollupObejct)], + program.getCompilerOptions()).transformed[0]; + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result = printer.printFile(transformed); + + expect(result === EXPECT_1_5).to.be.true; + projectConfig.modulePathMap = tmpModulePathMap; + }); + + mocha.it('1-6: transform type-only named import', () => { + const tmpModulePathMap = projectConfig.modulePathMap; + projectConfig.modulePathMap = { + 'testPkg': '/TestProject/testPkg' + }; + const { program, testSourceFile } = createMultiSymbolProgram(CASE_1_6_TEST, CASE_1_6_FILES, CASE_1_6_INDEX); + const transformed = ts.transform(testSourceFile, [expandAllImportPaths(program.getTypeChecker(), rollupObejct)], + program.getCompilerOptions()).transformed[0]; + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result = printer.printFile(transformed); + + expect(result === EXPECT_1_6).to.be.true; + projectConfig.modulePathMap = tmpModulePathMap; + }); + + mocha.it('2-1: should not transform when config is disabled', () => { + const tmpModulePathMap = projectConfig.modulePathMap; + projectConfig.modulePathMap = { + 'testPkg': '/TestProject/testPkg' + }; + rollupObejct.share.projectConfig.expandImportPath.enable = false; + const { program, testSourceFile } = createMultiSymbolProgram(CASE_2_1_TEST, CASE_2_1_FILES, CASE_2_1_INDEX); + const transformed = ts.transform(testSourceFile, [expandAllImportPaths(program.getTypeChecker(), rollupObejct)], + program.getCompilerOptions()).transformed[0]; + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result = printer.printFile(transformed); + + expect(result === EXPECT_2_1).to.be.true; + projectConfig.modulePathMap = tmpModulePathMap; + rollupObejct.share.projectConfig.expandImportPath.enable = true; + }); + + mocha.it('2-2: transform the variable name is default', () => { + const tmpModulePathMap = projectConfig.modulePathMap; + projectConfig.modulePathMap = { + 'testPkg': '/TestProject/testPkg' + }; + const { program, testSourceFile } = createMultiSymbolProgram(CASE_2_2_TEST, CASE_2_2_FILES, CASE_2_2_INDEX); + const transformed = ts.transform(testSourceFile, [expandAllImportPaths(program.getTypeChecker(), rollupObejct)], + program.getCompilerOptions()).transformed[0]; + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result = printer.printFile(transformed); + + expect(result === EXPECT_2_2).to.be.true; + projectConfig.modulePathMap = tmpModulePathMap; + }); + + mocha.it('3-1: should preserve namespace import', () => { + const tmpModulePathMap = projectConfig.modulePathMap; + projectConfig.modulePathMap = { + 'testPkg': '/TestProject/testPkg' + }; + const { program, testSourceFile } = createMultiSymbolProgram(CASE_3_1_TEST, CASE_3_1_FILES, CASE_3_1_INDEX); + const transformed = ts.transform(testSourceFile, [expandAllImportPaths(program.getTypeChecker(), rollupObejct)], + program.getCompilerOptions()).transformed[0]; + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result = printer.printFile(transformed); + + expect(result === EXPECT_3_1).to.be.true; + projectConfig.modulePathMap = tmpModulePathMap; + }); + + mocha.it('3-2: should preserve side-effect import', () => { + const tmpModulePathMap = projectConfig.modulePathMap; + projectConfig.modulePathMap = { + 'testPkg': '/TestProject/testPkg' + }; + const { program, testSourceFile } = createMultiSymbolProgram(CASE_3_2_IMPORT, CASE_3_2_FILES, CASE_3_2_INDEX); + const transformed = ts.transform(testSourceFile, [expandAllImportPaths(program.getTypeChecker(), rollupObejct)], + program.getCompilerOptions()).transformed[0]; + const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result = printer.printFile(transformed) + + expect(result === EXPECT_3_2).to.be.true; + projectConfig.modulePathMap = tmpModulePathMap; + }); +}); -- Gitee