Skip to content

Commit 2f0c8b2

Browse files
--noImplicitOverride (#39669)
* wip: add types * wip * Add cases * Add some case * Add more check * accept baseline * add abstract abd declare method * add override in declaration * accept baseline * add property override * Fix decalre modifier * update baseline * Add more cases * make lint happy * make lint happy * Update description * Add codefix * simplify code * accept baseline * Update desc * Accept baseline * Add override completions * filter out implements field in override context * fix tests * Add parameter property check * Accept baseline * acept baseline * Add parameter property to declaration code action * Add quickfix for override parameter property * fix code style * Add override with interface tests * Add more cases about modifier position * rename flag * rename flags * Added tests. * Accepted baselines. * Always issue errors for unnecessary 'override' modifiers. * Accepted baselines. * Override perf (#4) * try cache check result * pre check for override * Do not issue error if implement abstract * Add abstract-spec check * Avoid override dead lock * Add more case * Add codefix for new error * Fix error message * Add jsdoc override tag (#6) * Override jsdoc tag (#7) * accept baseline * Disallow codefix in js * update baseline * Omit override in d.ts * Add more case in js * Accept baseline * fix override js test * fix crlf * Revert merge conflict changes * Accept baseline * Avoid space * Fix CR issues * Accept baseline * Fix typo and add more check * Fix error name Co-authored-by: Daniel Rosenwasser <[email protected]>
1 parent 41dc625 commit 2f0c8b2

File tree

131 files changed

+5281
-426
lines changed

Some content is hidden

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

131 files changed

+5281
-426
lines changed

src/compiler/checker.ts

+88-2
Original file line numberDiff line numberDiff line change
@@ -36664,7 +36664,8 @@ namespace ts {
3666436664
checkClassForDuplicateDeclarations(node);
3666536665

3666636666
// Only check for reserved static identifiers on non-ambient context.
36667-
if (!(node.flags & NodeFlags.Ambient)) {
36667+
const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient);
36668+
if (!nodeInAmbientContext) {
3666836669
checkClassForStaticPropertyNameConflicts(node);
3666936670
}
3667036671

@@ -36728,6 +36729,8 @@ namespace ts {
3672836729
}
3672936730
}
3673036731

36732+
checkMembersForMissingOverrideModifier(node, type, typeWithThis);
36733+
3673136734
const implementedTypeNodes = getEffectiveImplementsTypeNodes(node);
3673236735
if (implementedTypeNodes) {
3673336736
for (const typeRefNode of implementedTypeNodes) {
@@ -36763,6 +36766,60 @@ namespace ts {
3676336766
}
3676436767
}
3676536768

36769+
function checkMembersForMissingOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type) {
36770+
const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient);
36771+
const baseTypeNode = getEffectiveBaseTypeNode(node);
36772+
const baseTypes = baseTypeNode && getBaseTypes(type);
36773+
const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined;
36774+
36775+
for (const member of node.members) {
36776+
if (isConstructorDeclaration(member)) {
36777+
forEach(member.parameters, param => {
36778+
if (isParameterPropertyDeclaration(param, member)) {
36779+
checkClassMember(param, /*memberIsParameterProperty*/ true);
36780+
}
36781+
});
36782+
}
36783+
checkClassMember(member);
36784+
}
36785+
function checkClassMember(member: ClassElement | ParameterPropertyDeclaration, memberIsParameterProperty?: boolean) {
36786+
const hasOverride = hasOverrideModifier(member);
36787+
if (baseWithThis && (hasOverride || compilerOptions.noImplicitOverride)) {
36788+
const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member);
36789+
if (!declaredProp) {
36790+
return;
36791+
}
36792+
36793+
const baseClassName = typeToString(baseWithThis);
36794+
const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName);
36795+
const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName);
36796+
if (prop && !baseProp && hasOverride) {
36797+
error(member, Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, baseClassName);
36798+
}
36799+
else if (prop && baseProp?.valueDeclaration && compilerOptions.noImplicitOverride && !nodeInAmbientContext) {
36800+
const baseHasAbstract = hasAbstractModifier(baseProp.valueDeclaration);
36801+
if (hasOverride) {
36802+
return;
36803+
}
36804+
36805+
if (!baseHasAbstract) {
36806+
const diag = memberIsParameterProperty ?
36807+
Diagnostics.This_parameter_property_must_be_rewritten_as_a_property_declaration_with_an_override_modifier_because_it_overrides_a_member_in_base_class_0 :
36808+
Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0;
36809+
error(member, diag, baseClassName);
36810+
}
36811+
else if (hasAbstractModifier(member) && baseHasAbstract) {
36812+
error(member, Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_an_abstract_method_that_is_declared_in_the_base_class_0, baseClassName);
36813+
}
36814+
}
36815+
}
36816+
else if (hasOverride) {
36817+
const className = typeToString(type);
36818+
error(member, Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class, className);
36819+
}
36820+
}
36821+
}
36822+
3676636823
function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) {
3676736824
// iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible
3676836825
let issuedMemberError = false;
@@ -40137,7 +40194,7 @@ namespace ts {
4013740194
return quickResult;
4013840195
}
4013940196

40140-
let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined;
40197+
let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined, lastOverride: Node | undefined;
4014140198
let flags = ModifierFlags.None;
4014240199
for (const modifier of node.modifiers!) {
4014340200
if (modifier.kind !== SyntaxKind.ReadonlyKeyword) {
@@ -40154,6 +40211,23 @@ namespace ts {
4015440211
return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword));
4015540212
}
4015640213
break;
40214+
case SyntaxKind.OverrideKeyword:
40215+
if (flags & ModifierFlags.Override) {
40216+
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "override");
40217+
}
40218+
else if (flags & ModifierFlags.Ambient) {
40219+
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "override", "declare");
40220+
}
40221+
else if (flags & ModifierFlags.Static) {
40222+
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "override");
40223+
}
40224+
if (node.kind === SyntaxKind.Parameter) {
40225+
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "override");
40226+
}
40227+
flags |= ModifierFlags.Override;
40228+
lastOverride = modifier;
40229+
break;
40230+
4015740231
case SyntaxKind.PublicKeyword:
4015840232
case SyntaxKind.ProtectedKeyword:
4015940233
case SyntaxKind.PrivateKeyword:
@@ -40162,6 +40236,9 @@ namespace ts {
4016240236
if (flags & ModifierFlags.AccessibilityModifier) {
4016340237
return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen);
4016440238
}
40239+
else if (compilerOptions.noImplicitOverride && flags & ModifierFlags.Override) {
40240+
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "override");
40241+
}
4016540242
else if (flags & ModifierFlags.Static) {
4016640243
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static");
4016740244
}
@@ -40195,6 +40272,9 @@ namespace ts {
4019540272
else if (flags & ModifierFlags.Readonly) {
4019640273
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly");
4019740274
}
40275+
else if (compilerOptions.noImplicitOverride && flags & ModifierFlags.Override) {
40276+
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "override");
40277+
}
4019840278
else if (flags & ModifierFlags.Async) {
4019940279
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async");
4020040280
}
@@ -40259,6 +40339,9 @@ namespace ts {
4025940339
else if (flags & ModifierFlags.Async) {
4026040340
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async");
4026140341
}
40342+
else if (compilerOptions.noImplicitOverride && flags & ModifierFlags.Override) {
40343+
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "override");
40344+
}
4026240345
else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) {
4026340346
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "declare");
4026440347
}
@@ -40333,6 +40416,9 @@ namespace ts {
4033340416
if (flags & ModifierFlags.Abstract) {
4033440417
return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); // TODO: GH#18217
4033540418
}
40419+
if (compilerOptions.noImplicitOverride && flags & ModifierFlags.Override) {
40420+
return grammarErrorOnNode(lastOverride!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "override"); // TODO: GH#18217
40421+
}
4033640422
else if (flags & ModifierFlags.Async) {
4033740423
return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async");
4033840424
}

