Skip to content

Commit 500a0df

Browse files
authored
Add useDefineForClassFields flag for Set -> Define property declaration (#33509)
* Disallow property/accessor overrides Unless the base property or accessor is abstract * Disallow uninitialised property overrides This causes quite a few test breaks. We'll probably want to revert many of them by switching to the upcoming `declare x: number` syntax. * Updates from design review + fix ancient bug 1. Don't error when overriding properties from interfaces. 2. Fix error when overriding methods with other things. This had no tests so I assume that the code was always dead and never worked. * Need to add a couple of errors and squash one Will update after checking out other branch for a minute * Everything works so far Need to test properties initialised in constructor * Check for constructor initialisation * change error wording * Improve error wording * Add codefix to add missing 'declare' * Always emit accessors in .d.ts files * Allow 'declare' on any uninitialised property decl * Undo code moves * Let sleeping dogs lie * Correctly set NodeFlags.Ambient And simplify redundant parts of check. * Remove more unneeded code * Update baselines * Update baselines * Update baselines * Ignore this-property assignments * Fix base-in-interface check * Do not error when base parent is interface * Fix base interface check * Add missed baselines * Fix check * Fix new errors in services * Fix new errors in services * Fix errors in testRunner * Add flag and turn off errors when on * Structure of new emit is correct, fake content It is 'hi'. * Basically right emit * Fix one last unitialised property declaration * Haha no I missed another one * Fix whitespace back to CRLF * Minor fix and code cleanup * New test case * Fix bug in isInitializedProperty * Updates from design meeting. 1. Change flag name to useDefineForClassFields (and flip polarity). 2. Forbid ES3 + useDefineForClassFields (since there is no defineProperty). 3. Forbid overriding an abstract property-with-initializer with an accessor. * Update baselines * Object.defineProperty for methods too Using code from Ron from his upcoming refactor of the factory functions. * Update slow baselines * Improve error message * Update src/compiler/transformers/utilities.ts Co-Authored-By: Andrew Branch <[email protected]> * Add test of computed properties * Remove done TODO
1 parent 7d9b22e commit 500a0df

File tree

170 files changed

+6500
-3004
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

170 files changed

+6500
-3004
lines changed

src/compiler/checker.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29904,7 +29904,6 @@ namespace ts {
2990429904
}
2990529905

2990629906
function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: BaseType): void {
29907-
2990829907
// TypeScript 1.0 spec (April 2014): 8.2.3
2990929908
// A derived class inherits all members from its base class it doesn't override.
2991029909
// Inheritance means that a derived class implicitly contains all non - overridden members of the base class.
@@ -29938,7 +29937,6 @@ namespace ts {
2993829937
// type declaration, derived and base resolve to the same symbol even in the case of generic classes.
2993929938
if (derived === base) {
2994029939
// derived class inherits base without override/redeclaration
29941-
2994229940
const derivedClassDecl = getClassLikeDeclarationOfSymbol(type.symbol)!;
2994329941

2994429942
// It is an error to inherit an abstract member without implementing it or being declared abstract.
@@ -29975,14 +29973,53 @@ namespace ts {
2997529973
continue;
2997629974
}
2997729975

29978-
if (isPrototypeProperty(base) || base.flags & SymbolFlags.PropertyOrAccessor && derived.flags & SymbolFlags.PropertyOrAccessor) {
29979-
// method is overridden with method or property/accessor is overridden with property/accessor - correct case
29980-
continue;
29981-
}
29982-
2998329976
let errorMessage: DiagnosticMessage;
29984-
if (isPrototypeProperty(base)) {
29985-
if (derived.flags & SymbolFlags.Accessor) {
29977+
const basePropertyFlags = base.flags & SymbolFlags.PropertyOrAccessor;
29978+
const derivedPropertyFlags = derived.flags & SymbolFlags.PropertyOrAccessor;
29979+
if (basePropertyFlags && derivedPropertyFlags) {
29980+
// property/accessor is overridden with property/accessor
29981+
if (!compilerOptions.useDefineForClassFields
29982+
|| baseDeclarationFlags & ModifierFlags.Abstract && !(base.valueDeclaration && isPropertyDeclaration(base.valueDeclaration) && base.valueDeclaration.initializer)
29983+
|| base.valueDeclaration && base.valueDeclaration.parent.kind === SyntaxKind.InterfaceDeclaration
29984+
|| derived.valueDeclaration && isBinaryExpression(derived.valueDeclaration)) {
29985+
// when the base property is abstract or from an interface, base/derived flags don't need to match
29986+
// same when the derived property is from an assignment
29987+
continue;
29988+
}
29989+
if (basePropertyFlags !== SymbolFlags.Property && derivedPropertyFlags === SymbolFlags.Property) {
29990+
errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_property;
29991+
}
29992+
else if (basePropertyFlags === SymbolFlags.Property && derivedPropertyFlags !== SymbolFlags.Property) {
29993+
errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_accessor;
29994+
}
29995+
else {
29996+
const uninitialized = find(derived.declarations, d => d.kind === SyntaxKind.PropertyDeclaration && !(d as PropertyDeclaration).initializer);
29997+
if (uninitialized
29998+
&& !(derived.flags & SymbolFlags.Transient)
29999+
&& !(baseDeclarationFlags & ModifierFlags.Abstract)
30000+
&& !(derivedDeclarationFlags & ModifierFlags.Abstract)
30001+
&& !derived.declarations.some(d => d.flags & NodeFlags.Ambient)) {
30002+
const constructor = findConstructorDeclaration(getClassLikeDeclarationOfSymbol(type.symbol)!);
30003+
const propName = (uninitialized as PropertyDeclaration).name;
30004+
if ((uninitialized as PropertyDeclaration).exclamationToken
30005+
|| !constructor
30006+
|| !isIdentifier(propName)
30007+
|| !strictNullChecks
30008+
|| !isPropertyInitializedInConstructor(propName, type, constructor)) {
30009+
const errorMessage = Diagnostics.Property_0_will_overwrite_the_base_property_in_1_If_this_is_intentional_add_an_initializer_Otherwise_add_a_declare_modifier_or_remove_the_redundant_declaration;
30010+
error(getNameOfDeclaration(derived.valueDeclaration) || derived.valueDeclaration, errorMessage, symbolToString(base), typeToString(baseType));
30011+
}
30012+
}
30013+
// correct case
30014+
continue;
30015+
}
30016+
}
30017+
else if (isPrototypeProperty(base)) {
30018+
if (isPrototypeProperty(derived)) {
30019+
// method is overridden with method -- correct case
30020+
continue;
30021+
}
30022+
else if (derived.flags & SymbolFlags.Accessor) {
2998630023
errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor;
2998730024
}
2998830025
else {
@@ -30044,6 +30081,9 @@ namespace ts {
3004430081
}
3004530082
const constructor = findConstructorDeclaration(node);
3004630083
for (const member of node.members) {
30084+
if (getModifierFlags(member) & ModifierFlags.Ambient) {
30085+
continue;
30086+
}
3004730087
if (isInstancePropertyWithoutInitializer(member)) {
3004830088
const propName = (<PropertyDeclaration>member).name;
3004930089
if (isIdentifier(propName)) {
@@ -32970,7 +33010,7 @@ namespace ts {
3297033010
else if (flags & ModifierFlags.Async) {
3297133011
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async");
3297233012
}
32973-
else if (isClassLike(node.parent)) {
33013+
else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) {
3297433014
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_class_element, "declare");
3297533015
}
3297633016
else if (node.kind === SyntaxKind.Parameter) {

src/compiler/commandLineParser.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,13 @@ namespace ts {
873873
category: Diagnostics.Advanced_Options,
874874
description: Diagnostics.Disable_strict_checking_of_generic_signatures_in_function_types,
875875
},
876+
{
877+
name: "useDefineForClassFields",
878+
type: "boolean",
879+
affectsSemanticDiagnostics: true,
880+
category: Diagnostics.Advanced_Options,
881+
description: Diagnostics.Emit_class_fields_with_Define_instead_of_Set,
882+
},
876883
{
877884
name: "keyofStringsOnly",
878885
type: "boolean",

src/compiler/diagnosticMessages.json

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2233,6 +2233,19 @@
22332233
"category": "Error",
22342234
"code": 2609
22352235
},
2236+
"Class '{0}' defines instance member accessor '{1}', but extended class '{2}' defines it as instance member property.": {
2237+
"category": "Error",
2238+
"code": 2610
2239+
},
2240+
"Class '{0}' defines instance member property '{1}', but extended class '{2}' defines it as instance member accessor.": {
2241+
"category": "Error",
2242+
"code": 2611
2243+
},
2244+
"Property '{0}' will overwrite the base property in '{1}'. If this is intentional, add an initializer. Otherwise, add a 'declare' modifier or remove the redundant declaration.": {
2245+
"category": "Error",
2246+
"code": 2612
2247+
},
2248+
22362249
"Cannot augment module '{0}' with value exports because it resolves to a non-module entity.": {
22372250
"category": "Error",
22382251
"code": 2649
@@ -3112,6 +3125,10 @@
31123125
"category": "Error",
31133126
"code": 5047
31143127
},
3128+
"Option '{0}' cannot be specified when option 'target' is 'ES3'.": {
3129+
"category": "Error",
3130+
"code": 5048
3131+
},
31153132
"Option '{0} can only be used when either option '--inlineSourceMap' or option '--sourceMap' is provided.": {
31163133
"category": "Error",
31173134
"code": 5051
@@ -3966,6 +3983,10 @@
39663983
"Conflicts are in this file.": {
39673984
"category": "Message",
39683985
"code": 6201
3986+
},
3987+
"Project references may not form a circular graph. Cycle detected: {0}": {
3988+
"category": "Error",
3989+
"code": 6202
39693990
},
39703991
"'{0}' was also declared here.": {
39713992
"category": "Message",
@@ -4043,6 +4064,10 @@
40434064
"category": "Message",
40444065
"code": 6221
40454066
},
4067+
"Emit class fields with Define instead of Set.": {
4068+
"category": "Message",
4069+
"code": 6222
4070+
},
40464071

40474072
"Projects to reference": {
40484073
"category": "Message",
@@ -4052,10 +4077,7 @@
40524077
"category": "Message",
40534078
"code": 6302
40544079
},
4055-
"Project references may not form a circular graph. Cycle detected: {0}": {
4056-
"category": "Error",
4057-
"code": 6202
4058-
},
4080+
40594081
"Composite projects may not disable declaration emit.": {
40604082
"category": "Error",
40614083
"code": 6304
@@ -5180,6 +5202,14 @@
51805202
"category": "Message",
51815203
"code": 95093
51825204
},
5205+
"Prefix with 'declare'": {
5206+
"category": "Message",
5207+
"code": 95094
5208+
},
5209+
"Prefix all incorrect property declarations with 'declare'": {
5210+
"category": "Message",
5211+
"code": 95095
5212+
},
51835213

51845214
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
51855215
"category": "Error",

src/compiler/factory.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,47 @@ namespace ts {
484484
return node;
485485
}
486486

487+
function createMethodCall(object: Expression, methodName: string | Identifier, argumentsList: readonly Expression[]) {
488+
return createCall(
489+
createPropertyAccess(object, asName(methodName)),
490+
/*typeArguments*/ undefined,
491+
argumentsList
492+
);
493+
}
494+
495+
function createGlobalMethodCall(globalObjectName: string, methodName: string, argumentsList: readonly Expression[]) {
496+
return createMethodCall(createIdentifier(globalObjectName), methodName, argumentsList);
497+
}
498+
499+
/* @internal */
500+
export function createObjectDefinePropertyCall(target: Expression, propertyName: string | Expression, attributes: Expression) {
501+
return createGlobalMethodCall("Object", "defineProperty", [target, asExpression(propertyName), attributes]);
502+
}
503+
504+
function tryAddPropertyAssignment(properties: Push<PropertyAssignment>, propertyName: string, expression: Expression | undefined) {
505+
if (expression) {
506+
properties.push(createPropertyAssignment(propertyName, expression));
507+
return true;
508+
}
509+
return false;
510+
}
511+
512+
/* @internal */
513+
export function createPropertyDescriptor(attributes: PropertyDescriptorAttributes, singleLine?: boolean) {
514+
const properties: PropertyAssignment[] = [];
515+
tryAddPropertyAssignment(properties, "enumerable", asExpression(attributes.enumerable));
516+
tryAddPropertyAssignment(properties, "configurable", asExpression(attributes.configurable));
517+
518+
let isData = tryAddPropertyAssignment(properties, "writable", asExpression(attributes.writable));
519+
isData = tryAddPropertyAssignment(properties, "value", attributes.value) || isData;
520+
521+
let isAccessor = tryAddPropertyAssignment(properties, "get", attributes.get);
522+
isAccessor = tryAddPropertyAssignment(properties, "set", attributes.set) || isAccessor;
523+
524+
Debug.assert(!(isData && isAccessor), "A PropertyDescriptor may not be both an accessor descriptor and a data descriptor.");
525+
return createObjectLiteral(properties, !singleLine);
526+
}
527+
487528
export function updateMethod(
488529
node: MethodDeclaration,
489530
decorators: readonly Decorator[] | undefined,
@@ -3146,8 +3187,11 @@ namespace ts {
31463187
return isString(name) ? createIdentifier(name) : name;
31473188
}
31483189

3149-
function asExpression(value: string | number | Expression) {
3150-
return isString(value) || typeof value === "number" ? createLiteral(value) : value;
3190+
function asExpression<T extends Expression | undefined>(value: string | number | boolean | T): T | StringLiteral | NumericLiteral | BooleanLiteral {
3191+
return typeof value === "string" ? createStringLiteral(value) :
3192+
typeof value === "number" ? createNumericLiteral(""+value) :
3193+
typeof value === "boolean" ? value ? createTrue() : createFalse() :
3194+
value;
31513195
}
31523196

31533197
function asNodeArray<T extends Node>(array: readonly T[]): NodeArray<T>;

src/compiler/parser.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5995,8 +5995,16 @@ namespace ts {
59955995
token() === SyntaxKind.NumericLiteral ||
59965996
token() === SyntaxKind.AsteriskToken ||
59975997
token() === SyntaxKind.OpenBracketToken) {
5998-
5999-
return parsePropertyOrMethodDeclaration(<PropertyDeclaration | MethodDeclaration>node);
5998+
const isAmbient = node.modifiers && some(node.modifiers, isDeclareModifier);
5999+
if (isAmbient) {
6000+
for (const m of node.modifiers!) {
6001+
m.flags |= NodeFlags.Ambient;
6002+
}
6003+
return doInsideOfContext(NodeFlags.Ambient, () => parsePropertyOrMethodDeclaration(node as PropertyDeclaration | MethodDeclaration));
6004+
}
6005+
else {
6006+
return parsePropertyOrMethodDeclaration(node as PropertyDeclaration | MethodDeclaration);
6007+
}
60006008
}
60016009

60026010
if (node.decorators || node.modifiers) {

src/compiler/program.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3073,6 +3073,10 @@ namespace ts {
30733073
}
30743074
}
30753075

3076+
if (options.useDefineForClassFields && languageVersion === ScriptTarget.ES3) {
3077+
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_when_option_target_is_ES3, "useDefineForClassFields");
3078+
}
3079+
30763080
if (!options.noEmit && options.allowJs && getEmitDeclarations(options)) {
30773081
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", getEmitDeclarationOptionName(options));
30783082
}

0 commit comments

Comments
 (0)