Est*_*ask 6 javascript typescript reactjs
有React + TypeScript应用程序,所有组件类都应该是大写的并且有Component后缀,例如:
export class FooBarComponent extends React.Component {...}
Run Code Online (Sandbox Code Playgroud)
应用程序被弹出create-react-application应用程序,即使用Webpack构建.
如何强制组件命名与样式指南一致,至少对于组件类,如果存在不一致,则会在构建时抛出错误?
我相信单凭TSLint/ESLint无法实现这一点.如果TypeScript和JavaScript应该使用不同的方法,那么两种语言的解决方案都会有所帮助.
yur*_*zui 10
我只能为你提供打字稿的解决方案.
我相信单凭TSLint/ESLint无法实现这一点.
有一个所谓的规则类名称可以部分解决您的问题,但似乎您需要为这种情况编写自定义规则.
所以让我们尝试编写这样的自定义tslint规则.为此,我们需要rulesDirectory在tslint配置中使用选项来指定自定义规则的路径
"rulesDirectory": [
"./tools/tslint-rules/"
],
Run Code Online (Sandbox Code Playgroud)
由于我将在打字稿中编写自定义规则,因此我将使用tslint@5.7.0中添加的一个功能
[增强]自定义lint规则将使用节点的路径解析来解析,以允许像ts节点这样的加载器(#3108)
我们需要安装ts-node包
npm i -D ts-node
Run Code Online (Sandbox Code Playgroud)
然后在tslint.json中添加假规则
"ts-loader": true,
Run Code Online (Sandbox Code Playgroud)
并tsLoaderRule.js在我们的rulesDirectory中创建文件:
const path = require('path');
const Lint = require('tslint');
// Custom rule that registers all of the custom rules, written in TypeScript, with ts-node.
// This is necessary, because `tslint` and IDEs won't execute any rules that aren't in a .js file.
require('ts-node').register({
project: path.join(__dirname, '../tsconfig.json')
});
// Add a noop rule so tslint doesn't complain.
exports.Rule = class Rule extends Lint.Rules.AbstractRule {
apply() {}
};
Run Code Online (Sandbox Code Playgroud)
这基本上是广泛用于角形包装,如角形材料,通用等的方法
现在我们可以创建将用typescript编写的自定义规则(类名规则的扩展版本).
myReactComponentRule.ts
import * as ts from 'typescript';
import * as Lint from 'tslint';
export class Rule extends Lint.Rules.AbstractRule {
/* tslint:disable:object-literal-sort-keys */
static metadata: Lint.IRuleMetadata = {
ruleName: 'my-react-component',
description: 'Enforces PascalCased React component class.',
rationale: 'Makes it easy to differentiate classes from regular variables at a glance.',
optionsDescription: 'Not configurable.',
options: null,
optionExamples: [true],
type: 'style',
typescriptOnly: false,
};
/* tslint:enable:object-literal-sort-keys */
static FAILURE_STRING = (className: string) => `React component ${className} must be PascalCased and prefixed by Component`;
static validate(name: string): boolean {
return isUpperCase(name[0]) && !name.includes('_') && name.endsWith('Component');
}
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk);
}
}
function walk(ctx: Lint.WalkContext<void>) {
return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void {
if (isClassLikeDeclaration(node) && node.name !== undefined && isReactComponent(node)) {
if (!Rule.validate(node.name!.text)) {
ctx.addFailureAtNode(node.name!, Rule.FAILURE_STRING(node.name!.text));
}
}
return ts.forEachChild(node, cb);
});
}
function isClassLikeDeclaration(node: ts.Node): node is ts.ClassLikeDeclaration {
return node.kind === ts.SyntaxKind.ClassDeclaration ||
node.kind === ts.SyntaxKind.ClassExpression;
}
function isReactComponent(node: ts.Node): boolean {
let result = false;
const classDeclaration = <ts.ClassDeclaration> node;
if (classDeclaration.heritageClauses) {
classDeclaration.heritageClauses.forEach((hc) => {
if (hc.token === ts.SyntaxKind.ExtendsKeyword && hc.types) {
hc.types.forEach(type => {
if (type.getText() === 'React.Component') {
result = true;
}
});
}
});
}
return result;
}
function isUpperCase(str: string): boolean {
return str === str.toUpperCase();
}
Run Code Online (Sandbox Code Playgroud)
最后我们应该把我们的新规则放到tsling.json:
// Custom rules
"ts-loader": true,
"my-react-component": true
Run Code Online (Sandbox Code Playgroud)
所以像这样的代码
App extends React.Component
Run Code Online (Sandbox Code Playgroud)
将导致:
我还创建了弹出的react-ts应用程序,您可以尝试它.
我想在爷爷奶奶中跟踪班级名称不是一项微不足道的任务
确实,我们可以处理继承.为此,我们需要从类扩展的创建规则Lint.Rules.TypedRule才能访问TypeChecker:
myReactComponentRule.ts
import * as ts from 'typescript';
import * as Lint from 'tslint';
export class Rule extends Lint.Rules.TypedRule {
/* tslint:disable:object-literal-sort-keys */
static metadata: Lint.IRuleMetadata = {
ruleName: 'my-react-component',
description: 'Enforces PascalCased React component class.',
rationale: 'Makes it easy to differentiate classes from regular variables at a glance.',
optionsDescription: 'Not configurable.',
options: null,
optionExamples: [true],
type: 'style',
typescriptOnly: false,
};
/* tslint:enable:object-literal-sort-keys */
static FAILURE_STRING = (className: string) =>
`React component ${className} must be PascalCased and prefixed by Component`;
static validate(name: string): boolean {
return isUpperCase(name[0]) && !name.includes('_') && name.endsWith('Component');
}
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
return this.applyWithFunction(sourceFile, walk, undefined, program.getTypeChecker());
}
}
function walk(ctx: Lint.WalkContext<void>, tc: ts.TypeChecker) {
return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void {
if (
isClassLikeDeclaration(node) && node.name !== undefined &&
containsType(tc.getTypeAtLocation(node), isReactComponentType) &&
!Rule.validate(node.name!.text)) {
ctx.addFailureAtNode(node.name!, Rule.FAILURE_STRING(node.name!.text));
}
return ts.forEachChild(node, cb);
});
}
/* tslint:disable:no-any */
function containsType(type: ts.Type, predicate: (symbol: any) => boolean): boolean {
if (type.symbol !== undefined && predicate(type.symbol)) {
return true;
}
const bases = type.getBaseTypes();
return bases && bases.some((t) => containsType(t, predicate));
}
function isReactComponentType(symbol: any) {
return symbol.name === 'Component' && symbol.parent && symbol.parent.name === 'React';
}
/* tslint:enable:no-any */
function isClassLikeDeclaration(node: ts.Node): node is ts.ClassLikeDeclaration {
return node.kind === ts.SyntaxKind.ClassDeclaration ||
node.kind === ts.SyntaxKind.ClassExpression;
}
function isUpperCase(str: string): boolean {
return str === str.toUpperCase();
}
Run Code Online (Sandbox Code Playgroud)
另见commit:
这样做更容易eslint.自定义插件不那么复杂.所以我创建了一个展示相同的插件.为了测试插件,我创建了以下文件
import React from "react"
class ABCComponent extends React.Component {
}
class ABC2component extends React.Component {
}
class TestComponent {
}
class FooBarComponent extends React.Component {
}
class fooBazComponent extends React.Component {
}
class FooBazing extends React.Component {
}
Run Code Online (Sandbox Code Playgroud)
然后在同一个插件上运行
我在编写插件时遵循了以下指南
https://flexport.engineering/writing-custom-lint-rules-for-your-picky-developers-67732afa1803
https://www.kenneth-truyers.net/2016/05/27/writing-custom-eslint-rules/
https://eslint.org/docs/developer-guide/working-with-rules
我提出的最终代码是下面的规则
/**
* @fileoverview Check that proper naming convention is followed for React components
* @author Tarun Lalwani
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
var toPascalCase = require('to-pascal-case');
module.exports = {
meta: {
docs: {
description: "Check that proper naming convention is followed for React components",
category: "Fill me in",
recommended: false
},
fixable: "code", // or "code" or "whitespace"
schema: [
// fill in your schema
]
},
create: function(context) {
// variables should be defined here
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
// any helper functions should go here or else delete this section
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
ClassDeclaration: function(node) {
var isReactComponent = false;
if (node.superClass && node.superClass && node.superClass)
{
if (node.superClass.object && node.superClass.object.name == 'React' && node.superClass.property.name === 'Component')
{
isReactComponent = true;
}
else if (node.superClass && node.superClass.name === 'Component') {
// if you want to suppot extends Component instead of just React.Component
isReactComponent = true;
}
}
if (isReactComponent) {
var className = node.id.name;
if (className[0] !== className[0].toUpperCase() || !className.endsWith("Component"))
context.report({
node: node,
message: "Please use Proper case for the React Component class - {{identifier}}",
data: {
identifier: className
}, fix: (fixer) => {
var newClassName = className.toLowerCase().replace('component', '') + 'Component';
newClassName = toPascalCase(newClassName);
return fixer.replaceTextRange(node.id.range, newClassName)
}
});
}
}
};
}
};
Run Code Online (Sandbox Code Playgroud)
关键是要了解AST树,我使用astexplorer.Rest代码非常自我解释.
我已经在下面的repo上托管了插件,以防你想直接给它一个简短的
https://github.com/tarunlalwani/eslint-plugin-react-class-naming
使用以下命令安装插件
npm i tarunlalwani/eslint-plugin-react-class-naming#master
Run Code Online (Sandbox Code Playgroud)
然后将其添加到.eslintrc
{
"plugins": [
"react-class-naming"
]
}
Run Code Online (Sandbox Code Playgroud)
然后在.eslintrc中添加规则
"rules": {
"react-class-naming/react-classnaming-convention": ["error"],
....
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
583 次 |
| 最近记录: |