src/compiler/commandLineParser.ts

+8
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,14 @@ namespace ts {
675675
category: Diagnostics.Additional_Checks,
676676
description: Diagnostics.Include_undefined_in_index_signature_results
677677
},
678+
{
679+
name: "noImplicitOverride",
680+
type: "boolean",
681+
affectsSemanticDiagnostics: true,
682+
showInSimplifiedHelpView: false,
683+
category: Diagnostics.Additional_Checks,
684+
description: Diagnostics.Ensure_overriding_members_in_derived_classes_are_marked_with_an_override_modifier
685+
},
678686
{
679687
name: "noPropertyAccessFromIndexSignature",
680688
type: "boolean",

src/compiler/diagnosticMessages.json

+44-4
Original file line numberDiff line numberDiff line change
@@ -3685,6 +3685,26 @@
36853685
"category": "Error",
36863686
"code": 4111
36873687
},
3688+
"This member cannot have an 'override' modifier because its containing class '{0}' does not extend another class.": {
3689+
"category": "Error",
3690+
"code": 4112
3691+
},
3692+
"This member cannot have an 'override' modifier because it is not declared in the base class '{0}'.": {
3693+
"category": "Error",
3694+
"code": 4113
3695+
},
3696+
"This member must have an 'override' modifier because it overrides a member in the base class '{0}'.": {
3697+
"category": "Error",
3698+
"code": 4114
3699+
},
3700+
"This parameter property must be rewritten as a property declaration with an 'override' modifier because it overrides a member in base class '{0}'.": {
3701+
"category": "Error",
3702+
"code": 4115
3703+
},
3704+
"This member must have an 'override' modifier because it overrides an abstract method that is declared in the base class '{0}'.": {
3705+
"category": "Error",
3706+
"code": 4116
3707+
},
36883708

