Skip to content

Commit 7b17de1

Browse files
committed
Allow accessors in ambient class declarations
1 parent f333684 commit 7b17de1

23 files changed

+524
-107
lines changed

Diff for: src/compiler/checker.ts

+25-32
Original file line numberDiff line numberDiff line change
@@ -5935,7 +5935,9 @@ namespace ts {
59355935
// Otherwise, fall back to 'any'.
59365936
else {
59375937
if (setter) {
5938-
errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol));
5938+
if (!isPrivateWithinAmbient(setter)) {
5939+
errorOrSuggestion(noImplicitAny, setter, Diagnostics.Property_0_implicitly_has_type_any_because_its_set_accessor_lacks_a_parameter_type_annotation, symbolToString(symbol));
5940+
}
59395941
}
59405942
else {
59415943
Debug.assert(!!getter, "there must existed getter as we are current checking either setter or getter in this function");
@@ -33035,43 +33037,39 @@ namespace ts {
3303533037
}
3303633038

3303733039
function checkGrammarAccessor(accessor: AccessorDeclaration): boolean {
33038-
const kind = accessor.kind;
33039-
if (languageVersion < ScriptTarget.ES5) {
33040-
return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher);
33041-
}
33042-
else if (accessor.flags & NodeFlags.Ambient) {
33043-
return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_be_declared_in_an_ambient_context);
33044-
}
33045-
else if (accessor.body === undefined && !hasModifier(accessor, ModifierFlags.Abstract)) {
33046-
return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{");
33040+
if (!(accessor.flags & NodeFlags.Ambient)) {
33041+
if (languageVersion < ScriptTarget.ES5) {
33042+
return grammarErrorOnNode(accessor.name, Diagnostics.Accessors_are_only_available_when_targeting_ECMAScript_5_and_higher);
33043+
}
33044+
if (accessor.body === undefined && !hasModifier(accessor, ModifierFlags.Abstract)) {
33045+
return grammarErrorAtPos(accessor, accessor.end - 1, ";".length, Diagnostics._0_expected, "{");
33046+
}
3304733047
}
33048-
else if (accessor.body && hasModifier(accessor, ModifierFlags.Abstract)) {
33048+
if (accessor.body && hasModifier(accessor, ModifierFlags.Abstract)) {
3304933049
return grammarErrorOnNode(accessor, Diagnostics.An_abstract_accessor_cannot_have_an_implementation);
3305033050
}
33051-
else if (accessor.typeParameters) {
33051+
if (accessor.typeParameters) {
3305233052
return grammarErrorOnNode(accessor.name, Diagnostics.An_accessor_cannot_have_type_parameters);
3305333053
}
33054-
else if (!doesAccessorHaveCorrectParameterCount(accessor)) {
33054+
if (!doesAccessorHaveCorrectParameterCount(accessor)) {
3305533055
return grammarErrorOnNode(accessor.name,
33056-
kind === SyntaxKind.GetAccessor ?
33056+
accessor.kind === SyntaxKind.GetAccessor ?
3305733057
Diagnostics.A_get_accessor_cannot_have_parameters :
3305833058
Diagnostics.A_set_accessor_must_have_exactly_one_parameter);
3305933059
}
33060-
else if (kind === SyntaxKind.SetAccessor) {
33060+
if (accessor.kind === SyntaxKind.SetAccessor) {
3306133061
if (accessor.type) {
3306233062
return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_cannot_have_a_return_type_annotation);
3306333063
}
33064-
else {
33065-
const parameter = accessor.parameters[0];
33066-
if (parameter.dotDotDotToken) {
33067-
return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter);
33068-
}
33069-
else if (parameter.questionToken) {
33070-
return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter);
33071-
}
33072-
else if (parameter.initializer) {
33073-
return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer);
33074-
}
33064+
const parameter = Debug.assertDefined(getSetAccessorValueParameter(accessor), "Return value does not match parameter count assertion.");
33065+
if (parameter.dotDotDotToken) {
33066+
return grammarErrorOnNode(parameter.dotDotDotToken, Diagnostics.A_set_accessor_cannot_have_rest_parameter);
33067+
}
33068+
if (parameter.questionToken) {
33069+
return grammarErrorOnNode(parameter.questionToken, Diagnostics.A_set_accessor_cannot_have_an_optional_parameter);
33070+
}
33071+
if (parameter.initializer) {
33072+
return grammarErrorOnNode(accessor.name, Diagnostics.A_set_accessor_parameter_cannot_have_an_initializer);
3307533073
}
3307633074
}
3307733075
return false;
@@ -33559,14 +33557,9 @@ namespace ts {
3355933557

3356033558
function checkGrammarStatementInAmbientContext(node: Node): boolean {
3356133559
if (node.flags & NodeFlags.Ambient) {
33562-
// An accessors is already reported about the ambient context
33563-
if (isAccessor(node.parent)) {
33564-
return getNodeLinks(node).hasReportedStatementInAmbientContext = true;
33565-
}
33566-
3356733560
// Find containing block which is either Block, ModuleBlock, SourceFile
3356833561
const links = getNodeLinks(node);
33569-
if (!links.hasReportedStatementInAmbientContext && isFunctionLike(node.parent)) {
33562+
if (!links.hasReportedStatementInAmbientContext && (isFunctionLike(node.parent) || isAccessor(node.parent))) {
3357033563
return getNodeLinks(node).hasReportedStatementInAmbientContext = grammarErrorOnFirstToken(node, Diagnostics.An_implementation_cannot_be_declared_in_ambient_contexts);
3357133564
}
3357233565

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+
type || ensureType(p, 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)