diff --git a/docs/rules/typescript-flow.md b/docs/rules/typescript-flow.md new file mode 100644 index 000000000..bfcc917bd --- /dev/null +++ b/docs/rules/typescript-flow.md @@ -0,0 +1,164 @@ + +This rules helps to define type import patterns for the typescript. + +## Rule details +This rule allows you to specify how type imports should be written in your TypeScript code. There are two options available: inline type imports and separate type import statements. Rule can also autofix your type imports to the preferred style. + +The rule has 2 modes: strict and preference. + +Strict option enforces either inline type imports with type modifiers, or separate type imports. + +If you select the preference mode, the rule will check for the availability of your preferred import style and use that if possible. If not, it will fall back to your second preferred option. + +For example, if you specify the preference as ["inline", "separate"], the rule will try to use inline type imports first. If that is not supported by your TypeScript version, it will use separate type import statements instead. + + +#### Rule schema + +```javascript +// strict mode +"import/typescript-flow": [ 0 | 1 | 2, "inline" | "separate" ] + +// preference mode +"import/typescript-flow": [ 0 | 1 | 2, [ "inline", "separate" ] | [ "separate", "inline" ] ] + +``` + +### Strict mode + +#### separate + +**Definition**: Separate type import statements are written using the import type syntax. They were introduced in TypeScript 3.8 + +The following patterns are *not* considered warnings: + +```javascript +// good1.js + +// Separate import type statement +import type { MyType } from "./typescript.ts" +``` + +```javascript +// good2.js + +// Separate import type statement +import type { MyType as Persona, Bar as Foo } from "./typescript.ts" +``` +```javascript +// good3.js + +// Default type imports are ignored +import type DefaultImport from "./typescript.ts" +``` + +```javascript +// good4.js + +// Default imports are ignored +import DefaultImport from "./typescript.ts" +``` + +The following patterns are considered warnings: + +```javascript +// bad1.js + +// type imports must be imported in separate import type statement +import {type MyType,Bar} from "./typescript.ts" + +// gets fixed to the following: +import {Bar} from "./typescript.ts" +import type { MyType } from "./typescript.ts" +``` + +```javascript +// bad2.js + +// type imports must be imported in separate import type statement +import {type MyType,type Foo} from "./typescript.ts" + +// gets fixed to the following: +import type { MyType, Foo} from "./typescript.ts" +``` + +```javascript +// bad3.js + +// type imports must be imported in separate import type statement +import {type MyType as Persona,Bar,type Foo as Baz} from "./typescript.ts" + +// gets fixed to the following: +import {Bar} from "./typescript.ts" +import type { MyType as Persona, Foo as Baz } from "./typescript.ts" +``` + +#### inline + +**Definition**: Inline type imports are written as type modifiers within the curly braces of a regular import statement. They were introduced in TypeScript 4.5. + +Patterns that do not raise the warning: + +```javascript +// good1.js + +// no separate import type statement. inline type import exists +import { type MyType } from "./typescript.ts" +``` + +```javascript +// good2.js + +// no separate import type statement. inline type import exists +import { type MyType, Bar, type Foo as Baz } from "./typescript.ts" +``` + +Patterns are considered warnings and fixed: + +```javascript +// bad1.js + +// type imports must be imported inline +import type {MyType} from "./typescript.ts" + +// gets fixed to the following: +import {type MyType} from "./typescript.ts" +``` + +```javascript +// bad1.js + +// type imports must be imported inline +import type {MyType, Bar} from "./typescript.ts" + +// gets fixed to the following: +import {type MyType, type Bar} from "./typescript.ts" +``` + +```javascript +// bad3.js + +// type imports must be imported inline +import type {MyType, Bar} from "./typescript.ts" +import {Value} from "./typescript.ts" + +// Rule can check if there are any other imports from the same source. If yes, then adds inline type imports there. gets fixed to the following: +import {Value, type MyType, type Bar} from "./typescript.ts" +``` + +### Preference mode + +```javascript +// preference mode where inline comes first +"import/typescript-flow": [ 0 | 1 | 2, [ "inline", "separate" ] ] +``` +Rule checks if `inline` type modifiers are supported by TypeScript version (must be >=4.5). If supported, then rule operates with `inline` option, if not, then it falls back to `separate` option. + +```javascript +// preference mode where strict comes first +"import/typescript-flow": [ 0 | 1 | 2, [ "separate", "inline" ] ] +``` + +If `separate` comes first in preferred order, then rule will act as if user chose `separate`. + +Since `separate` type imports are supported in TypeScript version 3.8 and above, and `inline` type imports are supported in version 4.5 and above, the `separate` option is guaranteed to be supported if the `inline` option is supported. Therefore, there is no need to consider the case where the `separate` option is not supported and the rule falls back to the `inline` option. \ No newline at end of file diff --git a/package.json b/package.json index c0a6b56e4..5ce658e50 100644 --- a/package.json +++ b/package.json @@ -129,4 +129,4 @@ "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" } -} +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 0ab82ebee..c65517889 100644 --- a/src/index.js +++ b/src/index.js @@ -54,6 +54,9 @@ export const rules = { // deprecated aliases to rules 'imports-first': require('./rules/imports-first'), + + // new typescript flow rule + 'typescript-flow': require('./rules/typescript-flow'), }; export const configs = { diff --git a/src/rules/typescript-flow.js b/src/rules/typescript-flow.js new file mode 100644 index 000000000..b4bc5f537 --- /dev/null +++ b/src/rules/typescript-flow.js @@ -0,0 +1,319 @@ +'use strict'; + +import docsUrl from '../docsUrl'; + +// import debug from 'debug'; +// const log = debug('eslint-plugin-import/typescript-flow'); + +import typescriptPkg from 'typescript/package.json'; +import semver from 'semver'; + +function tsVersionSatisfies(specifier) { + return semver.satisfies(typescriptPkg.version, specifier); +} + +const SEPARATE_ERROR_MESSAGE = 'Type imports should be separately imported.'; +const INLINE_ERROR_MESSAGE = 'Type imports should be imported inline with type modifier.'; + +// TODO: revert changes in ExportMap file. +module.exports = { + meta: { + type: 'suggestion', // Layout? + docs: { + category: 'Style guide', + description: 'Prefer a default export if module exports a single name or multiple names.', // TODO: change + url: docsUrl('prefer-default-export'), // TODO: change + }, + fixable: 'code', + schema: [{ + 'anyOf': [ + { + 'type': 'array', + 'items': { + 'type': 'string', + 'enum': ['separate', 'inline'], + }, + 'minItems': 2, + 'maxItems': 2, + 'uniqueItems': true, + }, + { + 'type': 'string', + 'enum': ['separate', 'inline'], + }, + ], + }], + }, + create(context) { + + // 3 cases: strict cases: separate, inline. 3rd case is array of preference. The only thing to check is if arr[0] === inline => + // check if it can be supported. If not, fall back on separate. + // If arr[0] === separate => just assume it is separate + + const isInlineTypeImportSupported = tsVersionSatisfies('>=4.5'); // type modifiers (inline type import) were introduced in TS 4.5. + // get Rule options. + const config = getRuleConfig(context.options[0], isInlineTypeImportSupported); + + const typeImportSpecifiers = []; + const valueImportSpecifiers =[]; + const valueImportNodes =[]; + const importSpecifierRanges = []; + + + const specifierCache = { + typeImportSpecifiers, valueImportSpecifiers, valueImportNodes, importSpecifierRanges, + }; + + + return { + 'ImportDeclaration': function (node){ + + if (config === 'inline' && !isInlineTypeImportSupported) { + context.report(node, 'Type modifiers are not supported by your version of TS.'); + } + + + if (config === 'separate' && node.importKind !== 'type') { + // identify importSpecifiers that have inline type imports as well as value imports + if (!setImportSpecifierCacheForSeparate(node.specifiers, specifierCache)) return; + + // no inline type imports found + if (typeImportSpecifiers.length === 0) return; + + if (typeImportSpecifiers.length === typeImportSpecifiers.length + valueImportSpecifiers.length) { + changeInlineImportToSeparate(node, context); + } + else { + addSeparateTypeImportDeclaration(node, context, specifierCache); + } + } + + + if (config === 'inline' && node.importKind === 'type') { + // ignore default type import like: import type A from 'x' + if (node.specifiers.length === 1 && node.specifiers[0].type === 'ImportDefaultSpecifier') { + return; + } + setImportSpecifierCacheForInline(node, specifierCache); + const sameSourceValueImport = getImportDeclarationFromTheSameSource(node); + if (sameSourceValueImport && !sameSourceValueImport.hasNamespaceImport) { + insertInlineTypeImportsToSameSourceImport(node, context, sameSourceValueImport, typeImportSpecifiers); + } else { + changeToInlineTypeImport(node, context, valueImportNodes); + } + } + }, + }; + }, +}; + + +function getRuleConfig(config, isInlineTypeImportSupported) { + if (config.length === 2 && config[0] === 'inline' && !isInlineTypeImportSupported) { + config = 'separate'; + } + if (config[0] === 'separate') { + config = 'separate'; + } + return config; +} + +/* + Functions for config === separate +*/ +function setImportSpecifierCacheForSeparate(specifiers, specifierCache) { + const typeImportSpecifiers = specifierCache.typeImportSpecifiers; + const valueImportSpecifiers = specifierCache.valueImportSpecifiers; + const valueImportNodes = specifierCache.valueImportNodes; + const importSpecifierRanges = specifierCache.importSpecifierRanges; + + for (let i = 0; i < specifiers.length; i++) { + const specifier = specifiers[i]; + if (specifier.type === 'ImportNamespaceSpecifier' || specifier.type === 'ImportDefaultSpecifier') { + return false; + } + specifierCache.allImportsSize = specifierCache.allImportsSize + 1; + if (specifier.importKind === 'type') { + if (specifier.local.name !== specifier.imported.name) { + typeImportSpecifiers.push(`${specifier.imported.name} as ${specifier.local.name}`); + } else { + typeImportSpecifiers.push(specifier.local.name); + } + importSpecifierRanges.push(specifier.range); + } else { + valueImportNodes.push(specifier); + if (specifier.local.name !== specifier.imported.name) { + valueImportSpecifiers.push(`${specifier.imported.name} as ${specifier.local.name}`); + } else { + valueImportSpecifiers.push(specifier.local.name); + } + importSpecifierRanges.push(specifier.range); + } + } + return true; +} + +function changeInlineImportToSeparate(node, context){ + // all inline imports are type imports => need to change it to separate import statement + // import {type X, type Y} form 'x' => import type { X, Y} from 'x' + + const fixerArray = []; + context.report({ + node, + message: SEPARATE_ERROR_MESSAGE, + fix(fixer) { + const sourceCode = context.getSourceCode(); + const tokens = sourceCode.getTokens(node); + tokens.forEach(token => { + if (token.value === 'type') { + fixerArray.push(fixer.remove(token)); + } + }); + fixerArray.push(fixer.insertTextAfter(tokens[0], ' type')); + return fixerArray; + }, + }); +} + +function addSeparateTypeImportDeclaration(node, context, specifierCache) { + // there is a mix of inline value imports and type imports + // import {type X, type Y, Z} form 'x' => import {Z} form 'x'\nimport type { X, Y } from 'x' + const fixerArray = []; + const importSpecifierRanges = specifierCache.importSpecifierRanges; + const valueImportSpecifiers = specifierCache.valueImportSpecifiers; + const typeImportSpecifiers = specifierCache.typeImportSpecifiers; + + context.report({ + node, + message: SEPARATE_ERROR_MESSAGE, + fix(fixer) { + const sourceCode = context.getSourceCode(); + const tokens = sourceCode.getTokens(node); + const importPath = tokens[tokens.length-1].value; + + // remove all imports + importSpecifierRanges.forEach((range)=>{ + fixerArray.push(fixer.removeRange([range[0], range[1]])); + }); + + let namedImportStart = undefined; + tokens.forEach( element => { + if (element.value === '{') { + namedImportStart = element; + } + if (element.value === ',') { + fixerArray.push(fixer.remove(element)); + } + }); + fixerArray.push(fixer.insertTextAfter(namedImportStart, valueImportSpecifiers.join(', '))); + fixerArray.push(fixer.insertTextAfter(node, `\nimport type { ${typeImportSpecifiers.join(', ')} } from ${importPath}`)); + return fixerArray; + }, + }); +} + + +/* + Functions for config === inline +*/ + +function setImportSpecifierCacheForInline(node, specifierCache) { + const typeImportSpecifiers = specifierCache.typeImportSpecifiers; + const valueImportNodes = specifierCache.valueImportNodes; + + for (let i = 0; i < node.specifiers.length; i++) { + const specifier = node.specifiers[i]; + if (specifier.local.name !== specifier.imported.name) { + typeImportSpecifiers.push(`type ${specifier.imported.name} as ${specifier.local.name}`); + } else { + typeImportSpecifiers.push(`type ${specifier.local.name}`); + } + valueImportNodes.push(specifier); + } +} + +function getImportDeclarationFromTheSameSource(node) { + const importDeclarationCache = new Map(); + const body = node.parent.body; + for (let j = 0; j < body.length; j++) { + const element = body[j]; + processBodyStatement(importDeclarationCache, element); + } + return importDeclarationCache.get(node.source.value); +} + +function processBodyStatement(importDeclarationCache, node){ + if (node.type !== 'ImportDeclaration' || node.importKind === 'type') return; + if (node.specifiers.length === 0) return; + + const specifiers = []; + let hasDefaultImport= false; + let hasInlineDefaultImport = false; + let hasNamespaceImport = false; + + node.specifiers.forEach((specifier)=>{ + if (specifier.type === 'ImportNamespaceSpecifier') { + hasNamespaceImport = true; + } + if (specifier.type === 'ImportDefaultSpecifier') { + hasDefaultImport = true; + specifiers.push(specifier); + } + if (specifier.type === 'ImportSpecifier') { + if (specifier.imported.name === 'default') { + hasInlineDefaultImport = true; + } + specifiers.push(specifier); + } + }); + importDeclarationCache.set(node.source.value, { specifiers, hasDefaultImport, hasInlineDefaultImport, hasNamespaceImport }); +} + +function insertInlineTypeImportsToSameSourceImport(node, context, sameSourceValueImport, typeImportSpecifiers) { + const fixerArray = []; + const lastSpecifier = sameSourceValueImport.specifiers[sameSourceValueImport.specifiers.length - 1]; + + context.report({ + node, + message: INLINE_ERROR_MESSAGE, + fix(fixer) { + if (lastSpecifier.type === 'ImportDefaultSpecifier' && sameSourceValueImport.specifiers.length === 1) { + // import defaultExport from 'x' + // import type { X, Y } from 'x' + // => import defaultExport, { type X, type Y } from 'x' + const inlineTypeImportsToInsert = ', { ' + typeImportSpecifiers.join(', ') + ' }'; + fixerArray.push(fixer.insertTextAfter(lastSpecifier, inlineTypeImportsToInsert)); + } else { + // import { namedImport } from 'x' + // import type { X, Y } from 'x' + // => import { namedImport, type X, type Y } from 'x' + const inlineTypeImportsToInsert = ', ' + typeImportSpecifiers.join(', '); + fixerArray.push(fixer.insertTextAfter(lastSpecifier, inlineTypeImportsToInsert)); + } + + fixerArray.push(fixer.remove(node)); + return fixerArray; + }, + }); +} + +function changeToInlineTypeImport(node, context, valueNodeImports) { + // There are no other imports from the same location => remove 'type' next to import statement and add "type" to every named import + // import type {a,b} from 'x' => import {type a, type b} from 'x' + + const fixerArray = []; + + context.report({ + node, + message: INLINE_ERROR_MESSAGE, + fix(fixer) { + const sourceCode = context.getSourceCode(); + const tokens = sourceCode.getTokens(node); + fixerArray.push(fixer.remove(tokens[1])); + valueNodeImports.forEach(element => { + fixerArray.push(fixer.insertTextBefore(element, 'type ')); + }); + return fixerArray; + }, + }); +} diff --git a/tests/files/tsx-type-exports.tsx b/tests/files/tsx-type-exports.tsx new file mode 100644 index 000000000..b1e8f0883 --- /dev/null +++ b/tests/files/tsx-type-exports.tsx @@ -0,0 +1,30 @@ +export type MyType = string +export enum MyEnum { + Foo, + Bar, + Baz +} +export interface Foo { + native: string | number + typedef: MyType + enum: MyEnum +} + +export abstract class Bar { + abstract foo(): Foo + + method() { + return "foo" + } +} + +export function getFoo() : MyType { + return "foo" +} + + +type DefaultTypeExport = { + name: string, + age: number +} +export default DefaultTypeExport; diff --git a/tests/files/typescript-no-default-export.ts b/tests/files/typescript-no-default-export.ts new file mode 100644 index 000000000..68ea332c6 --- /dev/null +++ b/tests/files/typescript-no-default-export.ts @@ -0,0 +1,4 @@ +export interface Persona { + name: string, + age: number +}; \ No newline at end of file diff --git a/tests/files/typescript.ts b/tests/files/typescript.ts index f8fe2e8e0..549fa5f69 100644 --- a/tests/files/typescript.ts +++ b/tests/files/typescript.ts @@ -35,3 +35,8 @@ export namespace MyNamespace { } interface NotExported {} +type DefaultTypeExport = { + name: string, + age: number +} +export default DefaultTypeExport; diff --git a/tests/src/rules/default.js b/tests/src/rules/default.js index 1df57a23a..4401d1ebb 100644 --- a/tests/src/rules/default.js +++ b/tests/src/rules/default.js @@ -264,13 +264,13 @@ context('TypeScript', function () { invalid: [ test({ - code: `import foobar from "./typescript"`, + code: `import foobar from "./typescript-no-default-export"`, parser, settings: { 'import/parsers': { [parser]: ['.ts'] }, 'import/resolver': { 'eslint-import-resolver-typescript': true }, }, - errors: ['No default export found in imported module "./typescript".'], + errors: ['No default export found in imported module "./typescript-no-default-export".'], }), test({ code: `import React from "./typescript-export-assign-default-namespace"`, diff --git a/tests/src/rules/typescript-flow.js b/tests/src/rules/typescript-flow.js new file mode 100644 index 000000000..266c58da5 --- /dev/null +++ b/tests/src/rules/typescript-flow.js @@ -0,0 +1,483 @@ + +import { test, getTSParsers } from '../utils'; + +import { RuleTester } from 'eslint'; + +const ruleTester = new RuleTester(); +const rule = require('rules/typescript-flow'); + +const SEPARATE_ERROR_MESSAGE = 'Type imports should be separately imported.'; +const INLINE_ERROR_MESSAGE = 'Type imports should be imported inline with type modifier.'; + +context('TypeScript', function () { + // Do we need to check for different TS parsers? TODO Question + getTSParsers().forEach((parser) => { + + const settings = { + 'import/parsers': { [parser]: ['.ts'] }, + 'import/resolver': { 'eslint-import-resolver-typescript': true }, + }; + + ruleTester.run('typescript-flow', rule, { + valid: [ + test({ + code: `import type { MyType } from "./typescript.ts"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import type { MyType, Bar } from "./typescript.ts"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import type { MyType as Foo } from "./typescript.ts"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import * as Bar from "./typescript.ts"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import Bar from "./typescript.ts"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import type Bar from "./typescript.ts"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import type Bar from "./typescript.ts"`, + parser, + settings, + options: ['inline'], + }), + test({ + code: `import Bar from "./typescript.ts"`, + parser, + settings, + options: ['inline'], + }), + test({ + code: `import { type MyType } from "./typescript.ts"`, + parser, + settings, + options: ['inline'], + }), + test({ + code: `import { type MyType as Persona } from "./typescript.ts"`, + parser, + settings, + options: ['inline'], + }), + test({ + code: `import { type MyType, Bar, MyEnum } from "./typescript.ts"`, + parser, + settings, + options: ['inline'], + }), + test({ + code: `import { type MyType as Persona, Bar as Bar, MyEnum } from "./typescript.ts"`, + parser, + settings, + options: ['inline'], + }), + // the same imports from TSX file + test({ + code: `import type { MyType } from "./tsx-type-exports.tsx"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import type { MyType, Bar } from "./tsx-type-exports.tsx"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import type { MyType as Foo } from "./tsx-type-exports.tsx"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import * as Bar from "./tsx-type-exports.tsx"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import Bar from "./tsx-type-exports.tsx"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import type Bar from "./tsx-type-exports.tsx"`, + parser, + settings, + options: ['separate'], + }), + test({ + code: `import { type MyType } from "./tsx-type-exports.tsx"`, + parser, + settings, + options: ['inline'], + }), + test({ + code: `import { type MyType as Persona } from "./tsx-type-exports.tsx"`, + parser, + settings, + options: ['inline'], + }), + test({ + code: `import { type MyType, Bar, MyEnum } from "./tsx-type-exports.tsx"`, + parser, + settings, + options: ['inline'], + }), + test({ + code: `import { type MyType as Persona, Bar as Bar, MyEnum } from "./tsx-type-exports.tsx"`, + parser, + settings, + options: ['inline'], + }), + ], + invalid: [ + test({ + code: 'import {type MyType,Bar} from "./typescript.ts"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import {Bar} from "./typescript.ts"\nimport type { MyType } from "./typescript.ts"', + }), + test({ + code: 'import {type MyType as Persona,Bar} from "./typescript.ts"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import {Bar} from "./typescript.ts"\nimport type { MyType as Persona } from "./typescript.ts"', + }), + test({ + code: 'import {type MyType,Bar,type Foo} from "./typescript.ts"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import {Bar} from "./typescript.ts"\nimport type { MyType, Foo } from "./typescript.ts"', + }), + test({ + code: 'import {type MyType as Persona,Bar,type Foo as Baz} from "./typescript.ts"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import {Bar} from "./typescript.ts"\nimport type { MyType as Persona, Foo as Baz } from "./typescript.ts"', + }), + test({ + code: 'import {type MyType,Bar as Namespace} from "./typescript.ts"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import {Bar as Namespace} from "./typescript.ts"\nimport type { MyType } from "./typescript.ts"', + }), + test({ + code: 'import {type MyType,type Foo} from "./typescript.ts"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import type { MyType, Foo} from "./typescript.ts"', + }), + test({ + code: 'import {type MyType as Bar,type Foo} from "./typescript.ts"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import type { MyType as Bar, Foo} from "./typescript.ts"', + }), + test({ + code: 'import type {MyType} from "./typescript.ts"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {type MyType} from "./typescript.ts"', + }), + test({ + code: 'import type {MyType, Bar} from "./typescript.ts"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {type MyType, type Bar} from "./typescript.ts"', + }), + test({ + code: 'import type {MyType, Bar} from "./typescript.ts";import {MyEnum} from "./typescript.ts"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {MyEnum, type MyType, type Bar} from "./typescript.ts"', + }), + test({ + code: 'import type {MyType as Persona, Bar as Foo} from "./typescript.ts";import {MyEnum} from "./typescript.ts"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {MyEnum, type MyType as Persona, type Bar as Foo} from "./typescript.ts"', + }), + test({ + code: 'import type {MyType, Bar} from "./typescript.ts";import {default as B} from "./typescript.ts"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {default as B, type MyType, type Bar} from "./typescript.ts"', + }), + test({ + code: 'import type {MyType, Bar} from "./typescript.ts";import defaultExport from "./typescript.ts"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import defaultExport, { type MyType, type Bar } from "./typescript.ts"', + }), + test({ + code: 'import type {MyType as Persona, Bar as Foo} from "./typescript.ts";import defaultExport from "./typescript.ts"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import defaultExport, { type MyType as Persona, type Bar as Foo } from "./typescript.ts"', + }), + test({ + code: 'import type {MyType, Bar} from "./typescript.ts";import * as b from "./typescript.ts"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {type MyType, type Bar} from "./typescript.ts";import * as b from "./typescript.ts"', + }), + test({ + code: 'import type {MyType, Bar} from "./typescript.ts";import type A from "./typescript.ts"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {type MyType, type Bar} from "./typescript.ts";import type A from "./typescript.ts"', + }), + // TSX cases + test({ + code: 'import {type MyType,Bar} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import {Bar} from "./tsx-type-exports.tsx"\nimport type { MyType } from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import {type MyType as Persona,Bar} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import {Bar} from "./tsx-type-exports.tsx"\nimport type { MyType as Persona } from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import {type MyType,Bar,type Foo} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import {Bar} from "./tsx-type-exports.tsx"\nimport type { MyType, Foo } from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import {type MyType as Persona,Bar,type Foo as Baz} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import {Bar} from "./tsx-type-exports.tsx"\nimport type { MyType as Persona, Foo as Baz } from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import {type MyType,Bar as Namespace} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import {Bar as Namespace} from "./tsx-type-exports.tsx"\nimport type { MyType } from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import {type MyType,type Foo} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import type { MyType, Foo} from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import {type MyType as Bar,type Foo} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['separate'], + errors: [{ + message: SEPARATE_ERROR_MESSAGE, + }], + output: 'import type { MyType as Bar, Foo} from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import type {MyType} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {type MyType} from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import type {MyType, Bar} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {type MyType, type Bar} from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import type {MyType, Bar} from "./tsx-type-exports.tsx";import {MyEnum} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {MyEnum, type MyType, type Bar} from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import type {MyType as Persona, Bar as Foo} from "./tsx-type-exports.tsx";import {MyEnum} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {MyEnum, type MyType as Persona, type Bar as Foo} from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import type {MyType, Bar} from "./tsx-type-exports.tsx";import {default as B} from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {default as B, type MyType, type Bar} from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import type {MyType, Bar} from "./tsx-type-exports.tsx";import defaultExport from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import defaultExport, { type MyType, type Bar } from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import type {MyType as Persona, Bar as Foo} from "./tsx-type-exports.tsx";import defaultExport from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import defaultExport, { type MyType as Persona, type Bar as Foo } from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import type {MyType, Bar} from "./tsx-type-exports.tsx";import * as b from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {type MyType, type Bar} from "./tsx-type-exports.tsx";import * as b from "./tsx-type-exports.tsx"', + }), + test({ + code: 'import type {MyType, Bar} from "./tsx-type-exports.tsx";import type A from "./tsx-type-exports.tsx"', + parser, + settings, + options: ['inline'], + errors: [{ + message: INLINE_ERROR_MESSAGE, + }], + output: 'import {type MyType, type Bar} from "./tsx-type-exports.tsx";import type A from "./tsx-type-exports.tsx"', + }), + ], + }, + ); + }); +});