Skip to content

Commit

Permalink
--noImplicitOverride (#39669)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
Kingwl and DanielRosenwasser authored Mar 26, 2021
1 parent 41dc625 commit 2f0c8b2
Show file tree
Hide file tree
Showing 131 changed files with 5,281 additions and 426 deletions.
90 changes: 88 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36664,7 +36664,8 @@ namespace ts {
checkClassForDuplicateDeclarations(node);

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

Expand Down Expand Up @@ -36728,6 +36729,8 @@ namespace ts {
}
}

checkMembersForMissingOverrideModifier(node, type, typeWithThis);

const implementedTypeNodes = getEffectiveImplementsTypeNodes(node);
if (implementedTypeNodes) {
for (const typeRefNode of implementedTypeNodes) {
Expand Down Expand Up @@ -36763,6 +36766,60 @@ namespace ts {
}
}

function checkMembersForMissingOverrideModifier(node: ClassLikeDeclaration, type: InterfaceType, typeWithThis: Type) {
const nodeInAmbientContext = !!(node.flags & NodeFlags.Ambient);
const baseTypeNode = getEffectiveBaseTypeNode(node);
const baseTypes = baseTypeNode && getBaseTypes(type);
const baseWithThis = baseTypes?.length ? getTypeWithThisArgument(first(baseTypes), type.thisType) : undefined;

for (const member of node.members) {
if (isConstructorDeclaration(member)) {
forEach(member.parameters, param => {
if (isParameterPropertyDeclaration(param, member)) {
checkClassMember(param, /*memberIsParameterProperty*/ true);
}
});
}
checkClassMember(member);
}
function checkClassMember(member: ClassElement | ParameterPropertyDeclaration, memberIsParameterProperty?: boolean) {
const hasOverride = hasOverrideModifier(member);
if (baseWithThis && (hasOverride || compilerOptions.noImplicitOverride)) {
const declaredProp = member.name && getSymbolAtLocation(member.name) || getSymbolAtLocation(member);
if (!declaredProp) {
return;
}

const baseClassName = typeToString(baseWithThis);
const prop = getPropertyOfType(typeWithThis, declaredProp.escapedName);
const baseProp = getPropertyOfType(baseWithThis, declaredProp.escapedName);
if (prop && !baseProp && hasOverride) {
error(member, Diagnostics.This_member_cannot_have_an_override_modifier_because_it_is_not_declared_in_the_base_class_0, baseClassName);
}
else if (prop && baseProp?.valueDeclaration && compilerOptions.noImplicitOverride && !nodeInAmbientContext) {
const baseHasAbstract = hasAbstractModifier(baseProp.valueDeclaration);
if (hasOverride) {
return;
}

if (!baseHasAbstract) {
const diag = memberIsParameterProperty ?
Diagnostics.This_parameter_property_must_be_rewritten_as_a_property_declaration_with_an_override_modifier_because_it_overrides_a_member_in_base_class_0 :
Diagnostics.This_member_must_have_an_override_modifier_because_it_overrides_a_member_in_the_base_class_0;
error(member, diag, baseClassName);
}
else if (hasAbstractModifier(member) && baseHasAbstract) {
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);
}
}
}
else if (hasOverride) {
const className = typeToString(type);
error(member, Diagnostics.This_member_cannot_have_an_override_modifier_because_its_containing_class_0_does_not_extend_another_class, className);
}
}
}

function issueMemberSpecificError(node: ClassLikeDeclaration, typeWithThis: Type, baseWithThis: Type, broadDiag: DiagnosticMessage) {
// iterate over all implemented properties and issue errors on each one which isn't compatible, rather than the class as a whole, if possible
let issuedMemberError = false;
Expand Down Expand Up @@ -40137,7 +40194,7 @@ namespace ts {
return quickResult;
}

let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined;
let lastStatic: Node | undefined, lastDeclare: Node | undefined, lastAsync: Node | undefined, lastReadonly: Node | undefined, lastOverride: Node | undefined;
let flags = ModifierFlags.None;
for (const modifier of node.modifiers!) {
if (modifier.kind !== SyntaxKind.ReadonlyKeyword) {
Expand All @@ -40154,6 +40211,23 @@ namespace ts {
return grammarErrorOnNode(node, Diagnostics.A_class_member_cannot_have_the_0_keyword, tokenToString(SyntaxKind.ConstKeyword));
}
break;
case SyntaxKind.OverrideKeyword:
if (flags & ModifierFlags.Override) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "override");
}
else if (flags & ModifierFlags.Ambient) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "override", "declare");
}
else if (flags & ModifierFlags.Static) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "override");
}
if (node.kind === SyntaxKind.Parameter) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_a_parameter, "override");
}
flags |= ModifierFlags.Override;
lastOverride = modifier;
break;

