From 97726448a208996589af8a4d5c4e51c557fd6ad8 Mon Sep 17 00:00:00 2001 From: paibamboo Date: Tue, 15 Oct 2019 16:47:59 +0700 Subject: [PATCH] feat: import-react rule --- README.md | 22 ++++++---- src/importReactRule.ts | 43 +++++++++++++++++++ test/rules/import-react/default/test.ts.lint | 3 ++ test/rules/import-react/default/tslint.json | 8 ++++ .../import-react/import-default/test.ts.lint | 3 ++ .../import-react/import-default/tslint.json | 13 ++++++ .../import-react/import-star/test.ts.lint | 3 ++ .../import-react/import-star/tslint.json | 13 ++++++ 8 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 src/importReactRule.ts create mode 100644 test/rules/import-react/default/test.ts.lint create mode 100644 test/rules/import-react/default/tslint.json create mode 100644 test/rules/import-react/import-default/test.ts.lint create mode 100644 test/rules/import-react/import-default/tslint.json create mode 100644 test/rules/import-react/import-star/test.ts.lint create mode 100644 test/rules/import-react/import-star/tslint.json diff --git a/README.md b/README.md index cf98d43..d36766c 100644 --- a/README.md +++ b/README.md @@ -26,26 +26,30 @@ Initially you should: ## Rules -- `create-async-actions` +### `create-async-actions` - In [ts-react-boilerplate](https://github.com/crazyfactory/ts-react-boilerplate), we use `createAsyncActions` to create Redux async actions. Four actions are created from calling it - `BASE`, `BASE_PENDING`, `BASE_FULFILLED`, and `BASE_REJECTED` as an example when `createAsyncActions("BASE", "BASE_PENDING", "BASE_FULFILLED", "BASE_REJECTED"` is called. Still, as you see, we need to provide string literal as arugments due to typescript limitation, if we provide any string variable, the type will be deduced to just `string`. This rule enforces 2nd, 3rd, and 4th argument to be the concatenation of the first argument string and `_PENDING`, `_FULFILLED`, and `_REJECTED` respectively. -- `hex-format` +### `import-react` + - Specify how you should import `react`. Either `import *` or `import React`. + - Rule options: + - `type: "default" | "star"`. Default is `star` +### `hex-format` - Requires literal string in hex format to be uppercase/lowercase and/or of specific lengths. - Rule options: - - `case: "uppercase" | "lowercase"` - - `allowedLengths: number[]` -- `interface-sort-keys` + - `case: "uppercase" | "lowercase"`. Default is `lowercase` + - `allowedLengths: number[]`. Default is `[4, 7]` +### `interface-sort-keys` - Same as [object-literal-sort-keys](https://palantir.github.io/tslint/rules/object-literal-sort-keys/) but applied to interface keys -- `jsx-space-before-trailing-slash` +### `jsx-space-before-trailing-slash` - Requires or bans space before `/>` part of jsx. - Rule options: - - `["always", "never"]` -- `language` + - `["always", "never"]`. Default is `always`. +### `language` - Requires that string argument called by `Translator` object is in the `reference.json` - Rule options: - `path`: path to `reference.json` @@ -62,6 +66,6 @@ Initially you should: } ``` - `callerNames: string[]`: Name of translator object type, default is `["Translator"]` -- `no-dup-actions` +### `no-dup-actions` - Requires that all actions created by [createAsyncActions](https://github.com/crazyfactory/ts-react-boilerplate/blob/master/src/app/redux/modules/baseModule.ts) and [createAction](https://github.com/piotrwitek/typesafe-actions#createaction) have unique name. diff --git a/src/importReactRule.ts b/src/importReactRule.ts new file mode 100644 index 0000000..ebde078 --- /dev/null +++ b/src/importReactRule.ts @@ -0,0 +1,43 @@ +import * as Lint from "tslint"; +import * as ts from "typescript"; + +interface IOption { + type: "star" | "default"; +} + +class ImportReactRule extends Lint.AbstractWalker { + public walk(sourceFile: ts.SourceFile): void { + if (sourceFile.isDeclarationFile) { + return; + } + const cb = (node: ts.Node): void => { + if ( + ts.isImportDeclaration(node) + && node.moduleSpecifier.getText(sourceFile) === `"react"` + && node.importClause + ) { + // import * as React has namedBindings, and import React has only name + if (this.options.type === "star" && !node.importClause.namedBindings) { + this.addFailureAtNode(node, "You must use `import * as React from 'react'`"); + } else if (this.options.type === "default" && node.importClause.namedBindings) { + this.addFailureAtNode(node, "You must use `import React from 'react'`"); + } + } + return ts.forEachChild(node, cb); + }; + return ts.forEachChild(sourceFile, cb); + } +} + +// tslint:disable-next-line:export-name max-classes-per-file +export class Rule extends Lint.Rules.AbstractRule { + public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] { + return this.applyWithWalker( + new ImportReactRule( + sourceFile, + "import-react", + {type: this.ruleArguments[0] && this.ruleArguments[0].type ? this.ruleArguments[0].type : "star"} + ) + ); + } +} diff --git a/test/rules/import-react/default/test.ts.lint b/test/rules/import-react/default/test.ts.lint new file mode 100644 index 0000000..5f0fc5a --- /dev/null +++ b/test/rules/import-react/default/test.ts.lint @@ -0,0 +1,3 @@ +import * as React from "react"; +import React from "react"; +~~~~~~~~~~~~~~~~~~~~~~~~~~ [You must use `import * as React from 'react'`] diff --git a/test/rules/import-react/default/tslint.json b/test/rules/import-react/default/tslint.json new file mode 100644 index 0000000..1d8fdf1 --- /dev/null +++ b/test/rules/import-react/default/tslint.json @@ -0,0 +1,8 @@ +{ + "rulesDirectory": [ + "../../../../lib" + ], + "rules": { + "import-react": true + } +} diff --git a/test/rules/import-react/import-default/test.ts.lint b/test/rules/import-react/import-default/test.ts.lint new file mode 100644 index 0000000..9017f2c --- /dev/null +++ b/test/rules/import-react/import-default/test.ts.lint @@ -0,0 +1,3 @@ +import * as React from "react"; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [You must use `import React from 'react'`] +import React from "react"; diff --git a/test/rules/import-react/import-default/tslint.json b/test/rules/import-react/import-default/tslint.json new file mode 100644 index 0000000..94e67f8 --- /dev/null +++ b/test/rules/import-react/import-default/tslint.json @@ -0,0 +1,13 @@ +{ + "rulesDirectory": [ + "../../../../lib" + ], + "rules": { + "import-react": [ + true, + { + "type": "default" + } + ] + } +} diff --git a/test/rules/import-react/import-star/test.ts.lint b/test/rules/import-react/import-star/test.ts.lint new file mode 100644 index 0000000..5f0fc5a --- /dev/null +++ b/test/rules/import-react/import-star/test.ts.lint @@ -0,0 +1,3 @@ +import * as React from "react"; +import React from "react"; +~~~~~~~~~~~~~~~~~~~~~~~~~~ [You must use `import * as React from 'react'`] diff --git a/test/rules/import-react/import-star/tslint.json b/test/rules/import-react/import-star/tslint.json new file mode 100644 index 0000000..d1d1c92 --- /dev/null +++ b/test/rules/import-react/import-star/tslint.json @@ -0,0 +1,13 @@ +{ + "rulesDirectory": [ + "../../../../lib" + ], + "rules": { + "import-react": [ + true, + { + "type": "star" + } + ] + } +}