有React + TypeScript应用程序,所有组件类都应该是大写的,后缀为Component
,例如:
export class FooBarComponent extends React.Component {...}
应用程序弹出create-react-application
应用程序,即使用Webpack构建。
如何强制组件命名与样式指南一致,至少对于组件类,如果存在不一致,则会在构建时抛出错误?
我认为单凭TSLint / ESLint无法实现这一点。如果TypeScript和JavaScript应该使用不同的方法,那么两种语言的解决方案都会有所帮助。
答案 0 :(得分:10)
我只能为你提供打字稿的解决方案。
我认为单凭TSLint / ESLint无法实现这一目标。
有一个所谓的规则class-name可以部分解决您的问题,但似乎您需要为此类情况编写自定义规则。
因此,让我们尝试编写这样的custom tslint rule。为此,我们需要在tslint配置中使用rulesDirectory
选项来指定自定义规则的路径
"rulesDirectory": [
"./tools/tslint-rules/"
],
由于我要在打字稿中编写自定义规则,我将使用tslint@5.7.0中添加的一项功能
[增强]自定义lint规则将使用节点的路径解析 分辨率允许加载器如ts-node(#3108)
我们需要安装ts-node
包
npm i -D ts-node
然后在tslint.json中添加假规则
"ts-loader": true,
并在rulesDirectory中创建文件tsLoaderRule.js
:
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() {}
};
这基本上是广泛用于角度包装,如角度材料,通用等的方法
现在我们可以创建将使用打字稿编写的自定义规则(class-name规则的扩展版本)。
<强> 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();
}
最后我们应该将新规则放到tsling.json
:
// Custom rules
"ts-loader": true,
"my-react-component": true
所以像
这样的代码App extends React.Component
将导致:
我还创建了 ejected 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();
}
另见commit:
答案 1 :(得分:6)
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 {
}
然后在同一个
上运行插件我在编写插件时遵循以下指南
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)
}
});
}
}
};
}
};
关键是要了解AST树,我使用astexplorer做了。 Rest代码非常自我解释。
我已经在下面的repo上托管了这个插件,以防你想直接给它一个简短的
https://github.com/tarunlalwani/eslint-plugin-react-class-naming
使用以下命令
安装插件npm i tarunlalwani/eslint-plugin-react-class-naming#master
然后将其添加到.eslintrc
{
"plugins": [
"react-class-naming"
]
}
然后在.eslintrc中添加规则
"rules": {
"react-class-naming/react-classnaming-convention": ["error"],
....
}