36893709
"The current host does not support the '{0}' option.": {
36903710
"category": "Error",
@@ -5018,15 +5038,19 @@
50185038
"category": "Message",
50195039
"code": 6505
50205040
},
5021-
"Require undeclared properties from index signatures to use element accesses.": {
5022-
"category": "Error",
5023-
"code": 6803
5024-
},
50255041

50265042
"Include 'undefined' in index signature results": {
50275043
"category": "Message",
50285044
"code": 6800
50295045
},
5046+
"Ensure overriding members in derived classes are marked with an 'override' modifier.": {
5047+
"category": "Message",
5048+
"code": 6801
5049+
},
5050+
"Require undeclared properties from index signatures to use element accesses.": {
5051+
"category": "Message",
5052+
"code": 6802
5053+
},
50305054

50315055
"Variable '{0}' implicitly has an '{1}' type.": {
50325056
"category": "Error",
@@ -6301,6 +6325,22 @@
63016325
"category": "Message",
63026326
"code": 95159
63036327
},
6328+
"Add 'override' modifier": {
6329+
"category": "Message",
6330+
"code": 95160
6331+
},
6332+
"Remove 'override' modifier": {
6333+
"category": "Message",
6334+
"code": 95161
6335+
},
6336+
"Add all missing 'override' modifiers": {
6337+
"category": "Message",
6338+
"code": 95162
6339+
},
6340+
"Remove all unnecessary 'override' modifiers": {
6341+
"category": "Message",
6342+
"code": 95163
6343+
},
63046344

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

src/compiler/factory/nodeFactory.ts

+4
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,8 @@ namespace ts {
368368
get updateJSDocProtectedTag() { return getJSDocSimpleTagUpdateFunction<JSDocProtectedTag>(SyntaxKind.JSDocProtectedTag); },
369369
get createJSDocReadonlyTag() { return getJSDocSimpleTagCreateFunction<JSDocReadonlyTag>(SyntaxKind.JSDocReadonlyTag); },
370370
get updateJSDocReadonlyTag() { return getJSDocSimpleTagUpdateFunction<JSDocReadonlyTag>(SyntaxKind.JSDocReadonlyTag); },
371+
get createJSDocOverrideTag() { return getJSDocSimpleTagCreateFunction<JSDocOverrideTag>(SyntaxKind.JSDocOverrideTag); },
372+
get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction<JSDocOverrideTag>(SyntaxKind.JSDocOverrideTag); },
371373
get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
372374
get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
373375
createJSDocUnknownTag,
@@ -1041,6 +1043,7 @@ namespace ts {
10411043
if (flags & ModifierFlags.Static) { result.push(createModifier(SyntaxKind.StaticKeyword)); }
10421044
if (flags & ModifierFlags.Readonly) { result.push(createModifier(SyntaxKind.ReadonlyKeyword)); }
10431045
if (flags & ModifierFlags.Async) { result.push(createModifier(SyntaxKind.AsyncKeyword)); }
1046+
if (flags & ModifierFlags.Override) { result.push(createModifier(SyntaxKind.OverrideKeyword)); }
10441047
return result;
10451048
}
10461049

