diff --git a/build_package/ohos-typescript-4.2.3-r2.tgz b/build_package/ohos-typescript-4.2.3-r2.tgz index d27fb4c01aaf56dfe4ced28d6a5c116c9e7aa4c2..8e652825ded95057c42b9ab9e3319790951b1324 100644 Binary files a/build_package/ohos-typescript-4.2.3-r2.tgz and b/build_package/ohos-typescript-4.2.3-r2.tgz differ diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 40de06e7cbfdbfe3d9c461d60a1a1e65cf790c4c..9d8a186ba50e3095d2c0062ba14021ced8b8776f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -39183,6 +39183,11 @@ namespace ts { return false; } + function isReferenced(node: Node): boolean { + const symbol = getSymbolOfNode(node); + return !!symbol?.isReferenced; + } + function isImplementationOfOverload(node: SignatureDeclaration) { if (nodeIsPresent((node as FunctionLikeDeclaration).body)) { if (isGetAccessor(node) || isSetAccessor(node)) return false; // Get or set accessors can never be overload implementations, but can have up to 2 signatures @@ -39518,6 +39523,10 @@ namespace ts { // Synthesized nodes are always treated as referenced. return node ? isReferencedAliasDeclaration(node, checkChildren) : true; }, + isReferenced: (nodeIn: Node | undefined): boolean => { + const node = getParseTreeNode(nodeIn); + return node ? isReferenced(node) : false; + }, getNodeCheckFlags: nodeIn => { const node = getParseTreeNode(nodeIn); return node ? getNodeCheckFlags(node) : 0; diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 947db5659cad5ef12b604c27125ee3a87bc7b36c..2764a9eeb0fa738820219dbe38ee3b548dc63f48 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -681,6 +681,7 @@ namespace ts { isDeclarationWithCollidingName: notImplemented, isValueAliasDeclaration: notImplemented, isReferencedAliasDeclaration: notImplemented, + isReferenced: notImplemented, isTopLevelValueImportEqualsWithEntityName: notImplemented, getNodeCheckFlags: notImplemented, isDeclarationVisible: notImplemented, diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 2ef491a620fa25ead09741244532f75dee08ee00..f1764ee68ea31b516ecbca860d9ca9ba58ea54a5 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -4010,4 +4010,8 @@ namespace ts { } Debug.fail("should never ask for module name at index higher than possible module name"); } + + export function getTypeExportImportAndConstEnumTransformer(context: TransformationContext): (node: SourceFile) => SourceFile { + return transformTypeExportImportAndConstEnumInTypeScript(context); + } } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 0d24f332c15bda06b6a9ab1d675eed8003655d65..5d66156dbfadeca00453972346e035d82705b3ad 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -3370,4 +3370,301 @@ namespace ts { return isPropertyAccessExpression(node) || isElementAccessExpression(node) ? resolver.getConstantValue(node) : undefined; } } + + + /** + * Add 'type' flag to import/export when import/export an type member. + * Replace const enum with number and string literal. + */ + export function transformTypeExportImportAndConstEnumInTypeScript(context: TransformationContext): (node: SourceFile) => SourceFile { + const resolver = context.getEmitResolver(); + interface ImportInfo { + name: Identifier | undefined, + namespaceImport: NamespaceImport | undefined, + namedImports: ImportSpecifier[] + }; + interface ExportInfo { + namedExports: ExportSpecifier[] + }; + + // recore type import/export info to create new import/export type statement + let currentTypeImportInfo: ImportInfo; + let currentTypeExportInfo: ExportInfo; + + return transformSourceFile; + + function transformSourceFile(node: SourceFile): SourceFile { + if (node.isDeclarationFile) { + return node; + } + const visited = factory.updateSourceFile( + node, + visitLexicalEnvironment(node.statements, visitImportExportAndConstEnumMember, context)); + return visited; + } + + function visitImportExportAndConstEnumMember(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.ImportDeclaration: + return visitImportDeclaration(node); + case SyntaxKind.ImportEqualsDeclaration: + return visitImportEqualsDeclaration(node); + case SyntaxKind.ExportDeclaration: + return visitExportDeclaration(node); + case SyntaxKind.PropertyAccessExpression: + case SyntaxKind.ElementAccessExpression: + return visitConstEnum(node); + case SyntaxKind.EnumMember: + return visitEnumMember(node); + default: + return visitEachChild(node, visitImportExportAndConstEnumMember, context); + } + } + + /** + * Transform: + * + * import a, {b, c} from ... + * + * To: + * + * import {b} from ... + * import type a from ... + * import type {c} from ... + * + * when 'a' and 'c' are type. + */ + function visitImportDeclaration(node: ImportDeclaration): VisitResult { + // return if the import already has 'type' + if (!node.importClause || node.importClause.isTypeOnly) { + return node; + } + resetcurrentTypeImportInfo(); + const res: Statement[] = []; + const importClause = visitNode(node.importClause, visitImportClause, isImportClause); + if (importClause) { + res.push(factory.updateImportDeclaration(node, /*decorators*/ undefined, /*modifiers*/ undefined, + importClause, node.moduleSpecifier)); + } + // create new import statement with 'type' + const typeImportClauses = createTypeImportClause(); + for (const typeImportClause of typeImportClauses) { + res.push(factory.createImportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, + typeImportClause, node.moduleSpecifier)); + } + return res.length > 0 ? res : undefined; + } + + function visitImportClause(node: ImportClause): VisitResult { + if (node.isTypeOnly) { + return node; + } + let name: Identifier | undefined; + if (resolver.isReferencedAliasDeclaration(node)) { + name = node.name; + } + // consider it is a type if the symbol has referenced. + else if (resolver.isReferenced(node)) { + addTypeImportClauseName(node); + } + const namedBindings = visitNode(node.namedBindings, visitNamedImportBindings, isNamedImportBindings); + return (name || namedBindings) ? + factory.updateImportClause(node, /*isTypeOnly*/ false, name, namedBindings) : + undefined; + } + + function visitNamedImportBindings(node: NamedImportBindings): VisitResult { + if (node.kind === SyntaxKind.NamespaceImport) { + if (resolver.isReferencedAliasDeclaration(node)) { + return node; + } + if (resolver.isReferenced(node)) { + addTypeNamespaceImport(node); + } + return undefined; + } + else { + const elements = visitNodes(node.elements, visitImportSpecifier, isImportSpecifier); + return some(elements) ? factory.updateNamedImports(node, elements) : undefined; + } + } + + function visitImportSpecifier(node: ImportSpecifier): VisitResult { + if (resolver.isReferencedAliasDeclaration(node)) { + return node; + } + if (resolver.isReferenced(node)) { + addTypeImportSpecifier(node); + } + return undefined; + } + + function addTypeImportClauseName(node: ImportClause): void { + currentTypeImportInfo.name = node.name; + } + + function addTypeNamespaceImport(node: NamespaceImport): void { + currentTypeImportInfo.namespaceImport = node; + } + + function addTypeImportSpecifier(node: ImportSpecifier): void { + currentTypeImportInfo.namedImports.push(node); + } + + /** + * Create new import type statement, like: + * import type {a} from ... + */ + function createTypeImportClause(): ImportClause[] { + const name: Identifier | undefined = currentTypeImportInfo.name; + let namedBindings: NamedImportBindings | undefined; + if (currentTypeImportInfo.namespaceImport) { + namedBindings = currentTypeImportInfo.namespaceImport; + } + else if (currentTypeImportInfo.namedImports.length > 0) { + namedBindings = factory.createNamedImports(currentTypeImportInfo.namedImports); + } + const typeImportClauses: ImportClause[] = []; + if (name !== undefined) { + typeImportClauses.push(factory.createImportClause(/*isTypeOnly*/ true, name, /*namedBindings*/ undefined)); + } + if (namedBindings !== undefined) { + typeImportClauses.push(factory.createImportClause(/*isTypeOnly*/ true, /*name*/ undefined, namedBindings)); + } + resetcurrentTypeImportInfo(); + return typeImportClauses; + } + + /** + * Transform: + * + * import a = require(...) + * + * To: + * + * import type a = require(...) + * + * when 'a' is type. + */ + function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): VisitResult { + // return if the import already has 'type' + if (node.isTypeOnly) { + return node; + } + + if (isExternalModuleImportEqualsDeclaration(node)) { + const isReferenced = resolver.isReferencedAliasDeclaration(node); + if (isReferenced) { + return node; + } + if (resolver.isReferenced(node)) { + return factory.updateImportEqualsDeclaration(node, node.decorators, node.modifiers, + /*isTypeOnly*/ true, node.name, node.moduleReference); + } + + return undefined; + } + + return node; + } + + /** + * Transform: + * + * export {a} + * + * To: + * + * export type {a} + * + * when 'a' is type. + */ + function visitExportDeclaration(node: ExportDeclaration): VisitResult { + // return if the export already has 'type'or export * + if (node.isTypeOnly || !node.exportClause || isNamespaceExport(node.exportClause)) { + return node; + } + + resetcurrentTypeExportInfo(); + const res: Statement[] = []; + + const exportClause = visitNode(node.exportClause, visitNamedExports, isNamedExportBindings); + if (exportClause) { + res.push(factory.updateExportDeclaration(node, /*decorators*/ undefined, /*modifiers*/ undefined, + node.isTypeOnly, exportClause, node.moduleSpecifier)); + } + const typeExportClause = createTypeExportClause(); + if (typeExportClause) { + res.push(factory.createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, + /*isTypeOnly*/ true, typeExportClause, node.moduleSpecifier)); + } + + return res.length > 0 ? res : undefined; + } + + function visitNamedExports(node: NamedExports): VisitResult { + const elements = visitNodes(node.elements, visitExportSpecifier, isExportSpecifier); + return some(elements) ? factory.updateNamedExports(node, elements) : undefined; + } + + function visitExportSpecifier(node: ExportSpecifier): VisitResult { + if (resolver.isValueAliasDeclaration(node)) { + return node; + } + // consider all rest member are type. + addTypeExportSpecifier(node); + return undefined; + } + + function addTypeExportSpecifier(node: ExportSpecifier): void { + currentTypeExportInfo.namedExports.push(node); + } + + /** + * Create new export type statement, like: + * export type {a} + */ + function createTypeExportClause(): NamedExports | undefined { + let namedBindings: NamedExports | undefined; + if (currentTypeExportInfo.namedExports.length > 0) { + namedBindings = factory.createNamedExports(currentTypeExportInfo.namedExports); + } + resetcurrentTypeExportInfo(); + return namedBindings; + } + + function visitConstEnum(node: PropertyAccessExpression | ElementAccessExpression): LeftHandSideExpression { + const constantValue = resolver.getConstantValue(node); + if (constantValue !== undefined) { + const substitute = typeof constantValue === "string" ? + factory.createStringLiteral(constantValue) : + factory.createNumericLiteral(constantValue); + return substitute; + } + + return node; + } + + /** + * If the enum member is a const value, replace it. + */ + function visitEnumMember(node: EnumMember): VisitResult { + const value = resolver.getConstantValue(node); + if (value !== undefined) { + const substitute = typeof value === "string" ? + factory.createStringLiteral(value) : + factory.createNumericLiteral(value); + return factory.updateEnumMember(node, node.name, substitute); + } + return visitEachChild(node, visitImportExportAndConstEnumMember, context); + } + + function resetcurrentTypeImportInfo(): void { + currentTypeImportInfo = { name: undefined, namespaceImport: undefined, namedImports:[] }; + } + + function resetcurrentTypeExportInfo(): void { + currentTypeExportInfo = { namedExports:[] }; + } + } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 75e8d6382340b22054f88eae5137044165eea951..b8ff341e6a64101af32fe67f04ce71ec90cd36a4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4628,6 +4628,7 @@ namespace ts { isDeclarationWithCollidingName(node: Declaration): boolean; isValueAliasDeclaration(node: Node): boolean; isReferencedAliasDeclaration(node: Node, checkChildren?: boolean): boolean; + isReferenced(node: Node): boolean; isTopLevelValueImportEqualsWithEntityName(node: ImportEqualsDeclaration): boolean; getNodeCheckFlags(node: Node): NodeCheckFlags; isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 7ab1cdda62c1a828db71a11b615f244a740752fe..b4a20245364bbb080bd09186ce076a74c105dc30 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4945,6 +4945,7 @@ declare namespace ts { */ export function resolveProjectReferencePath(ref: ProjectReference): ResolvedConfigFileName; /** @deprecated */ export function resolveProjectReferencePath(host: ResolveProjectReferencePathHost, ref: ProjectReference): ResolvedConfigFileName; + export function getTypeExportImportAndConstEnumTransformer(context: TransformationContext): (node: SourceFile) => SourceFile; export {}; } declare namespace ts { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index e0e1f7671f6c0bcde81ad079e55f57db1de78822..c7563e7dc035a427c99fb12c0f78dde902025cfa 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4945,6 +4945,7 @@ declare namespace ts { */ export function resolveProjectReferencePath(ref: ProjectReference): ResolvedConfigFileName; /** @deprecated */ export function resolveProjectReferencePath(host: ResolveProjectReferencePathHost, ref: ProjectReference): ResolvedConfigFileName; + export function getTypeExportImportAndConstEnumTransformer(context: TransformationContext): (node: SourceFile) => SourceFile; export {}; } declare namespace ts {