From 8f484856595f1f61098b7a11771c95d548a90951 Mon Sep 17 00:00:00 2001 From: Angelo Abdoelsamath Date: Tue, 3 Mar 2020 16:40:49 +0100 Subject: [PATCH] [New] Add `named-import` rule - also enabled it in the `jsx-runtime` config Co-authored-by: Angelo Abdoelsamath Co-authored-by: Jordan Harband --- README.md | 1 + docs/rules/named-import.md | 82 ++++ index.js | 2 + lib/rules/named-import.js | 245 +++++++++++ lib/types.d.ts | 3 + tests/index.js | 6 +- .../rules/function-component-definition.js | 8 +- tests/lib/rules/named-import.js | 409 ++++++++++++++++++ tests/lib/rules/no-named-import.js | 409 ++++++++++++++++++ 9 files changed, 1163 insertions(+), 2 deletions(-) create mode 100644 docs/rules/named-import.md create mode 100644 lib/rules/named-import.js create mode 100644 tests/lib/rules/named-import.js create mode 100644 tests/lib/rules/no-named-import.js diff --git a/README.md b/README.md index ad95c7e922..1afdef5c15 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ Enable the rules that you would like to use. | | | [react/forbid-foreign-prop-types](docs/rules/forbid-foreign-prop-types.md) | Forbid using another component's propTypes | | | | [react/forbid-prop-types](docs/rules/forbid-prop-types.md) | Forbid certain propTypes | | | 🔧 | [react/function-component-definition](docs/rules/function-component-definition.md) | Standardize the way function component get defined | +| | 🔧 | [react/named-import](docs/rules/named-import.md) | Enforce or prohibit named imports from React | | | | [react/no-access-state-in-setstate](docs/rules/no-access-state-in-setstate.md) | Reports when this.state is accessed within setState | | | | [react/no-adjacent-inline-elements](docs/rules/no-adjacent-inline-elements.md) | Prevent adjacent inline elements not separated by whitespace. | | | | [react/no-array-index-key](docs/rules/no-array-index-key.md) | Prevent usage of Array index in keys | diff --git a/docs/rules/named-import.md b/docs/rules/named-import.md new file mode 100644 index 0000000000..608dca28ec --- /dev/null +++ b/docs/rules/named-import.md @@ -0,0 +1,82 @@ +# Enforce or prohibit named imports from React (react/named-import) + +Prohibit the use of named imports from React, so it's visible in code that it's React and not custom code; or enforce the use of named imports from React, to avoid needing the `React` default import. This rule also creates consistency inside a codebase because it enforces or prohibits the use of specified named imports. + +## Rule Details + +### Options + +This rule has two different modes: 'import' and 'property', which can be set using the first argument. The second arguments accepts an object where the keys are equal to React named exports, and their value overrides the default mode + +``` +"react/named-import": [, 'import' | 'property', {}] +``` + +By default the rule is set to `import` with an empty object, which means that all of React's named exports will have the rule applied. + + + +Example configurations: + +```javascript +// import +{ + "rules": { + "react/named-import": ["error", "import", { + "Component": "property", + "useState": "property" + }] + } +} +// property +{ + "rules": { + "react/named-import": ["error", "property", { + "Component": "import", + "useState": "import" + }] + } +} +``` + +The following patterns are considered warnings: + +```jsx +const [loading, setLoading] = React.useState(false); + +// ['property', {}] +import React, { Component } from 'react'; + +// ['property', {'useState': 'import' }] +const [loading, setLoading] = React.useState(false); + +// ['import', {}] +const [loading, setLoading] = React.useState(false); + +// ['import', {'useState': 'property' }] +import React, { useState } from 'react'; + +``` + +The following patterns are **not** considered warnings: + +```jsx +import React, { useState } from 'react'; + +// ['property', {}] +import React from 'react'; + +const [loading, setLoading] = React.useState(false); + +// ['property', {'useState': 'import' }] +import { useState } from 'react'; + +// ['import', {}] +import React, { useState } from 'react'; + +const [loading, setLoading] = useState(false); + +// ['import', {'useState': 'property' }] +const [loading, setLoading] = React.useState(false); + +``` diff --git a/index.js b/index.js index e89b0c8ea4..03b57a5ad2 100644 --- a/index.js +++ b/index.js @@ -67,6 +67,7 @@ const allRules = { 'no-find-dom-node': require('./lib/rules/no-find-dom-node'), 'no-is-mounted': require('./lib/rules/no-is-mounted'), 'no-multi-comp': require('./lib/rules/no-multi-comp'), + 'named-import': require('./lib/rules/named-import'), 'no-set-state': require('./lib/rules/no-set-state'), 'no-string-refs': require('./lib/rules/no-string-refs'), 'no-redundant-should-component-update': require('./lib/rules/no-redundant-should-component-update'), @@ -171,6 +172,7 @@ module.exports = { } }, rules: { + 'react/named-import': [2, 'import'], 'react/react-in-jsx-scope': 0, 'react/jsx-uses-react': 0 } diff --git a/lib/rules/named-import.js b/lib/rules/named-import.js new file mode 100644 index 0000000000..9962ad1aa6 --- /dev/null +++ b/lib/rules/named-import.js @@ -0,0 +1,245 @@ +/** + * @fileoverview No named imports on React + * @author Angelo Abdoelsamath + */ + +'use strict'; + +const docsUrl = require('../util/docsUrl'); + +const DEFAULT_TYPE = 'import'; + +const getFromContext = require('../util/pragma').getFromContext; + +module.exports = { + meta: { + fixable: 'code', + docs: { + description: 'Enforce or prohibit named imports from React', + category: 'Best Practices', + recommended: false, + url: docsUrl('named-import') + }, + schema: [ + { + type: 'string', + enum: ['property', 'import'] + }, + { + type: 'object', + patternProperties: { + '.': { + type: 'string', + enum: ['property', 'import'] + } + }, + additionalProperties: false + } + + ], + messages: { + usePropertyAccessor: 'use {{react}}.{{name}}', + useNamedImport: 'Import {{name}} from "react"', + fixImportStatement: 'Fix import statement' + } + }, + /** + * + * @param {RuleContext} context + * @returns {any} + */ + create(context) { + // ignore non-modules + if (context.parserOptions.sourceType !== 'module') { + return {}; + } + const options = context.options; + const mode = options[0] || DEFAULT_TYPE; + const overrides = options[1] || {}; + + const reactPragma = getFromContext(context); + + let propertySpecifiers = []; + let specifiers = []; + + let importDeclaration; + + /** @type {import('eslint').SourceCode} */ + const sourceCode = context.getSourceCode(); + + function isNameOfType(name, type) { + return (overrides[name] || mode) === type; + } + + function getImportDeclarationString() { + let str = reactPragma; + + if (specifiers.length) { + str += ', { '; + str += specifiers.join(', '); + str += ' }'; + } + + return str; + } + + function fixImportDeclaration() { + const node = importDeclaration; + const tokens = sourceCode.getTokens(node); + + const importToken = tokens[0]; + const fromToken = tokens.find((token) => token.value === 'from'); + + const range = [ + importToken.range[1] + 1, + fromToken.range[0] - 1 + ]; + + return (fixer) => fixer.replaceTextRange(range, getImportDeclarationString()); + } + + function shouldUpdateImportStatement() { + const currentSpecifiers = importDeclaration.specifiers + .filter((specifier) => specifier.type !== 'ImportDefaultSpecifier') + .map((specifier) => specifier.imported.name); + + return specifiers.some((spec) => currentSpecifiers.indexOf(spec) === -1) + || currentSpecifiers.some((spec) => isNameOfType(spec, 'property')); + } + + function getFixer(name, range, newType) { + const newText = `${newType === 'property' ? `${reactPragma}.` : ''}${name}`; + + return (fixer) => fixer.replaceTextRange(range, newText); + } + + function getReferenceIdentifiers(node) { + const variables = context.getDeclaredVariables(node); + return variables[0].references.map((reference) => reference.identifier); + } + + function report(node, newType, name, range) { + const useName = name || node.name; + const useRange = range || node.range; + + if (newType === 'import') { + specifiers.push(useName); + } + + context.report({ + node, + messageId: newType === 'property' ? 'usePropertyAccessor' : 'useNamedImport', + data: { + name: useName, + react: reactPragma + }, + fix: getFixer(useName, useRange, newType) + }); + } + + function getTypeNamespace(node) { + if (node.type === 'TSQualifiedName') { + if (node.left) { + return getTypeNamespace(node.left); + } + } + + if (node.type === 'Identifier') { + return node.name; + } + } + + function getTypeProperties(node) { + if (!node) { + return; + } + if (node.type === 'TSQualifiedName') { + const namespace = getTypeNamespace(node); + return { + namespace, + name: node.right.name + }; + } + + if (node.type === 'Identifier') { + return { + namespace: null, + name: node.name + }; + } + } + + const typeSelector = (node) => { + const tsType = getTypeProperties(node); + if (!tsType) { return; } + + let type; + + if (tsType.namespace === reactPragma) { + type = 'import'; + } else if (propertySpecifiers.indexOf(tsType.name) > -1) { + type = 'property'; + } + + if (!type) { return; } + + if (isNameOfType(tsType.name, type)) { + report( + node, + type, + tsType.name + ); + } + }; + + return { + 'Program:exit'(node) { + if (shouldUpdateImportStatement()) { + context.report({ + node, + messageId: 'fixImportStatement', + fix: fixImportDeclaration() + }); + } + + importDeclaration = null; + specifiers = []; + propertySpecifiers = []; + }, + TSTypeReference: (node) => typeSelector(node.typeName), + 'ImportDeclaration[source.value=\'react\']'(node) { + importDeclaration = node; + node.specifiers.forEach((specifier) => { + if (specifier.type === 'ImportDefaultSpecifier') { + const variables = context.getDeclaredVariables(specifier); + + const firstVariableWithRefs = variables.find((variable) => variable.references.length); + + if (!firstVariableWithRefs) { return; } + + firstVariableWithRefs.references.forEach((reference) => { + /** @type ASTNode */ + const memberExpression = sourceCode.getNodeByRangeIndex(reference.identifier.range[0]); + + const nodeName = memberExpression.parent.property.name; + + if (isNameOfType(nodeName, 'import')) { + report(memberExpression.parent.property, 'import', null, memberExpression.parent.range); + } + }); + + return; + } + + if (isNameOfType(specifier.imported.name, 'property')) { + propertySpecifiers.push(specifier.imported.name); + const nodes = getReferenceIdentifiers(specifier); + nodes.forEach((innerNode) => report(innerNode, 'property')); + } else { + specifiers.push(specifier.imported.name); + } + }); + } + }; + } +}; diff --git a/lib/types.d.ts b/lib/types.d.ts index 7e22f2788b..8643ee3d5d 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -8,6 +8,7 @@ declare global { type Scope = eslint.Scope.Scope; type Token = eslint.AST.Token; type Fixer = eslint.Rule.RuleFixer; + type RuleContext = eslint.Rule.RuleContext; type JSXAttribute = ASTNode; type JSXElement = ASTNode; type JSXFragment = ASTNode; @@ -17,6 +18,8 @@ declare global { getFirstTokens(node: estree.Node | ASTNode, options?: eslint.SourceCode.CursorWithCountOptions): eslint.AST.Token[]; } + interface Listener extends eslint.Rule.RuleListener { } + type TypeDeclarationBuilder = (annotation: ASTNode, parentName: string, seen: Set) => object; type TypeDeclarationBuilders = { diff --git a/tests/index.js b/tests/index.js index c04936fa26..089fc4a9e4 100644 --- a/tests/index.js +++ b/tests/index.js @@ -80,7 +80,11 @@ describe('configurations', () => { Object.keys(plugin.configs[configName].rules).forEach((ruleName) => { assert.ok(ruleName.startsWith('react/')); - assert.equal(plugin.configs[configName].rules[ruleName], 0); + const config = plugin.configs[configName].rules[ruleName]; + if (config !== 0) { + assert.ok(Array.isArray(config)); + assert.equal(config[0], 2); + } const inDeprecatedRules = Boolean(plugin.deprecatedRules[ruleName]); const inConfig = typeof plugin.configs[configName].rules[ruleName] !== 'undefined'; diff --git a/tests/lib/rules/function-component-definition.js b/tests/lib/rules/function-component-definition.js index 5c66c90383..7cb8ea4b73 100644 --- a/tests/lib/rules/function-component-definition.js +++ b/tests/lib/rules/function-component-definition.js @@ -310,15 +310,21 @@ ruleTester.run('function-component-definition', rule, { code: [ 'function Hello(props) {', ' return
', + '}', + 'function Hello2(props) {', + ' return
', '}' ].join('\n'), output: [ 'var Hello = (props) => {', ' return
', + '}', + 'var Hello2 = (props) => {', + ' return
', '}' ].join('\n'), options: [{namedComponents: 'arrow-function'}], - errors: [{messageId: 'arrow-function'}], + errors: [{messageId: 'arrow-function'}, {messageId: 'arrow-function'}], parser: parsers.BABEL_ESLINT }, { code: [ diff --git a/tests/lib/rules/named-import.js b/tests/lib/rules/named-import.js new file mode 100644 index 0000000000..2a4f56ce5c --- /dev/null +++ b/tests/lib/rules/named-import.js @@ -0,0 +1,409 @@ +/** + * @fileoverview Tests for named-import + */ + +'use strict'; + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +const RuleTester = require('eslint').RuleTester; +const rule = require('../../../lib/rules/named-import'); +const parsers = require('../../helpers/parsers'); + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true + } +}; + +const settings = { + react: { + pragma: 'Act' + } +}; + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +const ruleTester = new RuleTester({parserOptions}); +ruleTester.run('named-import', rule, { + valid: [ + { + code: "import Act from 'react';", + settings + }, + { + code: "import Act, { useState } from 'react';", + settings + }, + { + code: ` + import Act from 'react'; + const [loading, setLoading] = Act.useState(false); + `, + options: ['property'], + settings + }, + { + code: ` + import Act, { useState } from 'react'; + const [loading, setLoading] = useState(false); + `, + options: ['import'], + settings + }, + { + code: ` + import Act, { useEffect, Component } from 'react'; + const [loading, setLoading] = Act.useState(false); + `, + options: [ + 'import', + { + useEffect: 'import', + useState: 'property' + } + ], + settings + }, + { + code: ` + import Act, { useEffect } from 'react'; + const [loading, setLoading] = Act.useState(false); + const a = Act.Component; + `, + options: [ + 'property', + { + useEffect: 'import', + useState: 'property' + } + ], + settings + } + ], + invalid: [ + { + code: ` + import Act from 'react'; + const [value, setValue] = Act.useState(''); + `, + output: ` + import Act, { useState } from 'react'; + const [value, setValue] = useState(''); + `, + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: { + name: 'useState' + } + } + ], + settings + }, + { + code: ` + import Act from 'react'; + const [value, setValue] = Act.useState(''); + `, + output: ` + import Act, { useState } from 'react'; + const [value, setValue] = useState(''); + `, + options: ['import'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: {name: 'useState'} + } + ], + settings + }, + { + code: ` + import Act, { useState } from 'react'; + const [value, setValue] = useState(''); + `, + output: ` + import Act from 'react'; + const [value, setValue] = Act.useState(''); + `, + options: ['property'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'usePropertyAccessor', + data: { + name: 'useState', + react: 'Act' + } + } + ], + settings + }, + { + code: ` + import Act from 'react'; + const [value, setValue] = Act.useState(''); + Act.useEffect(() => {}, []); + `, + output: ` + import Act, { useEffect } from 'react'; + const [value, setValue] = Act.useState(''); + useEffect(() => {}, []); + `, + options: ['property', {useEffect: 'import'}], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: { + name: 'useEffect' + } + } + ], + settings + }, + { + code: ` + import Act from 'react'; + const [value, setValue] = Act.useState(''); + Act.useEffect(() => {}, []); + `, + output: ` + import Act, { useEffect } from 'react'; + const [value, setValue] = Act.useState(''); + useEffect(() => {}, []); + `, + options: ['import', {useState: 'property'}], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: {name: 'useEffect'} + } + ], + settings + } + + ].concat(parsers.TS([ + { + code: ` + import Act from 'react'; + const Comp: Act.AType = () => {} + const a = (p: Act.BType) => {} + + type A = { + b: Act.CType; + } + + type B = { + d: C + } + + type A = B; + `, + output: ` + import Act, { AType, BType, CType, DType } from 'react'; + const Comp: AType = () => {} + const a = (p: BType) => {} + + type A = { + b: CType; + } + + type B = { + d: C + } + + type A = B; + `, + options: ['import'], + parser: parsers['@TYPESCRIPT_ESLINT'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: {name: 'AType'} + }, + { + messageId: 'useNamedImport', + data: {name: 'BType'} + }, + { + messageId: 'useNamedImport', + data: {name: 'CType'} + }, + { + messageId: 'useNamedImport', + data: {name: 'DType'} + } + ], + settings + }, + { + code: ` + import Act, { AType, BType, CType, DType } from 'react'; + const Comp: AType = () => {} + const a = (p: BType) => {} + + type A = { + b: CType; + } + + type B = { + d: C + } + + type A = B; + `, + output: ` + import Act from 'react'; + const Comp: Act.AType = () => {} + const a = (p: Act.BType) => {} + + type A = { + b: Act.CType; + } + + type B = { + d: C + } + + type A = B; + `, + options: ['property'], + parser: parsers['@TYPESCRIPT_ESLINT'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'AType', react: 'Act'} + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'BType', react: 'Act'} + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'CType', react: 'Act'} + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'DType', react: 'Act'} + } + ], + settings + }, + { + code: ` + import Act, { AType, BType } from 'react'; + type Props = { + a: AType, + b: BType + } + `, + output: ` + import Act, { BType } from 'react'; + type Props = { + a: Act.AType, + b: BType + } + `, + options: ['property', {BType: 'import'}], + parser: parsers['@TYPESCRIPT_ESLINT'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'AType', react: 'Act'} + } + ], + settings + }, + { + code: ` + import Act from 'react'; + type Props = { + a: Act.AType, + b: Act.BType + } + `, + output: ` + import Act, { BType } from 'react'; + type Props = { + a: Act.AType, + b: BType + } + `, + options: ['import', {AType: 'property'}], + parser: parsers['@TYPESCRIPT_ESLINT'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: {name: 'BType'} + } + ], + settings + }, + { + code: ` + import Act, { AType } from 'react'; + type Props = { + a: AType, + b: Act.BType + } + `, + output: ` + import Act, { BType } from 'react'; + type Props = { + a: Act.AType, + b: BType + } + `, + options: ['property', {BType: 'import'}], + parser: parsers['@TYPESCRIPT_ESLINT'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'AType', react: 'Act'} + }, + { + messageId: 'useNamedImport', + data: {name: 'BType'} + } + ], + settings + } + ])) +}); diff --git a/tests/lib/rules/no-named-import.js b/tests/lib/rules/no-named-import.js new file mode 100644 index 0000000000..2a4f56ce5c --- /dev/null +++ b/tests/lib/rules/no-named-import.js @@ -0,0 +1,409 @@ +/** + * @fileoverview Tests for named-import + */ + +'use strict'; + +// ----------------------------------------------------------------------------- +// Requirements +// ----------------------------------------------------------------------------- + +const RuleTester = require('eslint').RuleTester; +const rule = require('../../../lib/rules/named-import'); +const parsers = require('../../helpers/parsers'); + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { + jsx: true + } +}; + +const settings = { + react: { + pragma: 'Act' + } +}; + +// ----------------------------------------------------------------------------- +// Tests +// ----------------------------------------------------------------------------- + +const ruleTester = new RuleTester({parserOptions}); +ruleTester.run('named-import', rule, { + valid: [ + { + code: "import Act from 'react';", + settings + }, + { + code: "import Act, { useState } from 'react';", + settings + }, + { + code: ` + import Act from 'react'; + const [loading, setLoading] = Act.useState(false); + `, + options: ['property'], + settings + }, + { + code: ` + import Act, { useState } from 'react'; + const [loading, setLoading] = useState(false); + `, + options: ['import'], + settings + }, + { + code: ` + import Act, { useEffect, Component } from 'react'; + const [loading, setLoading] = Act.useState(false); + `, + options: [ + 'import', + { + useEffect: 'import', + useState: 'property' + } + ], + settings + }, + { + code: ` + import Act, { useEffect } from 'react'; + const [loading, setLoading] = Act.useState(false); + const a = Act.Component; + `, + options: [ + 'property', + { + useEffect: 'import', + useState: 'property' + } + ], + settings + } + ], + invalid: [ + { + code: ` + import Act from 'react'; + const [value, setValue] = Act.useState(''); + `, + output: ` + import Act, { useState } from 'react'; + const [value, setValue] = useState(''); + `, + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: { + name: 'useState' + } + } + ], + settings + }, + { + code: ` + import Act from 'react'; + const [value, setValue] = Act.useState(''); + `, + output: ` + import Act, { useState } from 'react'; + const [value, setValue] = useState(''); + `, + options: ['import'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: {name: 'useState'} + } + ], + settings + }, + { + code: ` + import Act, { useState } from 'react'; + const [value, setValue] = useState(''); + `, + output: ` + import Act from 'react'; + const [value, setValue] = Act.useState(''); + `, + options: ['property'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'usePropertyAccessor', + data: { + name: 'useState', + react: 'Act' + } + } + ], + settings + }, + { + code: ` + import Act from 'react'; + const [value, setValue] = Act.useState(''); + Act.useEffect(() => {}, []); + `, + output: ` + import Act, { useEffect } from 'react'; + const [value, setValue] = Act.useState(''); + useEffect(() => {}, []); + `, + options: ['property', {useEffect: 'import'}], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: { + name: 'useEffect' + } + } + ], + settings + }, + { + code: ` + import Act from 'react'; + const [value, setValue] = Act.useState(''); + Act.useEffect(() => {}, []); + `, + output: ` + import Act, { useEffect } from 'react'; + const [value, setValue] = Act.useState(''); + useEffect(() => {}, []); + `, + options: ['import', {useState: 'property'}], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: {name: 'useEffect'} + } + ], + settings + } + + ].concat(parsers.TS([ + { + code: ` + import Act from 'react'; + const Comp: Act.AType = () => {} + const a = (p: Act.BType) => {} + + type A = { + b: Act.CType; + } + + type B = { + d: C + } + + type A = B; + `, + output: ` + import Act, { AType, BType, CType, DType } from 'react'; + const Comp: AType = () => {} + const a = (p: BType) => {} + + type A = { + b: CType; + } + + type B = { + d: C + } + + type A = B; + `, + options: ['import'], + parser: parsers['@TYPESCRIPT_ESLINT'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: {name: 'AType'} + }, + { + messageId: 'useNamedImport', + data: {name: 'BType'} + }, + { + messageId: 'useNamedImport', + data: {name: 'CType'} + }, + { + messageId: 'useNamedImport', + data: {name: 'DType'} + } + ], + settings + }, + { + code: ` + import Act, { AType, BType, CType, DType } from 'react'; + const Comp: AType = () => {} + const a = (p: BType) => {} + + type A = { + b: CType; + } + + type B = { + d: C + } + + type A = B; + `, + output: ` + import Act from 'react'; + const Comp: Act.AType = () => {} + const a = (p: Act.BType) => {} + + type A = { + b: Act.CType; + } + + type B = { + d: C + } + + type A = B; + `, + options: ['property'], + parser: parsers['@TYPESCRIPT_ESLINT'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'AType', react: 'Act'} + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'BType', react: 'Act'} + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'CType', react: 'Act'} + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'DType', react: 'Act'} + } + ], + settings + }, + { + code: ` + import Act, { AType, BType } from 'react'; + type Props = { + a: AType, + b: BType + } + `, + output: ` + import Act, { BType } from 'react'; + type Props = { + a: Act.AType, + b: BType + } + `, + options: ['property', {BType: 'import'}], + parser: parsers['@TYPESCRIPT_ESLINT'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'AType', react: 'Act'} + } + ], + settings + }, + { + code: ` + import Act from 'react'; + type Props = { + a: Act.AType, + b: Act.BType + } + `, + output: ` + import Act, { BType } from 'react'; + type Props = { + a: Act.AType, + b: BType + } + `, + options: ['import', {AType: 'property'}], + parser: parsers['@TYPESCRIPT_ESLINT'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'useNamedImport', + data: {name: 'BType'} + } + ], + settings + }, + { + code: ` + import Act, { AType } from 'react'; + type Props = { + a: AType, + b: Act.BType + } + `, + output: ` + import Act, { BType } from 'react'; + type Props = { + a: Act.AType, + b: BType + } + `, + options: ['property', {BType: 'import'}], + parser: parsers['@TYPESCRIPT_ESLINT'], + errors: [ + { + messageId: 'fixImportStatement' + }, + { + messageId: 'usePropertyAccessor', + data: {name: 'AType', react: 'Act'} + }, + { + messageId: 'useNamedImport', + data: {name: 'BType'} + } + ], + settings + } + ])) +});