@@ -5934,6 +5937,7 @@ namespace ts {
59345937
case SyntaxKind.JSDocPrivateTag: return "private";
59355938
case SyntaxKind.JSDocProtectedTag: return "protected";
59365939
case SyntaxKind.JSDocReadonlyTag: return "readonly";
5940+
case SyntaxKind.JSDocOverrideTag: return "override";
59375941
case SyntaxKind.JSDocTemplateTag: return "template";
59385942
case SyntaxKind.JSDocTypedefTag: return "typedef";
59395943
case SyntaxKind.JSDocParameterTag: return "param";

src/compiler/factory/nodeTests.ts

+4
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,10 @@ namespace ts {
852852
return node.kind === SyntaxKind.JSDocReadonlyTag;
853853
}
854854

855+
export function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag {
856+
return node.kind === SyntaxKind.JSDocOverrideTag;
857+
}
858+
855859
export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag {
856860
return node.kind === SyntaxKind.JSDocDeprecatedTag;
857861
}

src/compiler/parser.ts

+3
Original file line numberDiff line numberDiff line change
@@ -7542,6 +7542,9 @@ namespace ts {
75427542
case "readonly":
75437543
tag = parseSimpleTag(start, factory.createJSDocReadonlyTag, tagName, margin, indentText);
75447544
break;
7545+
case "override":
7546+
tag = parseSimpleTag(start, factory.createJSDocOverrideTag, tagName, margin, indentText);
7547+
break;
75457548
case "deprecated":
75467549
hasDeprecatedTag = true;
75477550
tag = parseSimpleTag(start, factory.createJSDocDeprecatedTag, tagName, margin, indentText);

src/compiler/program.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2148,6 +2148,7 @@ namespace ts {
21482148
case SyntaxKind.ReadonlyKeyword:
21492149
case SyntaxKind.DeclareKeyword:
21502150
case SyntaxKind.AbstractKeyword:
2151+
case SyntaxKind.OverrideKeyword:
21512152
diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind)));
21522153
break;
21532154

src/compiler/scanner.ts

+1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ namespace ts {
127127
private: SyntaxKind.PrivateKeyword,
128128
protected: SyntaxKind.ProtectedKeyword,
129129
public: SyntaxKind.PublicKeyword,
130+
override: SyntaxKind.OverrideKeyword,
130131
readonly: SyntaxKind.ReadonlyKeyword,
131132
require: SyntaxKind.RequireKeyword,
132133
global: SyntaxKind.GlobalKeyword,

src/compiler/transformers/declarations.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1540,7 +1540,7 @@ namespace ts {
15401540
}
15411541

15421542
function ensureModifierFlags(node: Node): ModifierFlags {
1543-
let mask = ModifierFlags.All ^ (ModifierFlags.Public | ModifierFlags.Async); // No async modifiers in declaration files
1543+
let mask = ModifierFlags.All ^ (ModifierFlags.Public | ModifierFlags.Async | ModifierFlags.Override); // No async and override modifiers in declaration files
15441544
let additions = (needsDeclare && !isAlwaysType(node)) ? ModifierFlags.Ambient : ModifierFlags.None;
15451545
const parentIsFile = node.parent.kind === SyntaxKind.SourceFile;
15461546
if (!parentIsFile || (isBundledEmit && parentIsFile && isExternalModule(node.parent as SourceFile))) {

0 commit comments

Comments
 (0)