Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e6aedfd

Browse filesBrowse files
Merge pull request microsoft#37907 from Jack-Works/feat/class-to-classname
feat: add a codefix to fix class to className in react & add spelling suggest for JSX attributes
2 parents 0476a1a + 8dc4f7e commit e6aedfd
Copy full SHA for e6aedfd

11 files changed

+314-14Lines changed: 314 additions & 14 deletions
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/compiler/checker.ts‎

Copy file name to clipboardExpand all lines: src/compiler/checker.ts
+21-4Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,7 @@ namespace ts {
606606
getAllPossiblePropertiesOfTypes,
607607
getSuggestedSymbolForNonexistentProperty,
608608
getSuggestionForNonexistentProperty,
609+
getSuggestedSymbolForNonexistentJSXAttribute,
609610
getSuggestedSymbolForNonexistentSymbol: (location, name, meaning) => getSuggestedSymbolForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning),
610611
getSuggestionForNonexistentSymbol: (location, name, meaning) => getSuggestionForNonexistentSymbol(location, escapeLeadingUnderscores(name), meaning),
611612
getSuggestedSymbolForNonexistentModule,
@@ -16313,18 +16314,25 @@ namespace ts {
1631316314
if (isJsxAttributes(errorNode) || isJsxOpeningLikeElement(errorNode) || isJsxOpeningLikeElement(errorNode.parent)) {
1631416315
// JsxAttributes has an object-literal flag and undergo same type-assignablity check as normal object-literal.
1631516316
// However, using an object-literal error message will be very confusing to the users so we give different a message.
16316-
// TODO: Spelling suggestions for excess jsx attributes (needs new diagnostic messages)
1631716317
if (prop.valueDeclaration && isJsxAttribute(prop.valueDeclaration) && getSourceFileOfNode(errorNode) === getSourceFileOfNode(prop.valueDeclaration.name)) {
1631816318
// Note that extraneous children (as in `<NoChild>extra</NoChild>`) don't pass this check,
1631916319
// since `children` is a SyntaxKind.PropertySignature instead of a SyntaxKind.JsxAttribute.
1632016320
errorNode = prop.valueDeclaration.name;
1632116321
}
16322-
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, symbolToString(prop), typeToString(errorTarget));
16322+
const propName = symbolToString(prop);
16323+
const suggestionSymbol = getSuggestedSymbolForNonexistentJSXAttribute(propName, errorTarget);
16324+
const suggestion = suggestionSymbol ? symbolToString(suggestionSymbol) : undefined;
16325+
if (suggestion) {
16326+
reportError(Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, propName, typeToString(errorTarget), suggestion);
16327+
}
16328+
else {
16329+
reportError(Diagnostics.Property_0_does_not_exist_on_type_1, propName, typeToString(errorTarget));
16330+
}
1632316331
}
1632416332
else {
1632516333
// use the property's value declaration if the property is assigned inside the literal itself
1632616334
const objectLiteralDeclaration = source.symbol && firstOrUndefined(source.symbol.declarations);
16327-
let suggestion;
16335+
let suggestion: string | undefined;
1632816336
if (prop.valueDeclaration && findAncestor(prop.valueDeclaration, d => d === objectLiteralDeclaration) && getSourceFileOfNode(objectLiteralDeclaration) === getSourceFileOfNode(errorNode)) {
1632916337
const propDeclaration = prop.valueDeclaration as ObjectLiteralElementLike;
1633016338
Debug.assertNode(propDeclaration, isObjectLiteralElementLike);
@@ -24877,6 +24885,15 @@ namespace ts {
2487724885
return getSpellingSuggestionForName(isString(name) ? name : idText(name), getPropertiesOfType(containingType), SymbolFlags.Value);
2487824886
}
2487924887

24888+
function getSuggestedSymbolForNonexistentJSXAttribute(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined {
24889+
const strName = isString(name) ? name : idText(name);
24890+
const properties = getPropertiesOfType(containingType);
24891+
const jsxSpecific = strName === "for" ? find(properties, x => symbolName(x) === "htmlFor")
24892+
: strName === "class" ? find(properties, x => symbolName(x) === "className")
24893+
: undefined;
24894+
return jsxSpecific ?? getSpellingSuggestionForName(strName, properties, SymbolFlags.Value);
24895+
}
24896+
2488024897
function getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined {
2488124898
const suggestion = getSuggestedSymbolForNonexistentProperty(name, containingType);
2488224899
return suggestion && symbolName(suggestion);
@@ -28223,7 +28240,7 @@ namespace ts {
2822328240
error(expr, Diagnostics.The_operand_of_a_delete_operator_must_be_a_property_reference);
2822428241
return booleanType;
2822528242
}
28226-
if (expr.kind === SyntaxKind.PropertyAccessExpression && isPrivateIdentifier(expr.name)) {
28243+
if (isPropertyAccessExpression(expr) && isPrivateIdentifier(expr.name)) {
2822728244
error(expr, Diagnostics.The_operand_of_a_delete_operator_cannot_be_a_private_identifier);
2822828245
}
2822928246
const links = getNodeLinks(expr);
Collapse file

‎src/compiler/types.ts‎

Copy file name to clipboardExpand all lines: src/compiler/types.ts
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4010,6 +4010,7 @@ namespace ts {
40104010
/* @internal */ tryGetMemberInModuleExportsAndProperties(memberName: string, moduleSymbol: Symbol): Symbol | undefined;
40114011
getApparentType(type: Type): Type;
40124012
/* @internal */ getSuggestedSymbolForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): Symbol | undefined;
4013+
/* @internal */ getSuggestedSymbolForNonexistentJSXAttribute(name: Identifier | string, containingType: Type): Symbol | undefined;
40134014
/* @internal */ getSuggestionForNonexistentProperty(name: Identifier | PrivateIdentifier | string, containingType: Type): string | undefined;
40144015
/* @internal */ getSuggestedSymbolForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): Symbol | undefined;
40154016
/* @internal */ getSuggestionForNonexistentSymbol(location: Node, name: string, meaning: SymbolFlags): string | undefined;
Collapse file

‎src/services/codefixes/fixSpelling.ts‎

Copy file name to clipboardExpand all lines: src/services/codefixes/fixSpelling.ts
+19-4Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,16 @@ namespace ts.codefix {
77
Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code,
88
Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0.code,
99
Diagnostics.Module_0_has_no_exported_member_1_Did_you_mean_2.code,
10+
// for JSX class components
11+
Diagnostics.No_overload_matches_this_call.code,
12+
// for JSX FC
13+
Diagnostics.Type_0_is_not_assignable_to_type_1.code,
1014
];
1115
registerCodeFix({
1216
errorCodes,
1317
getCodeActions(context) {
14-
const { sourceFile } = context;
15-
const info = getInfo(sourceFile, context.span.start, context);
18+
const { sourceFile, errorCode } = context;
19+
const info = getInfo(sourceFile, context.span.start, context, errorCode);
1620
if (!info) return undefined;
1721
const { node, suggestedSymbol } = info;
1822
const { target } = context.host.getCompilationSettings();
@@ -21,18 +25,23 @@ namespace ts.codefix {
2125
},
2226
fixIds: [fixId],
2327
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
24-
const info = getInfo(diag.file, diag.start, context);
28+
const info = getInfo(diag.file, diag.start, context, diag.code);
2529
const { target } = context.host.getCompilationSettings();
2630
if (info) doChange(changes, context.sourceFile, info.node, info.suggestedSymbol, target!);
2731
}),
2832
});
2933

30-
function getInfo(sourceFile: SourceFile, pos: number, context: CodeFixContextBase): { node: Node, suggestedSymbol: Symbol } | undefined {
34+
function getInfo(sourceFile: SourceFile, pos: number, context: CodeFixContextBase, errorCode: number): { node: Node, suggestedSymbol: Symbol } | undefined {
3135
// This is the identifier of the misspelled word. eg:
3236
// this.speling = 1;
3337
// ^^^^^^^
3438
const node = getTokenAtPosition(sourceFile, pos);
3539
const parent = node.parent;
40+
// Only fix spelling for No_overload_matches_this_call emitted on the React class component
41+
if ((
42+
errorCode === Diagnostics.No_overload_matches_this_call.code ||
43+
errorCode === Diagnostics.Type_0_is_not_assignable_to_type_1.code) &&
44+
!isJsxAttribute(parent)) return undefined;
3645
const checker = context.program.getTypeChecker();
3746

3847
let suggestedSymbol: Symbol | undefined;
@@ -52,6 +61,12 @@ namespace ts.codefix {
5261
suggestedSymbol = checker.getSuggestedSymbolForNonexistentModule(node, resolvedSourceFile.symbol);
5362
}
5463
}
64+
else if (isJsxAttribute(parent) && parent.name === node) {
65+
Debug.assertNode(node, isIdentifier, "Expected an identifier for JSX attribute");
66+
const tag = findAncestor(node, isJsxOpeningLikeElement)!;
67+
const props = checker.getContextualTypeForArgumentAtIndex(tag, 0);
68+
suggestedSymbol = checker.getSuggestedSymbolForNonexistentJSXAttribute(node, props!);
69+
}
5570
else {
5671
const meaning = getMeaningFromLocation(node);
5772
const name = getTextOfNode(node);
Collapse file
+79Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
tests/cases/compiler/spellingSuggestionJSXAttribute.tsx(8,4): error TS2322: Type '{ class: string; }' is not assignable to type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
2+
Property 'class' does not exist on type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'. Did you mean 'className'?
3+
tests/cases/compiler/spellingSuggestionJSXAttribute.tsx(9,4): error TS2322: Type '{ for: string; }' is not assignable to type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
4+
Property 'for' does not exist on type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
5+
tests/cases/compiler/spellingSuggestionJSXAttribute.tsx(10,8): error TS2322: Type '{ for: string; }' is not assignable to type 'DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>'.
6+
Property 'for' does not exist on type 'DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>'. Did you mean 'htmlFor'?
7+
tests/cases/compiler/spellingSuggestionJSXAttribute.tsx(11,8): error TS2322: Type '{ for: string; class: string; }' is not assignable to type 'DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>'.
8+
Property 'for' does not exist on type 'DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>'. Did you mean 'htmlFor'?
9+
tests/cases/compiler/spellingSuggestionJSXAttribute.tsx(12,9): error TS2769: No overload matches this call.
10+
Overload 1 of 2, '(props: Readonly<{ className?: string; htmlFor?: string; }>): MyComp', gave the following error.
11+
Type '{ class: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'.
12+
Property 'class' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'. Did you mean 'className'?
13+
Overload 2 of 2, '(props: { className?: string; htmlFor?: string; }, context?: any): MyComp', gave the following error.
14+
Type '{ class: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'.
15+
Property 'class' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'. Did you mean 'className'?
16+
tests/cases/compiler/spellingSuggestionJSXAttribute.tsx(13,10): error TS2322: Type '{ class: string; }' is not assignable to type 'IntrinsicAttributes & { className?: string; htmlFor?: string; }'.
17+
Property 'class' does not exist on type 'IntrinsicAttributes & { className?: string; htmlFor?: string; }'. Did you mean 'className'?
18+
tests/cases/compiler/spellingSuggestionJSXAttribute.tsx(14,9): error TS2769: No overload matches this call.
19+
Overload 1 of 2, '(props: Readonly<{ className?: string; htmlFor?: string; }>): MyComp', gave the following error.
20+
Type '{ for: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'.
21+
Property 'for' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'. Did you mean 'htmlFor'?
22+
Overload 2 of 2, '(props: { className?: string; htmlFor?: string; }, context?: any): MyComp', gave the following error.
23+
Type '{ for: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'.
24+
Property 'for' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'. Did you mean 'htmlFor'?
25+
tests/cases/compiler/spellingSuggestionJSXAttribute.tsx(15,10): error TS2322: Type '{ for: string; }' is not assignable to type 'IntrinsicAttributes & { className?: string; htmlFor?: string; }'.
26+
Property 'for' does not exist on type 'IntrinsicAttributes & { className?: string; htmlFor?: string; }'. Did you mean 'htmlFor'?
27+
28+
29+
==== tests/cases/compiler/spellingSuggestionJSXAttribute.tsx (8 errors) ====
30+
/// <reference path="/.lib/react16.d.ts" />
31+
import * as React from "react";
32+
33+
function MyComp2(props: { className?: string, htmlFor?: string }) {
34+
return null!;
35+
}
36+
class MyComp extends React.Component<{ className?: string, htmlFor?: string }> { }
37+
<a class="" />;
38+
~~~~~
39+
!!! error TS2322: Type '{ class: string; }' is not assignable to type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
40+
!!! error TS2322: Property 'class' does not exist on type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'. Did you mean 'className'?
41+
<a for="" />; // should have no fix
42+
~~~
43+
!!! error TS2322: Type '{ for: string; }' is not assignable to type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
44+
!!! error TS2322: Property 'for' does not exist on type 'DetailedHTMLProps<AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>'.
45+
<label for="" />;
46+
~~~
47+
!!! error TS2322: Type '{ for: string; }' is not assignable to type 'DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>'.
48+
!!! error TS2322: Property 'for' does not exist on type 'DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>'. Did you mean 'htmlFor'?
49+
<label for="" class="" />;
50+
~~~
51+
!!! error TS2322: Type '{ for: string; class: string; }' is not assignable to type 'DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>'.
52+
!!! error TS2322: Property 'for' does not exist on type 'DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>'. Did you mean 'htmlFor'?
53+
<MyComp class="" />;
54+
~~~~~
55+
!!! error TS2769: No overload matches this call.
56+
!!! error TS2769: Overload 1 of 2, '(props: Readonly<{ className?: string; htmlFor?: string; }>): MyComp', gave the following error.
57+
!!! error TS2769: Type '{ class: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'.
58+
!!! error TS2769: Property 'class' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'. Did you mean 'className'?
59+
!!! error TS2769: Overload 2 of 2, '(props: { className?: string; htmlFor?: string; }, context?: any): MyComp', gave the following error.
60+
!!! error TS2769: Type '{ class: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'.
61+
!!! error TS2769: Property 'class' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'. Did you mean 'className'?
62+
<MyComp2 class="" />;
63+
~~~~~
64+
!!! error TS2322: Type '{ class: string; }' is not assignable to type 'IntrinsicAttributes & { className?: string; htmlFor?: string; }'.
65+
!!! error TS2322: Property 'class' does not exist on type 'IntrinsicAttributes & { className?: string; htmlFor?: string; }'. Did you mean 'className'?
66+
<MyComp for="" />;
67+
~~~
68+
!!! error TS2769: No overload matches this call.
69+
!!! error TS2769: Overload 1 of 2, '(props: Readonly<{ className?: string; htmlFor?: string; }>): MyComp', gave the following error.
70+
!!! error TS2769: Type '{ for: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'.
71+
!!! error TS2769: Property 'for' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'. Did you mean 'htmlFor'?
72+
!!! error TS2769: Overload 2 of 2, '(props: { className?: string; htmlFor?: string; }, context?: any): MyComp', gave the following error.
73+
!!! error TS2769: Type '{ for: string; }' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'.
74+
!!! error TS2769: Property 'for' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<MyComp> & Readonly<{ children?: ReactNode; }> & Readonly<{ className?: string; htmlFor?: string; }>'. Did you mean 'htmlFor'?
75+
<MyComp2 for="" />;
76+
~~~
77+
!!! error TS2322: Type '{ for: string; }' is not assignable to type 'IntrinsicAttributes & { className?: string; htmlFor?: string; }'.
78+
!!! error TS2322: Property 'for' does not exist on type 'IntrinsicAttributes & { className?: string; htmlFor?: string; }'. Did you mean 'htmlFor'?
79+

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.