case SyntaxKind.PublicKeyword:
case SyntaxKind.ProtectedKeyword:
case SyntaxKind.PrivateKeyword:
Expand All @@ -40162,6 +40236,9 @@ namespace ts {
if (flags & ModifierFlags.AccessibilityModifier) {
return grammarErrorOnNode(modifier, Diagnostics.Accessibility_modifier_already_seen);
}
else if (compilerOptions.noImplicitOverride && flags & ModifierFlags.Override) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "override");
}
else if (flags & ModifierFlags.Static) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, text, "static");
}
Expand Down Expand Up @@ -40195,6 +40272,9 @@ namespace ts {
else if (flags & ModifierFlags.Readonly) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "readonly");
}
else if (compilerOptions.noImplicitOverride && flags & ModifierFlags.Override) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_with_1_modifier, "static", "override");
}
else if (flags & ModifierFlags.Async) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_must_precede_1_modifier, "static", "async");
}
Expand Down Expand Up @@ -40259,6 +40339,9 @@ namespace ts {
else if (flags & ModifierFlags.Async) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "async");
}
else if (compilerOptions.noImplicitOverride && flags & ModifierFlags.Override) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_be_used_in_an_ambient_context, "override");
}
else if (isClassLike(node.parent) && !isPropertyDeclaration(node)) {
return grammarErrorOnNode(modifier, Diagnostics._0_modifier_cannot_appear_on_class_elements_of_this_kind, "declare");
}
Expand Down Expand Up @@ -40333,6 +40416,9 @@ namespace ts {
if (flags & ModifierFlags.Abstract) {
return grammarErrorOnNode(lastStatic!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "abstract"); // TODO: GH#18217
}
if (compilerOptions.noImplicitOverride && flags & ModifierFlags.Override) {
return grammarErrorOnNode(lastOverride!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "override"); // TODO: GH#18217
}
else if (flags & ModifierFlags.Async) {
return grammarErrorOnNode(lastAsync!, Diagnostics._0_modifier_cannot_appear_on_a_constructor_declaration, "async");
}
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,14 @@ namespace ts {
category: Diagnostics.Additional_Checks,
description: Diagnostics.Include_undefined_in_index_signature_results
},
{
name: "noImplicitOverride",
type: "boolean",
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: false,
category: Diagnostics.Additional_Checks,
description: Diagnostics.Ensure_overriding_members_in_derived_classes_are_marked_with_an_override_modifier
},
{
name: "noPropertyAccessFromIndexSignature",
type: "boolean",
Expand Down
48 changes: 44 additions & 4 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3685,6 +3685,26 @@
"category": "Error",
"code": 4111
},
"This member cannot have an 'override' modifier because its containing class '{0}' does not extend another class.": {
"category": "Error",
"code": 4112
},
"This member cannot have an 'override' modifier because it is not declared in the base class '{0}'.": {
"category": "Error",
"code": 4113
},
"This member must have an 'override' modifier because it overrides a member in the base class '{0}'.": {
"category": "Error",
"code": 4114
},
"This parameter property must be rewritten as a property declaration with an 'override' modifier because it overrides a member in base class '{0}'.": {
"category": "Error",
"code": 4115
},
"This member must have an 'override' modifier because it overrides an abstract method that is declared in the base class '{0}'.": {
"category": "Error",
"code": 4116
},

"The current host does not support the '{0}' option.": {
"category": "Error",
Expand Down Expand Up @@ -5018,15 +5038,19 @@
"category": "Message",
"code": 6505
},
"Require undeclared properties from index signatures to use element accesses.": {
"category": "Error",
"code": 6803
},

"Include 'undefined' in index signature results": {
"category": "Message",
"code": 6800
},
"Ensure overriding members in derived classes are marked with an 'override' modifier.": {
"category": "Message",
"code": 6801
},
"Require undeclared properties from index signatures to use element accesses.": {
"category": "Message",
"code": 6802
},

"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
Expand Down Expand Up @@ -6301,6 +6325,22 @@
"category": "Message",
"code": 95159
},
"Add 'override' modifier": {
"category": "Message",
"code": 95160
},
"Remove 'override' modifier": {
"category": "Message",
"code": 95161
},
"Add all missing 'override' modifiers": {
"category": "Message",
"code": 95162
},
"Remove all unnecessary 'override' modifiers": {
"category": "Message",
"code": 95163
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,8 @@ namespace ts {
get updateJSDocProtectedTag() { return getJSDocSimpleTagUpdateFunction<JSDocProtectedTag>(SyntaxKind.JSDocProtectedTag); },
get createJSDocReadonlyTag() { return getJSDocSimpleTagCreateFunction<JSDocReadonlyTag>(SyntaxKind.JSDocReadonlyTag); },
get updateJSDocReadonlyTag() { return getJSDocSimpleTagUpdateFunction<JSDocReadonlyTag>(SyntaxKind.JSDocReadonlyTag); },
get createJSDocOverrideTag() { return getJSDocSimpleTagCreateFunction<JSDocOverrideTag>(SyntaxKind.JSDocOverrideTag); },
get updateJSDocOverrideTag() { return getJSDocSimpleTagUpdateFunction<JSDocOverrideTag>(SyntaxKind.JSDocOverrideTag); },
get createJSDocDeprecatedTag() { return getJSDocSimpleTagCreateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
get updateJSDocDeprecatedTag() { return getJSDocSimpleTagUpdateFunction<JSDocDeprecatedTag>(SyntaxKind.JSDocDeprecatedTag); },
createJSDocUnknownTag,
Expand Down Expand Up @@ -1041,6 +1043,7 @@ namespace ts {
if (flags & ModifierFlags.Static) { result.push(createModifier(SyntaxKind.StaticKeyword)); }
if (flags & ModifierFlags.Readonly) { result.push(createModifier(SyntaxKind.ReadonlyKeyword)); }
if (flags & ModifierFlags.Async) { result.push(createModifier(SyntaxKind.AsyncKeyword)); }
if (flags & ModifierFlags.Override) { result.push(createModifier(SyntaxKind.OverrideKeyword)); }
return result;
}

Expand Down Expand Up @@ -5934,6 +5937,7 @@ namespace ts {
case SyntaxKind.JSDocPrivateTag: return "private";
case SyntaxKind.JSDocProtectedTag: return "protected";
case SyntaxKind.JSDocReadonlyTag: return "readonly";
case SyntaxKind.JSDocOverrideTag: return "override";
case SyntaxKind.JSDocTemplateTag: return "template";
case SyntaxKind.JSDocTypedefTag: return "typedef";
case SyntaxKind.JSDocParameterTag: return "param";
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/factory/nodeTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,10 @@ namespace ts {
return node.kind === SyntaxKind.JSDocReadonlyTag;
}

export function isJSDocOverrideTag(node: Node): node is JSDocOverrideTag {
return node.kind === SyntaxKind.JSDocOverrideTag;
}

export function isJSDocDeprecatedTag(node: Node): node is JSDocDeprecatedTag {
return node.kind === SyntaxKind.JSDocDeprecatedTag;
}
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7542,6 +7542,9 @@ namespace ts {
case "readonly":
tag = parseSimpleTag(start, factory.createJSDocReadonlyTag, tagName, margin, indentText);
break;
case "override":
tag = parseSimpleTag(start, factory.createJSDocOverrideTag, tagName, margin, indentText);
break;
case "deprecated":
hasDeprecatedTag = true;
tag = parseSimpleTag(start, factory.createJSDocDeprecatedTag, tagName, margin, indentText);
Expand Down
1 change: 1 addition & 0 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2148,6 +2148,7 @@ namespace ts {
case SyntaxKind.ReadonlyKeyword:
case SyntaxKind.DeclareKeyword:
case SyntaxKind.AbstractKeyword:
case SyntaxKind.OverrideKeyword:
diagnostics.push(createDiagnosticForNode(modifier, Diagnostics.The_0_modifier_can_only_be_used_in_TypeScript_files, tokenToString(modifier.kind)));
break;

Expand Down
1 change: 1 addition & 0 deletions src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ namespace ts {
private: SyntaxKind.PrivateKeyword,
protected: SyntaxKind.ProtectedKeyword,
public: SyntaxKind.PublicKeyword,
override: SyntaxKind.OverrideKeyword,
readonly: SyntaxKind.ReadonlyKeyword,
require: SyntaxKind.RequireKeyword,
global: SyntaxKind.GlobalKeyword,
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1540,7 +1540,7 @@ namespace ts {
}

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

0 comments on commit 2f0c8b2

Please sign in to comment.