mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-26 20:14:05 -05:00
feat: add a codefix to fix class to className in react
This commit is contained in:
60
src/services/codefixes/fixReactClassNameAndHTMLFor.ts
Normal file
60
src/services/codefixes/fixReactClassNameAndHTMLFor.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/* @internal */
|
||||
namespace ts.codefix {
|
||||
const fixID = "fixReactClassNameAndHTMLFor";
|
||||
const errorCodes = [Diagnostics.Type_0_is_not_assignable_to_type_1.code];
|
||||
registerCodeFix({
|
||||
errorCodes,
|
||||
getCodeActions: context => {
|
||||
const { jsx } = context.program.getCompilerOptions();
|
||||
if (jsx !== JsxEmit.React) {
|
||||
return undefined;
|
||||
}
|
||||
const { sourceFile, span } = context;
|
||||
const node = getTokenAtPosition(sourceFile, span.start);
|
||||
if (!shouldFix(node)) return undefined;
|
||||
const changes = textChanges.ChangeTracker.with(context, t => doChange(t, sourceFile, node));
|
||||
return [createCodeFixAction(fixID, changes, [Diagnostics.Did_you_mean_0, getCorrectName(node)], fixID, Diagnostics.Fix_all_detected_spelling_errors)];
|
||||
},
|
||||
fixIds: [fixID],
|
||||
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, diag) => {
|
||||
const node = getTokenAtPosition(context.sourceFile, diag.start);
|
||||
if (!shouldFix(node)) return;
|
||||
doChange(changes, context.sourceFile, node);
|
||||
}),
|
||||
});
|
||||
|
||||
function doChange(changeTracker: textChanges.ChangeTracker, sf: SourceFile, node: Identifier) {
|
||||
changeTracker.replaceNode(sf, node, createIdentifier(getCorrectName(node)));
|
||||
}
|
||||
|
||||
function getCorrectName(node: Identifier) {
|
||||
const text = node.text;
|
||||
if (text === "class") return "className";
|
||||
if (text === "for") return "htmlFor";
|
||||
return Debug.fail();
|
||||
}
|
||||
|
||||
function shouldFix(node: Node): node is Identifier {
|
||||
// <label for="content">, the error is on the `for`
|
||||
if (!isIdentifier(node)) return false;
|
||||
|
||||
const id = node.text;
|
||||
// Only fix class => className and for => htmlFor
|
||||
if (id !== "for" && id !== "class") return false;
|
||||
|
||||
if (!isJsxAttribute(node.parent)) return false;
|
||||
const parent = node.parent.parent.parent;
|
||||
if (!isIdentifier(parent.tagName)) return false;
|
||||
const tagName = parent.tagName.text;
|
||||
// html for only appear on label tag
|
||||
if (tagName !== "label" && id === "for") return false;
|
||||
const firstChar = tagName[0];
|
||||
// Only fix for html elements. Non-lowercase elements are React Elements
|
||||
if (firstChar.toLowerCase() !== firstChar) return false;
|
||||
|
||||
// See https://reactjs.org/docs/web-components.html#using-web-components-in-react
|
||||
// "One common confusion is that Web Components use “class” instead of “className”."
|
||||
if (tagName.indexOf("-") !== -1 && id === "class") return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,7 @@
|
||||
"codefixes/fixPropertyOverrideAccessor.ts",
|
||||
"codefixes/inferFromUsage.ts",
|
||||
"codefixes/fixReturnTypeInAsyncFunction.ts",
|
||||
"codefixes/fixReactClassNameAndHTMLFor.ts",
|
||||
"codefixes/disableJsDiagnostics.ts",
|
||||
"codefixes/helpers.ts",
|
||||
"codefixes/generateAccessors.ts",
|
||||
|
||||
15
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor1.ts
Normal file
15
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor1.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @jsx: react
|
||||
// @Filename: /a.tsx
|
||||
////declare namespace JSX {
|
||||
//// interface Element {}
|
||||
//// interface IntrinsicElements {
|
||||
//// div: {
|
||||
//// className?: string
|
||||
//// }
|
||||
//// }
|
||||
////}
|
||||
////[|const div = <div class="a" />|]
|
||||
|
||||
verify.rangeAfterCodeFix(`const div = <div className="a" />`, /*includeWhiteSpace*/false, /*errorCode*/ 2322, /*index*/ 0);
|
||||
15
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor2.ts
Normal file
15
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor2.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @jsx: react
|
||||
// @Filename: /a.tsx
|
||||
////declare namespace JSX {
|
||||
//// interface Element {}
|
||||
//// interface IntrinsicElements {
|
||||
//// label: {
|
||||
//// htmlFor?: string
|
||||
//// }
|
||||
//// }
|
||||
////}
|
||||
////[|<label for="a" />|]
|
||||
|
||||
verify.rangeAfterCodeFix(`<label htmlFor="a" />`, /*includeWhiteSpace*/false, /*errorCode*/ 2322, /*index*/ 0);
|
||||
16
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor3-1.ts
Normal file
16
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor3-1.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @jsx: react
|
||||
// @Filename: /a.tsx
|
||||
////<div for="a" />;
|
||||
////declare namespace JSX {
|
||||
//// interface Element {}
|
||||
//// interface IntrinsicElements {
|
||||
//// div: {
|
||||
//// className?: string
|
||||
//// }
|
||||
//// 'my-tag': { className?: string }
|
||||
//// }
|
||||
////}
|
||||
|
||||
verify.not.codeFixAvailable("fixReactClassNameAndHTMLFor");
|
||||
17
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor3-2.ts
Normal file
17
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor3-2.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @jsx: react
|
||||
// @Filename: /a.tsx
|
||||
////function MyComponent(props: {className?: string}) {}
|
||||
////<MyComponent class="a" />;
|
||||
////declare namespace JSX {
|
||||
//// interface Element {}
|
||||
//// interface IntrinsicElements {
|
||||
//// div: {
|
||||
//// className?: string
|
||||
//// }
|
||||
//// 'my-tag': { className?: string }
|
||||
//// }
|
||||
////}
|
||||
|
||||
verify.not.codeFixAvailable("fixReactClassNameAndHTMLFor");
|
||||
16
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor3-3.ts
Normal file
16
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor3-3.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @jsx: react
|
||||
// @Filename: /a.tsx
|
||||
////<my-tag class="a" />;
|
||||
////declare namespace JSX {
|
||||
//// interface Element {}
|
||||
//// interface IntrinsicElements {
|
||||
//// div: {
|
||||
//// className?: string
|
||||
//// }
|
||||
//// 'my-tag': { className?: string }
|
||||
//// }
|
||||
////}
|
||||
|
||||
verify.not.codeFixAvailable("fixReactClassNameAndHTMLFor");
|
||||
30
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor4.ts
Normal file
30
tests/cases/fourslash/codeFixReactClassNameAndHTMLFor4.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @jsx: react
|
||||
// @Filename: /a.tsx
|
||||
////declare namespace JSX {
|
||||
////interface Element {}
|
||||
////interface IntrinsicElements {
|
||||
////div: { className?: string }
|
||||
////label: { htmlFor?: string }
|
||||
////}}
|
||||
////<div class="a" />;
|
||||
////<label for="a" />;
|
||||
////<div class="a" />;
|
||||
////<div class="a" id="a" />;
|
||||
////<div id="b" />;
|
||||
|
||||
verify.codeFixAll({
|
||||
newFileContent: `declare namespace JSX {
|
||||
interface Element {}
|
||||
interface IntrinsicElements {
|
||||
div: { className?: string }
|
||||
label: { htmlFor?: string }
|
||||
}}
|
||||
<div className="a" />;
|
||||
<label htmlFor="a" />;
|
||||
<div className="a" />;
|
||||
<div className="a" id="a" />;
|
||||
<div id="b" />;`, fixId: "fixReactClassNameAndHTMLFor",
|
||||
fixAllDescription: "Fix all detected spelling errors"
|
||||
});
|
||||
Reference in New Issue
Block a user