diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index c9517440ac6ed..edfea19abb1d3 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -337,6 +337,14 @@ namespace ts { description: Diagnostics.Only_emit_d_ts_declaration_files, transpileOptionValue: undefined }, + { + name: "legacyAccessorDeclarations", + type: "boolean", + affectsEmit: true, + category: Diagnostics.Advanced_Options, + description: Diagnostics.Emits_accessors_as_property_declarations_in_declaration_emit, + transpileOptionValue: undefined + }, { name: "sourceMap", type: "boolean", diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 5cb18c1f33367..e798fb714b064 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4128,6 +4128,10 @@ "category": "Message", "code": 6223 }, + "Emits accessors as property declarations in declaration emit.": { + "category": "Message", + "code": 6224 + }, "Projects to reference": { "category": "Message", diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 59b57bfa99a41..f98ebca81fd21 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -3001,6 +3001,12 @@ namespace ts { } } + if (options.legacyAccessorDeclarations) { + if (!getEmitDeclarations(options)) { + createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationDir", "declaration", "composite"); + } + } + if (options.declarationMap && !getEmitDeclarations(options)) { createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1_or_option_2, "declarationMap", "declaration", "composite"); } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index bd8f34ee128c6..e3a97e0fcabec 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -29,7 +29,7 @@ namespace ts { export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers, emitOnlyDtsFiles?: boolean): EmitTransformers { return { scriptTransformers: getScriptTransformers(compilerOptions, customTransformers, emitOnlyDtsFiles), - declarationTransformers: getDeclarationTransformers(customTransformers), + declarationTransformers: getDeclarationTransformers(compilerOptions, customTransformers), }; } @@ -87,9 +87,12 @@ namespace ts { return transformers; } - function getDeclarationTransformers(customTransformers?: CustomTransformers) { + function getDeclarationTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers) { const transformers: TransformerFactory<SourceFile | Bundle>[] = []; transformers.push(transformDeclarations); + if (compilerOptions.legacyAccessorDeclarations) { + transformers.push(transformAccessorsToPropertyDeclarations); + } addRange(transformers, customTransformers && map(customTransformers.afterDeclarations, wrapDeclarationTransformerFactory)); return transformers; } diff --git a/src/compiler/transformers/legacyAccessorDeclarations.ts b/src/compiler/transformers/legacyAccessorDeclarations.ts new file mode 100644 index 0000000000000..4124291bb8b21 --- /dev/null +++ b/src/compiler/transformers/legacyAccessorDeclarations.ts @@ -0,0 +1,65 @@ +/*@internal*/ +namespace ts { + /** + * Transforms accessor declarations in class bodies into roughly equivalent property declarations + */ + export function transformAccessorsToPropertyDeclarations(context: TransformationContext) { + return chainBundle(transformSourceFile); + + function transformSourceFile(file: SourceFile) { + return visitEachChild(file, transformNode, context); + } + + function transformNode(node: Node): Node { + if (isClassLike(node)) { + if (some(node.members, isAccessor)) { + return visitEachChild(transformClass(node), transformNode, context); + } + } + return visitEachChild(node, transformNode, context); + } + + function transformClass(cls: ClassLikeDeclaration): ClassLikeDeclaration { + const members: ClassElement[] = []; + for (const member of cls.members) { + if (!isAccessor(member)) { + members.push(member); + continue; + } + const { firstAccessor, setAccessor } = getAllAccessorDeclarations(cls.members, member); + if (firstAccessor === member) { + members.push(setTextRange(setOriginalNode(createProperty( + member.decorators, + !setAccessor ? createModifiersFromModifierFlags(getModifierFlags(member) | ModifierFlags.Readonly) : member.modifiers, + member.name, + member.questionToken, + isSetAccessor(member) ? getSetAccessorTypeAnnotationNode(member) : getEffectiveReturnTypeNode(member), + /*initializer*/ undefined + ), member), member)); + } + } + + if (isClassDeclaration(cls)) { + return updateClassDeclaration( + cls, + cls.decorators, + cls.modifiers, + cls.name, + cls.typeParameters, + cls.heritageClauses, + members + ); + } + else { + return updateClassExpression( + cls, + cls.modifiers, + cls.name, + cls.typeParameters, + cls.heritageClauses, + members + ); + } + } + } +} \ No newline at end of file diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index cef7aba0484de..7203d8478d1cd 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -49,6 +49,7 @@ "transformers/module/es2015.ts", "transformers/declarations/diagnostics.ts", "transformers/declarations.ts", + "transformers/legacyAccessorDeclarations.ts", "transformer.ts", "emitter.ts", "watchUtilities.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 25ed46dde55a9..42c298ba56c5a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4939,6 +4939,7 @@ namespace ts { declaration?: boolean; declarationMap?: boolean; emitDeclarationOnly?: boolean; + legacyAccessorDeclarations?: boolean; declarationDir?: string; /* @internal */ diagnostics?: boolean; /* @internal */ extendedDiagnostics?: boolean; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c43f28c6e9095..20f3b19e4c6de 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2589,6 +2589,7 @@ declare namespace ts { declaration?: boolean; declarationMap?: boolean; emitDeclarationOnly?: boolean; + legacyAccessorDeclarations?: boolean; declarationDir?: string; disableSizeLimit?: boolean; disableSourceOfProjectReferenceRedirect?: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index e84b948c9386a..af21bada622f8 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2589,6 +2589,7 @@ declare namespace ts { declaration?: boolean; declarationMap?: boolean; emitDeclarationOnly?: boolean; + legacyAccessorDeclarations?: boolean; declarationDir?: string; disableSizeLimit?: boolean; disableSourceOfProjectReferenceRedirect?: boolean; diff --git a/tests/baselines/reference/declarationEmitAccessorsAsProperties.js b/tests/baselines/reference/declarationEmitAccessorsAsProperties.js new file mode 100644 index 0000000000000..8d0d814b14c7f --- /dev/null +++ b/tests/baselines/reference/declarationEmitAccessorsAsProperties.js @@ -0,0 +1,51 @@ +//// [declarationEmitAccessorsAsProperties.ts] +export class Cls { + get prop(): number { + return 12; + } + + set evt(x: number) {} + + get val(): number { + return 42; + } + set val(_) {} +} + +//// [declarationEmitAccessorsAsProperties.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +var Cls = /** @class */ (function () { + function Cls() { + } + Object.defineProperty(Cls.prototype, "prop", { + get: function () { + return 12; + }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Cls.prototype, "evt", { + set: function (x) { }, + enumerable: true, + configurable: true + }); + Object.defineProperty(Cls.prototype, "val", { + get: function () { + return 42; + }, + set: function (_) { }, + enumerable: true, + configurable: true + }); + return Cls; +}()); +exports.Cls = Cls; + + +//// [declarationEmitAccessorsAsProperties.d.ts] +export declare class Cls { + readonly prop: number; + evt: number; + val: number; +} diff --git a/tests/baselines/reference/declarationEmitAccessorsAsProperties.symbols b/tests/baselines/reference/declarationEmitAccessorsAsProperties.symbols new file mode 100644 index 0000000000000..26e6927f48524 --- /dev/null +++ b/tests/baselines/reference/declarationEmitAccessorsAsProperties.symbols @@ -0,0 +1,23 @@ +=== tests/cases/compiler/declarationEmitAccessorsAsProperties.ts === +export class Cls { +>Cls : Symbol(Cls, Decl(declarationEmitAccessorsAsProperties.ts, 0, 0)) + + get prop(): number { +>prop : Symbol(Cls.prop, Decl(declarationEmitAccessorsAsProperties.ts, 0, 18)) + + return 12; + } + + set evt(x: number) {} +>evt : Symbol(Cls.evt, Decl(declarationEmitAccessorsAsProperties.ts, 3, 5)) +>x : Symbol(x, Decl(declarationEmitAccessorsAsProperties.ts, 5, 12)) + + get val(): number { +>val : Symbol(Cls.val, Decl(declarationEmitAccessorsAsProperties.ts, 5, 25), Decl(declarationEmitAccessorsAsProperties.ts, 9, 5)) + + return 42; + } + set val(_) {} +>val : Symbol(Cls.val, Decl(declarationEmitAccessorsAsProperties.ts, 5, 25), Decl(declarationEmitAccessorsAsProperties.ts, 9, 5)) +>_ : Symbol(_, Decl(declarationEmitAccessorsAsProperties.ts, 10, 12)) +} diff --git a/tests/baselines/reference/declarationEmitAccessorsAsProperties.types b/tests/baselines/reference/declarationEmitAccessorsAsProperties.types new file mode 100644 index 0000000000000..c321c61f8c502 --- /dev/null +++ b/tests/baselines/reference/declarationEmitAccessorsAsProperties.types @@ -0,0 +1,25 @@ +=== tests/cases/compiler/declarationEmitAccessorsAsProperties.ts === +export class Cls { +>Cls : Cls + + get prop(): number { +>prop : number + + return 12; +>12 : 12 + } + + set evt(x: number) {} +>evt : number +>x : number + + get val(): number { +>val : number + + return 42; +>42 : 42 + } + set val(_) {} +>val : number +>_ : number +} diff --git a/tests/baselines/reference/showConfig/Shows tsconfig for single option/legacyAccessorDeclarations/tsconfig.json b/tests/baselines/reference/showConfig/Shows tsconfig for single option/legacyAccessorDeclarations/tsconfig.json new file mode 100644 index 0000000000000..91e0a7a991846 --- /dev/null +++ b/tests/baselines/reference/showConfig/Shows tsconfig for single option/legacyAccessorDeclarations/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "legacyAccessorDeclarations": true + } +} diff --git a/tests/cases/compiler/declarationEmitAccessorsAsProperties.ts b/tests/cases/compiler/declarationEmitAccessorsAsProperties.ts new file mode 100644 index 0000000000000..19f169de4fd39 --- /dev/null +++ b/tests/cases/compiler/declarationEmitAccessorsAsProperties.ts @@ -0,0 +1,15 @@ +// @declaration: true +// @target: es5 +// @legacyAccessorDeclarations: true +export class Cls { + get prop(): number { + return 12; + } + + set evt(x: number) {} + + get val(): number { + return 42; + } + set val(_) {} +} \ No newline at end of file