feat: add a codefix to fix class to className in react

This commit is contained in:
Jack Works
2020-04-11 22:35:26 +08:00
parent 59ad375234
commit 9fe25ca077
8 changed files with 170 additions and 0 deletions

View 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;
}
}

View File

@@ -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",

View 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);

View 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);

View 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");

View 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");

View 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");

View 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"
});