diff --git a/README.md b/README.md index 0d5e68d..1f5b2b5 100644 --- a/README.md +++ b/README.md @@ -269,15 +269,6 @@ import g from "."; import h from "./constants"; import i from "./styles"; -// TypeScript import assignments. -import J = require("../parent"); -import K = require("./sibling"); -export import L = require("an-npm-package"); -import M = require("different-npm-package"); -import N = Namespace; -export import O = Namespace.A.B.C; -import P = Namespace.A.C; - // Different types of exports: export { a } from "../.."; export { b } from "/"; @@ -389,8 +380,6 @@ Side effect imports have `\u0000` _prepended_ to their `from` string (starts wit Type imports have `\u0000` _appended_ to their `from` string (ends with `\u0000`). You can match them with `"\\u0000$"` – but you probably need more than that to avoid them also being matched by other regexes. -TypeScript import assignments have `\u0001` (for `import A = require("A")`) or `\u0002` (for `import A = B.C.D`) prepended to their `from` string (starts with `\u0001` or `\u0002`). It is _not_ possible to distinguish `export import A =` and `import A =`. - All imports that match the same regex are sorted internally as mentioned in [Sort order]. This is the default value for the `groups` option: @@ -410,8 +399,6 @@ This is the default value for the `groups` option: // Relative imports. // Anything that starts with a dot. ["^\\."], - // TypeScript import assignments. - ["^\\u0001", "^\\u0002"], ]; ``` @@ -530,8 +517,6 @@ The final whitespace rule is that this plugin puts one import/export per line. I No. This is intentional to keep things simple. Use some other sorting rule, such as [import/order], for sorting `require`. Or consider migrating your code using `require` to `import`. `import` is well supported these days. -The only `require`-like thing supported is TypeScript import assignments like `import Thing = require("something")`. They’re much easier to support since they are very restricted: The thing to the left of the `=` has to be a single identifier, and inside `require()` there has to be a single string literal. This makes it sortable as if it was `import Thing from "something"`. - ### Why sort on `from`? Some other import sorting rules sort based on the first name after `import`, rather than the string after `from`. This plugin intentionally sorts on the `from` string to be `git diff` friendly. @@ -707,7 +692,7 @@ Use [custom grouping], setting the `groups` option to only have a single inner a For example, here’s the default value but changed to a single inner array: ```js -[["^\\u0000", "^node:", "^@?\\w", "^", "^\\.", "^\\u0001", "^\\u0002"]]; +[["^\\u0000", "^node:", "^@?\\w", "^", "^\\."]]; ``` (By default, each string is in its _own_ array (that’s 5 inner arrays) – causing a blank line between each.) diff --git a/examples/.eslintrc.js b/examples/.eslintrc.js index 8ffd10f..44df29b 100644 --- a/examples/.eslintrc.js +++ b/examples/.eslintrc.js @@ -103,7 +103,7 @@ module.exports = { "error", { // The default grouping, but with no blank lines. - groups: [["^\\u0000", "^node:", "^@?\\w", "^", "^\\.", "^\\u0001", "^\\u0002"]], + groups: [["^\\u0000", "^node:", "^@?\\w", "^", "^\\."]], }, ], }, @@ -115,7 +115,7 @@ module.exports = { "error", { // The default grouping, but in reverse. - groups: [["^\\u0001", "^\\u0002"], ["^\\."], ["^"], ["^@?\\w"], ["^node:"], ["^\\u0000"]], + groups: [["^\\."], ["^"], ["^@?\\w"], ["^node:"], ["^\\u0000"]], }, ], }, @@ -128,7 +128,7 @@ module.exports = { "error", { // The default grouping, but with type imports first as a separate group. - groups: [["^.*\\u0000$"], ["^\\u0000"], ["^node:"], ["^@?\\w"], ["^"], ["^\\."], ["^\\u0001", "^\\u0002"]], + groups: [["^.*\\u0000$"], ["^\\u0000"], ["^node:"], ["^@?\\w"], ["^"], ["^\\."]], }, ], }, @@ -141,7 +141,7 @@ module.exports = { "error", { // The default grouping, but with type imports last as a separate group. - groups: [["^\\u0000"], ["^node:"], ["^@?\\w"], ["^"], ["^\\."], ["^\\u0001", "^\\u0002"], ["^.+\\u0000$"]], + groups: [["^\\u0000"], ["^node:"], ["^@?\\w"], ["^"], ["^\\."], ["^.+\\u0000$"]], }, ], }, @@ -162,7 +162,6 @@ module.exports = { ["^@?\\w"], ["^"], ["^\\."], - ["^\\u0001", "^\\u0002"], ], }, ], @@ -183,7 +182,6 @@ module.exports = { ["^@?\\w"], ["^"], ["^\\."], - ["^\\u0001", "^\\u0002"], ["^node:.*\\u0000$", "^@?\\w.*\\u0000$", "^[^.].*\\u0000$", "^\\..*\\u0000$"], ], }, @@ -204,7 +202,6 @@ module.exports = { ["^@?\\w.*\\u0000$", "^@?\\w"], ["(?<=\\u0000)$", "^"], ["^\\..*\\u0000$", "^\\."], - ["^\\u0001", "^\\u0002"], ], }, ], diff --git a/examples/readme-order.prettier.ts b/examples/readme-order.prettier.ts index d1b4f28..f5e9c73 100644 --- a/examples/readme-order.prettier.ts +++ b/examples/readme-order.prettier.ts @@ -25,15 +25,6 @@ import g from "."; import h from "./constants"; import i from "./styles"; -// TypeScript import assignments. -import J = require("../parent"); -import K = require("./sibling"); -export import L = require("an-npm-package"); -import M = require("different-npm-package"); -import N = Namespace; -export import O = Namespace.A.B.C; -import P = Namespace.A.C; - // Different types of exports: export { a } from "../.."; export { b } from "/"; diff --git a/src/imports.js b/src/imports.js index 20fac46..69c7d68 100644 --- a/src/imports.js +++ b/src/imports.js @@ -16,8 +16,6 @@ const defaultGroups = [ // Relative imports. // Anything that starts with a dot. ["^\\."], - // TypeScript import assignments. - ["^\\u0001", "^\\u0002"], ]; module.exports = { @@ -58,7 +56,7 @@ module.exports = { const parents = new Set(); return { - "ImportDeclaration,TSImportEqualsDeclaration": (node) => { + ImportDeclaration: (node) => { parents.add(node.parent); }, @@ -99,16 +97,14 @@ function makeSortedItems(items, outerGroups) { for (const item of items) { const { originalSource } = item.source; - const sourceWithControlCharacter = getSourceWithControlCharacter( - originalSource, - item - ); + const source = item.isSideEffectImport + ? `\0${originalSource}` + : item.source.kind !== "value" + ? `${originalSource}\0` + : originalSource; const [matchedGroup] = shared .flatMap(itemGroups, (groups) => - groups.map((group) => [ - group, - group.regex.exec(sourceWithControlCharacter), - ]) + groups.map((group) => [group, group.regex.exec(source)]) ) .reduce( ([group, longestMatch], [nextGroup, nextMatch]) => @@ -134,41 +130,14 @@ function makeSortedItems(items, outerGroups) { ); } -function getSourceWithControlCharacter(originalSource, item) { - if (item.isSideEffectImport) { - return `\0${originalSource}`; - } - switch (item.source.kind) { - case shared.KIND_VALUE: - return originalSource; - case shared.KIND_TS_IMPORT_ASSIGNMENT_REQUIRE: - return `\u0001${originalSource}`; - case shared.KIND_TS_IMPORT_ASSIGNMENT_NAMESPACE: - return `\u0002${originalSource}`; - default: // `type` and `typeof`. - return `${originalSource}\u0000`; - } -} - // Exclude "ImportDefaultSpecifier" – the "def" in `import def, {a, b}`. function getSpecifiers(importNode) { - switch (importNode.type) { - case "ImportDeclaration": - return importNode.specifiers.filter((node) => isImportSpecifier(node)); - case "TSImportEqualsDeclaration": - return []; - // istanbul ignore next - default: - throw new Error(`Unsupported import node type: ${importNode.type}`); - } + return importNode.specifiers.filter((node) => isImportSpecifier(node)); } // Full import statement. function isImport(node) { - return ( - node.type === "ImportDeclaration" || - node.type === "TSImportEqualsDeclaration" - ); + return node.type === "ImportDeclaration"; } // import def, { a, b as c, type d } from "A" @@ -181,21 +150,9 @@ function isImportSpecifier(node) { // But not: import {} from "setup" // And not: import type {} from "setup" function isSideEffectImport(importNode, sourceCode) { - switch (importNode.type) { - case "ImportDeclaration": - return ( - importNode.specifiers.length === 0 && - (!importNode.importKind || - importNode.importKind === shared.KIND_VALUE) && - !shared.isPunctuator( - sourceCode.getFirstToken(importNode, { skip: 1 }), - "{" - ) - ); - case "TSImportEqualsDeclaration": - return false; - // istanbul ignore next - default: - throw new Error(`Unsupported import node type: ${importNode.type}`); - } + return ( + importNode.specifiers.length === 0 && + (!importNode.importKind || importNode.importKind === "value") && + !shared.isPunctuator(sourceCode.getFirstToken(importNode, { skip: 1 }), "{") + ); } diff --git a/src/shared.js b/src/shared.js index 65650fc..45d39d6 100644 --- a/src/shared.js +++ b/src/shared.js @@ -164,7 +164,7 @@ function getImportExportItems( const [start] = all[0].range; const [, end] = all[all.length - 1].range; - const source = getSource(sourceCode, node); + const source = getSource(node); return { node, @@ -795,8 +795,8 @@ function isNewline(node) { return node.type === "Newline"; } -function getSource(sourceCode, node) { - const [source, kind] = getSourceTextAndKind(sourceCode, node); +function getSource(node) { + const source = node.source.value; return { // Sort by directory level rather than by string length. @@ -806,7 +806,7 @@ function getSource(sourceCode, node) { // Make `../` sort after `../../` but before `../a` etc. // Why a comma? See the next comment. .replace(/^[./]*\/$/, "$&,") - // Make `.` and `/` sort before any other punctuation. + // Make `.` and `/` sort before any other punctation. // The default order is: _ - , x x x . x x x / x x x // We’re changing it to: . / , x x x _ x x x - x x x .replace(/[./_-]/g, (char) => { @@ -825,85 +825,16 @@ function getSource(sourceCode, node) { } }), originalSource: source, - kind, + kind: getImportExportKind(node), }; } -function getSourceTextAndKind(sourceCode, node) { - switch (node.type) { - case "ImportDeclaration": - case "ExportNamedDeclaration": - case "ExportAllDeclaration": - return [node.source.value, getImportExportKind(node)]; - case "TSImportEqualsDeclaration": - return getSourceTextAndKindFromModuleReference( - sourceCode, - node.moduleReference - ); - // istanbul ignore next - default: - throw new Error(`Unsupported import/export node type: ${node.type}`); - } -} - -const KIND_VALUE = "value"; -const KIND_TS_IMPORT_ASSIGNMENT_REQUIRE = "z_require"; -const KIND_TS_IMPORT_ASSIGNMENT_NAMESPACE = "z_namespace"; - -function getSourceTextAndKindFromModuleReference(sourceCode, node) { - switch (node.type) { - case "TSExternalModuleReference": - // Only string literals inside `require()` are allowed by - // TypeScript, but the parser supports anything. Sorting - // is defined for string literals only. For other expressions, - // we just make sure not to crash. - switch (node.expression.type) { - case "Literal": - return [ - typeof node.expression.value === "string" - ? node.expression.value - : node.expression.raw, - KIND_TS_IMPORT_ASSIGNMENT_REQUIRE, - ]; - default: { - const [start, end] = node.expression.range; - return [ - sourceCode.text.slice(start, end), - KIND_TS_IMPORT_ASSIGNMENT_REQUIRE, - ]; - } - } - case "TSQualifiedName": - return [ - getSourceTextFromTSQualifiedName(sourceCode, node), - KIND_TS_IMPORT_ASSIGNMENT_NAMESPACE, - ]; - case "Identifier": - return [node.name, KIND_TS_IMPORT_ASSIGNMENT_NAMESPACE]; - // istanbul ignore next - default: - throw new Error(`Unsupported module reference node type: ${node.type}`); - } -} - -function getSourceTextFromTSQualifiedName(sourceCode, node) { - switch (node.left.type) { - case "Identifier": - return `${node.left.name}.${node.right.name}`; - case "TSQualifiedName": - return `${getSourceTextFromTSQualifiedName(sourceCode, node.left)}.${ - node.right.name - }`; - // istanbul ignore next - default: - throw new Error(`Unsupported TS qualified name node type: ${node.type}`); - } -} - function getImportExportKind(node) { // `type` and `typeof` imports, as well as `type` exports (there are no - // `typeof` exports). - return node.importKind || node.exportKind || KIND_VALUE; + // `typeof` exports). In Flow, import specifiers can also have a kind. Default + // to "value" (like TypeScript) to make regular imports/exports come after the + // type imports/exports. + return node.importKind || node.exportKind || "value"; } // Like `Array.prototype.findIndex`, but searches from the end. @@ -936,9 +867,6 @@ module.exports = { getImportExportItems, getSourceCode, isPunctuator, - KIND_TS_IMPORT_ASSIGNMENT_NAMESPACE, - KIND_TS_IMPORT_ASSIGNMENT_REQUIRE, - KIND_VALUE, maybeReportSorting, printSortedItems, printWithSortedSpecifiers, diff --git a/test/__snapshots__/examples.test.js.snap b/test/__snapshots__/examples.test.js.snap index 803dbff..c706ca1 100644 --- a/test/__snapshots__/examples.test.js.snap +++ b/test/__snapshots__/examples.test.js.snap @@ -496,15 +496,6 @@ import g from "."; import h from "./constants"; import i from "./styles"; -// TypeScript import assignments. -import J = require("../parent"); -import K = require("./sibling"); -export import L = require("an-npm-package"); -import M = require("different-npm-package"); -import N = Namespace; -export import O = Namespace.A.B.C; -import P = Namespace.A.C; - // Different types of exports: export { a } from "../.."; export { b } from "/"; diff --git a/test/imports.test.js b/test/imports.test.js index 6707a66..4ddd5fb 100644 --- a/test/imports.test.js +++ b/test/imports.test.js @@ -1846,13 +1846,6 @@ const typescriptTests = { `import type {} from "a"`, `import type { } from "a"`, `import json from "./foo.json" assert { type: "json" };`, - `import A = B`, - `import A = B.C`, - `import A = require("A")`, - - // These are parsed as type imports, but they are not valid in TypeScript: - `import A = require(1)`, - `import A = require({ b, a })`, // type specifiers. `import { a, type b, c, type d } from "a"`, @@ -1862,13 +1855,6 @@ const typescriptTests = { |import type x1 from "a"; |import type x2 from "b" `, - input` - |export namespace Foo { - | import A = _A; - | import B = _B; - | export import Enum = _Enum; - |} - `, ], invalid: [ // Type imports. @@ -2051,203 +2037,6 @@ const typescriptTests = { }, errors: 3, }, - - // Import assignments. - { - code: input` - |import { Namespace } from './namespace'; - |import Foo = Namespace.Foo; - |import old = require('./old'); - |import { bar } from './a'; - `, - output: (actual) => { - expect(actual).toMatchInlineSnapshot(` - |import { bar } from './a'; - |import { Namespace } from './namespace'; - | - |import old = require('./old'); - |import Foo = Namespace.Foo; - `); - }, - errors: 1, - }, - { - code: input` - |import _ = require(''); - |import _ = require(""); - |import _ = require(''); - |import B = require('./b'); - |import A = require('./a'); - |import Foo = require('foo'); - |import Foo = require('../foo'); - |import Foo = require('../'); - |import Foo = require('..'); - |import At = require("@org/name"); - |import fs = require("node:fs"); - `, - output: (actual) => { - expect(actual).toMatchInlineSnapshot(` - |import _ = require(''); - |import _ = require(""); - |import _ = require(''); - |import Foo = require('..'); - |import Foo = require('../'); - |import Foo = require('../foo'); - |import A = require('./a'); - |import B = require('./b'); - |import At = require("@org/name"); - |import Foo = require('foo'); - |import fs = require("node:fs"); - `); - }, - errors: 1, - }, - { - // This is invalid TypeScript, but supported by the parser. - // The order here doesn’t matter – we should just not crash. - code: input` - |import A = require(null); - |import A = require(cool().thing); - |import A = require(1 + 1); - |import A = require({ a }); - |import A = require('foo'); - `, - output: (actual) => { - expect(actual).toMatchInlineSnapshot(` - |import A = require({ a }); - |import A = require(1 + 1); - |import A = require(cool().thing); - |import A = require('foo'); - |import A = require(null); - `); - }, - errors: 1, - }, - { - code: input` - |/*1*/import/*2*/B/*3*/=/*4*/require/*5*/(/*6*/"B"/*7*/)/*8*/;/*9*//*10 - |*/import B - | //11 - | = require('A'); //12 - `, - output: (actual) => { - expect(actual).toMatchInlineSnapshot(` - |/*10 - |*/import B - | //11 - | = require('A'); //12 - |/*1*/import/*2*/B/*3*/=/*4*/require/*5*/(/*6*/"B"/*7*/)/*8*/;/*9*/ - `); - }, - errors: 1, - }, - { - code: input` - |/*1*/import/*2*/B/*3*/=/*4*/Namespace/*5*/./*5*/B/*6*/;/*7*//*8 - |*/import AB = Namespace.A.B; //9 - |import A = - | //10 - | Namespace.A.A; - `, - output: (actual) => { - expect(actual).toMatchInlineSnapshot(` - |import A = - | //10 - | Namespace.A.A; - |/*8 - |*/import AB = Namespace.A.B; //9 - |/*1*/import/*2*/B/*3*/=/*4*/Namespace/*5*/./*5*/B/*6*/;/*7*/ - `); - }, - errors: 1, - }, - { - code: input` - |export namespace Foo { - | import B = _B; - | export import Enum = _Enum; - | import A = _A; - |} - `, - output: (actual) => { - expect(actual).toMatchInlineSnapshot(` - |export namespace Foo { - | import A = _A; - | import B = _B; - | export import Enum = _Enum; - |} - `); - }, - errors: 1, - }, - { - options: [{ groups: [] }], - code: input` - |import {} from 'A.B.C'; - |import type {} from 'A.B.C'; - |import A = require('A.B.C'); - |import B = A.B.C; - `, - output: (actual) => { - expect(actual).toMatchInlineSnapshot(` - |import type {} from 'A.B.C'; - |import {} from 'A.B.C'; - |import B = A.B.C; - |import A = require('A.B.C'); - `); - }, - errors: 1, - }, - { - options: [{ groups: [] }], - code: input` - |import type {} from 'D'; - |import {} from 'C'; - |import B = B; - |import A = require('A'); - `, - output: (actual) => { - expect(actual).toMatchInlineSnapshot(` - |import A = require('A'); - |import B = B; - |import {} from 'C'; - |import type {} from 'D'; - `); - }, - errors: 1, - }, - { - options: [{ groups: [] }], - code: input` - |import A = require('./A'); - |import {} from '../parent'; - `, - output: (actual) => { - expect(actual).toMatchInlineSnapshot(` - |import {} from '../parent'; - |import A = require('./A'); - `); - }, - errors: 1, - }, - { - options: [{ groups: [["^\\u0002"], ["^"], ["^\\u0001"]] }], - code: input` - |import A = require('./A'); - |import {} from '../parent'; - |import B = B; - `, - output: (actual) => { - expect(actual).toMatchInlineSnapshot(` - |import B = B; - | - |import {} from '../parent'; - | - |import A = require('./A'); - `); - }, - errors: 1, - }, ], };