Skip to content

Commit 98b6db8

Browse files
rbucktonweswigham
andauthored
Allow accessors in ambient class declarations (#32787)
* Allow accessors in ambient class declarations * Update src/compiler/transformers/declarations.ts Co-Authored-By: Wesley Wigham <[email protected]>
1 parent f2719f9 commit 98b6db8

25 files changed

+537
-174
lines changed

Diff for: src/compiler/checker.ts

+25-32
Original file line numberDiff line numberDiff line change
@@ -5938,7 +5938,9 @@ namespace ts {
59385938
// Otherwise, fall back to 'any'.
59395939
else {
59405940
if (setter) {
5941-
errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol));
5941+
if (!isPrivateWithinAmbient(setter)) {
5942+
errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol));
5943+
}
59425944
}
59435945
else {
59445946
Debug.assert(!!getter, "there must existed getter as we are current checking either setter or getter in this function");
@@ -33059,43 +33061,39 @@ namespace ts {
3305933061
}
3306033062

3306133063
function checkGrammarAccessor(accessor: AccessorDeclaration): boolean {
33062-
const kind = accessor.kind;
33063-
if (languageVersion < ScriptTarget.ES5) {
33064-
return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher);
33065-
}
33066-
else if (accessor.flags & NodeFlags.Ambient) {
33067-
return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_be_declared_in_an_ambient_context);
33068-
}
33069-
else if (accessor.body === undefined && !hasModifier(accessor, ModifierFlags.Abstract)) {
33070-
return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{");
33064+
if (!(accessor.flags & NodeFlags.Ambient)) {
33065+
if (languageVersion < ScriptTarget.ES5) {
33066+
return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher);
33067+
}
33068+
if (accessor.body === undefined && !hasModifier(accessor, ModifierFlags.Abstract)) {
33069+
return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{");
33070+
}
3307133071
}
33072-
else if (accessor.body && hasModifier(accessor, ModifierFlags.Abstract)) {
33072+
if (accessor.body && hasModifier(accessor, ModifierFlags.Abstract)) {
3307333073
return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation);
3307433074
}
33075-
else if (accessor.typeParameters) {
33075+
if (accessor.typeParameters) {
3307633076
return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters);
3307733077
}
33078-
else if (!doesAccessorHaveCorrectParameterCount(accessor)) {
33078+
if (!doesAccessorHaveCorrectParameterCount(accessor)) {
3307933079
return grammarErrorOnNode(accessor.name,
33080-
kind === SyntaxKind.GetAccessor ?
33080+
accessor.kind === SyntaxKind.GetAccessor ?
3308133081
Diagnostics.A_get_accessor_cannot_have_parameters :
3308233082
Diagnostics.A_set_accessor_must_have_exactly_one_parameter);
3308333083
}
33084-
else if (kind === SyntaxKind.SetAccessor) {
33084+
if (accessor.kind === SyntaxKind.SetAccessor) {
3308533085
if (accessor.type) {
3308633086
return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation);
3308733087
}
33088-
else {
33089-
const parameter = accessor.parameters[0];
33090-
if (parameter.dotDotDotToken) {
33091-
return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter);
33092-
}
33093-
else if (parameter.questionToken) {
33094-
return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter);
33095-
}
33096-
else if (parameter.initializer) {
33097-
return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer);
33098-
}
33088+
const parameter = Debug.assertDefined(getSetAccessorValueParameter(accessor), "Return value does not match parameter count assertion.");
33089+
if (parameter.dotDotDotToken) {
33090+
return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter);
33091+
}
33092+
if (parameter.questionToken) {
33093+
return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter);
33094+
}
33095+
if (parameter.initializer) {
33096+
return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer);
3309933097
}
3310033098
}
3310133099
return false;
@@ -33583,14 +33581,9 @@ namespace ts {
3358333581

3358433582
function checkGrammarStatementInAmbientContext(node: Node): boolean {
3358533583
if (node.flags & NodeFlags.Ambient) {
33586-
// An accessors is already reported about the ambient context
33587-
if (isAccessor(node.parent)) {
33588-
return getNodeLinks(node).hasReportedStatementInAmbientContext = true;
33589-
}
33590-
3359133584
// Find containing block which is either Block, ModuleBlock, SourceFile
3359233585
const links = getNodeLinks(node);
33593-
if (!links.hasReportedStatementInAmbientContext && isFunctionLike(node.parent)) {
33586+
if (!links.hasReportedStatementInAmbientContext && (isFunctionLike(node.parent) || isAccessor(node.parent))) {
3359433587
return getNodeLinks(node).hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts);
3359533588
}
3359633589

Diff for: src/compiler/diagnosticMessages.json

-4
Original file line numberDiff line numberDiff line change
@@ -243,10 +243,6 @@
243243
"category": "Error",
244244
"code": 1085
245245
},
246-
"An accessor cannot be declared in an ambient context.": {
247-
"category": "Error",
248-
"code": 1086
249-
},
250246
"'{0}' modifier cannot appear on a constructor declaration.": {
251247
"category": "Error",
252248
"code": 1089

Diff for: src/compiler/transformers/declarations.ts

+71-8
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ namespace ts {
393393
}
394394
}
395395

396-
function ensureParameter(p: ParameterDeclaration, modifierMask?: ModifierFlags): ParameterDeclaration {
396+
function ensureParameter(p: ParameterDeclaration, modifierMask?: ModifierFlags, type?: TypeNode): ParameterDeclaration {
397397
let oldDiag: typeof getSymbolAccessibilityDiagnostic | undefined;
398398
if (!suppressNewDiagnosticContexts) {
399399
oldDiag = getSymbolAccessibilityDiagnostic;
@@ -406,7 +406,7 @@ namespace ts {
406406
p.dotDotDotToken,
407407
filterBindingPatternInitializers(p.name),
408408
resolver.isOptionalParameter(p) ? (p.questionToken || createToken(SyntaxKind.QuestionToken)) : undefined,
409-
ensureType(p, p.type, /*ignorePrivate*/ true), // Ignore private param props, since this type is going straight back into a param
409+
ensureType(p, type || p.type, /*ignorePrivate*/ true), // Ignore private param props, since this type is going straight back into a param
410410
ensureNoInitializer(p)
411411
);
412412
if (!suppressNewDiagnosticContexts) {
@@ -535,6 +535,36 @@ namespace ts {
535535
return createNodeArray(newParams, params.hasTrailingComma);
536536
}
537537

538+
function updateAccessorParamsList(input: AccessorDeclaration, isPrivate: boolean) {
539+
let newParams: ParameterDeclaration[] | undefined;
540+
if (!isPrivate) {
541+
const thisParameter = getThisParameter(input);
542+
if (thisParameter) {
543+
newParams = [ensureParameter(thisParameter)];
544+
}
545+
}
546+
if (isSetAccessorDeclaration(input)) {
547+
let newValueParameter: ParameterDeclaration | undefined;
548+
if (!isPrivate) {
549+
const valueParameter = getSetAccessorValueParameter(input);
550+
if (valueParameter) {
551+
const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input));
552+
newValueParameter = ensureParameter(valueParameter, /*modifierMask*/ undefined, accessorType);
553+
}
554+
}
555+
if (!newValueParameter) {
556+
newValueParameter = createParameter(
557+
/*decorators*/ undefined,
558+
/*modifiers*/ undefined,
559+
/*dotDotDotToken*/ undefined,
560+
"value"
561+
);
562+
}
563+
newParams = append(newParams, newValueParameter);
564+
}
565+
return createNodeArray(newParams || emptyArray) as NodeArray<ParameterDeclaration>;
566+
}
567+
538568
function ensureTypeParams(node: Node, params: NodeArray<TypeParameterDeclaration> | undefined) {
539569
return hasModifier(node, ModifierFlags.Private) ? undefined : visitNodes(params, visitDeclarationSubtree);
540570
}
@@ -811,10 +841,33 @@ namespace ts {
811841
return cleanup(sig);
812842
}
813843
case SyntaxKind.GetAccessor: {
844+
// For now, only emit class accessors as accessors if they were already declared in an ambient context.
845+
if (input.flags & NodeFlags.Ambient) {
846+
const isPrivate = hasModifier(input, ModifierFlags.Private);
847+
const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input));
848+
return cleanup(updateGetAccessor(
849+
input,
850+
/*decorators*/ undefined,
851+
ensureModifiers(input),
852+
input.name,
853+
updateAccessorParamsList(input, isPrivate),
854+
!isPrivate ? ensureType(input, accessorType) : undefined,
855+
/*body*/ undefined));
856+
}
814857
const newNode = ensureAccessor(input);
815858
return cleanup(newNode);
816859
}
817860
case SyntaxKind.SetAccessor: {
861+
// For now, only emit class accessors as accessors if they were already declared in an ambient context.
862+
if (input.flags & NodeFlags.Ambient) {
863+
return cleanup(updateSetAccessor(
864+
input,
865+
/*decorators*/ undefined,
866+
ensureModifiers(input),
867+
input.name,
868+
updateAccessorParamsList(input, hasModifier(input, ModifierFlags.Private)),
869+
/*body*/ undefined));
870+
}
818871
const newNode = ensureAccessor(input);
819872
return cleanup(newNode);
820873
}
@@ -1374,17 +1427,27 @@ namespace ts {
13741427
return maskModifierFlags(node, mask, additions);
13751428
}
13761429

1377-
function ensureAccessor(node: AccessorDeclaration): PropertyDeclaration | undefined {
1378-
const accessors = resolver.getAllAccessorDeclarations(node);
1379-
if (node.kind !== accessors.firstAccessor.kind) {
1380-
return;
1381-
}
1430+
function getTypeAnnotationFromAllAccessorDeclarations(node: AccessorDeclaration, accessors: AllAccessorDeclarations) {
13821431
let accessorType = getTypeAnnotationFromAccessor(node);
1383-
if (!accessorType && accessors.secondAccessor) {
1432+
if (!accessorType && node !== accessors.firstAccessor) {
1433+
accessorType = getTypeAnnotationFromAccessor(accessors.firstAccessor);
1434+
// If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message
1435+
getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.firstAccessor);
1436+
}
1437+
if (!accessorType && accessors.secondAccessor && node !== accessors.secondAccessor) {
13841438
accessorType = getTypeAnnotationFromAccessor(accessors.secondAccessor);
13851439
// If we end up pulling the type from the second accessor, we also need to change the diagnostic context to get the expected error message
13861440
getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(accessors.secondAccessor);
13871441
}
1442+
return accessorType;
1443+
}
1444+
1445+
function ensureAccessor(node: AccessorDeclaration): PropertyDeclaration | undefined {
1446+
const accessors = resolver.getAllAccessorDeclarations(node);
1447+
if (node.kind !== accessors.firstAccessor.kind) {
1448+
return;
1449+
}
1450+
const accessorType = getTypeAnnotationFromAllAccessorDeclarations(node, accessors);
13881451
const prop = createProperty(/*decorators*/ undefined, maskModifiers(node, /*mask*/ undefined, (!accessors.setAccessor) ? ModifierFlags.Readonly : ModifierFlags.None), node.name, node.questionToken, ensureType(node, accessorType), /*initializer*/ undefined);
13891452
const leadingsSyntheticCommentRanges = accessors.secondAccessor && getLeadingCommentRangesOfNode(accessors.secondAccessor, currentSourceFile);
13901453
if (leadingsSyntheticCommentRanges) {

Diff for: src/compiler/utilities.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3518,7 +3518,7 @@ namespace ts {
35183518
return find(node.members, (member): member is ConstructorDeclaration & { body: FunctionBody } => isConstructorDeclaration(member) && nodeIsPresent(member.body));
35193519
}
35203520

3521-
function getSetAccessorValueParameter(accessor: SetAccessorDeclaration): ParameterDeclaration | undefined {
3521+
export function getSetAccessorValueParameter(accessor: SetAccessorDeclaration): ParameterDeclaration | undefined {
35223522
if (accessor && accessor.parameters.length > 0) {
35233523
const hasThis = accessor.parameters.length === 2 && parameterIsThisKeyword(accessor.parameters[0]);
35243524
return accessor.parameters[hasThis ? 1 : 0];
@@ -3553,7 +3553,7 @@ namespace ts {
35533553
return id.originalKeywordKind === SyntaxKind.ThisKeyword;
35543554
}
35553555

3556-
export function getAllAccessorDeclarations(declarations: NodeArray<Declaration>, accessor: AccessorDeclaration): AllAccessorDeclarations {
3556+
export function getAllAccessorDeclarations(declarations: readonly Declaration[], accessor: AccessorDeclaration): AllAccessorDeclarations {
35573557
// TODO: GH#18217
35583558
let firstAccessor!: AccessorDeclaration;
35593559
let secondAccessor!: AccessorDeclaration;

Diff for: src/services/codefixes/helpers.ts

+42-10
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,9 @@ namespace ts.codefix {
5555
const modifiers = visibilityModifier ? createNodeArray([visibilityModifier]) : undefined;
5656
const type = checker.getWidenedType(checker.getTypeOfSymbolAtLocation(symbol, enclosingDeclaration));
5757
const optional = !!(symbol.flags & SymbolFlags.Optional);
58+
const ambient = !!(enclosingDeclaration.flags & NodeFlags.Ambient);
5859

5960
switch (declaration.kind) {
60-
case SyntaxKind.GetAccessor:
61-
case SyntaxKind.SetAccessor:
6261
case SyntaxKind.PropertySignature:
6362
case SyntaxKind.PropertyDeclaration:
6463
const typeNode = checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context));
@@ -70,6 +69,37 @@ namespace ts.codefix {
7069
typeNode,
7170
/*initializer*/ undefined));
7271
break;
72+
case SyntaxKind.GetAccessor:
73+
case SyntaxKind.SetAccessor: {
74+
const allAccessors = getAllAccessorDeclarations(declarations, declaration as AccessorDeclaration);
75+
const typeNode = checker.typeToTypeNode(type, enclosingDeclaration, /*flags*/ undefined, getNoopSymbolTrackerWithResolver(context));
76+
const orderedAccessors = allAccessors.secondAccessor
77+
? [allAccessors.firstAccessor, allAccessors.secondAccessor]
78+
: [allAccessors.firstAccessor];
79+
for (const accessor of orderedAccessors) {
80+
if (isGetAccessorDeclaration(accessor)) {
81+
out(createGetAccessor(
82+
/*decorators*/ undefined,
83+
modifiers,
84+
name,
85+
emptyArray,
86+
typeNode,
87+
ambient ? undefined : createStubbedMethodBody(preferences)));
88+
}
89+
else {
90+
Debug.assertNode(accessor, isSetAccessorDeclaration);
91+
const parameter = getSetAccessorValueParameter(accessor);
92+
const parameterName = parameter && isIdentifier(parameter.name) ? idText(parameter.name) : undefined;
93+
out(createSetAccessor(
94+
/*decorators*/ undefined,
95+
modifiers,
96+
name,
97+
createDummyParameters(1, [parameterName], [typeNode], 1, /*inJs*/ false),
98+
ambient ? undefined : createStubbedMethodBody(preferences)));
99+
}
100+
}
101+
break;
102+
}
73103
case SyntaxKind.MethodSignature:
74104
case SyntaxKind.MethodDeclaration:
75105
// The signature for the implementation appears as an entry in `signatures` iff
@@ -87,7 +117,7 @@ namespace ts.codefix {
87117
if (declarations.length === 1) {
88118
Debug.assert(signatures.length === 1);
89119
const signature = signatures[0];
90-
outputMethod(signature, modifiers, name, createStubbedMethodBody(preferences));
120+
outputMethod(signature, modifiers, name, ambient ? undefined : createStubbedMethodBody(preferences));
91121
break;
92122
}
93123

@@ -96,13 +126,15 @@ namespace ts.codefix {
96126
outputMethod(signature, getSynthesizedDeepClones(modifiers, /*includeTrivia*/ false), getSynthesizedDeepClone(name, /*includeTrivia*/ false));
97127
}
98128

99-
if (declarations.length > signatures.length) {
100-
const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration)!;
101-
outputMethod(signature, modifiers, name, createStubbedMethodBody(preferences));
102-
}
103-
else {
104-
Debug.assert(declarations.length === signatures.length);
105-
out(createMethodImplementingSignatures(signatures, name, optional, modifiers, preferences));
129+
if (!ambient) {
130+
if (declarations.length > signatures.length) {
131+
const signature = checker.getSignatureFromDeclaration(declarations[declarations.length - 1] as SignatureDeclaration)!;
132+
outputMethod(signature, modifiers, name, createStubbedMethodBody(preferences));
133+
}
134+
else {
135+
Debug.assert(declarations.length === signatures.length);
136+
out(createMethodImplementingSignatures(signatures, name, optional, modifiers, preferences));
137+
}
106138
}
107139
break;
108140
}

0 commit comments

Comments
 (0)