From 458fb5fb833e978aa289c75a5534465f9404461e Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 15 Jan 2023 23:23:40 +0100 Subject: [PATCH 1/4] Add failing tests --- test/exports.test.js | 35 +++++++++++++++++++++++++++++++++++ test/imports.test.js | 25 +++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/test/exports.test.js b/test/exports.test.js index 1e55a78..fc062bb 100644 --- a/test/exports.test.js +++ b/test/exports.test.js @@ -1106,6 +1106,41 @@ const typescriptTests = { }, errors: 1, }, + + // Exports inside module declarations. + { + code: input` + |export type {X} from "X"; + |export type {B} from "./B"; + | + |declare module "a" { + | export type {Z} from "Z"; + | export type Y = 5; + | export type {X} from "X"; + | export type {B} from "./B"; + | export type {C} from "/B"; + | export type {E} from "@/B"; + | export {a, type type as type, z} from "../type"; + |} + `, + output: (actual) => { + expect(actual).toMatchInlineSnapshot(` + |export type {B} from "./B"; + |export type {X} from "X"; + | + |declare module "a" { + | export type {Z} from "Z"; + | export type Y = 5; + | export {type type as type, a, z} from "../type"; + | export type {B} from "./B"; + | export type {C} from "/B"; + | export type {E} from "@/B"; + | export type {X} from "X"; + |} + `); + }, + errors: 2, + }, ], }; diff --git a/test/imports.test.js b/test/imports.test.js index 3815fb7..242e8ad 100644 --- a/test/imports.test.js +++ b/test/imports.test.js @@ -2000,6 +2000,31 @@ const typescriptTests = { }, errors: 1, }, + + // Imports inside module declarations. + { + code: input` + |import type { ParsedPath } from 'path'; + |import type { CopyOptions } from 'fs'; + | + |declare module 'my-module' { + | import type { ParsedPath } from 'path'; + | import type { CopyOptions } from 'fs'; + |} + `, + output: (actual) => { + expect(actual).toMatchInlineSnapshot(` + |import type { CopyOptions } from 'fs'; + |import type { ParsedPath } from 'path'; + | + |declare module 'my-module' { + | import type { CopyOptions } from 'fs'; + | import type { ParsedPath } from 'path'; + |} + `); + }, + errors: 2, + }, ], }; From 6bfa1acba4986bbd0474ff01e2ccfba70e277c6d Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 15 Jan 2023 23:45:09 +0100 Subject: [PATCH 2/4] Support imports anywhere --- src/exports.js | 46 ++++++++++++++++++++++++++++++++-------------- src/imports.js | 21 ++++++++++++++++----- src/shared.js | 4 ++-- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/exports.js b/src/exports.js index 76a6f29..925c650 100644 --- a/src/exports.js +++ b/src/exports.js @@ -14,21 +14,39 @@ module.exports = { sort: "Run autofix to sort these exports!", }, }, - create: (context) => ({ - Program: (programNode) => { - const sourceCode = context.getSourceCode(); - for (const chunk of shared.extractChunks(programNode, (node, lastNode) => - isPartOfChunk(node, lastNode, sourceCode) - )) { - maybeReportChunkSorting(chunk, context); - } - }, - ExportNamedDeclaration: (node) => { - if (node.source == null && node.declaration == null) { - maybeReportExportSpecifierSorting(node, context); + create: (context) => { + const parents = new Set(); + + const addParent = (node) => { + if (isExportFrom(node)) { + parents.add(node.parent); } - }, - }), + }; + + return { + ExportNamedDeclaration: (node) => { + if (node.source == null && node.declaration == null) { + maybeReportExportSpecifierSorting(node, context); + } else { + addParent(node); + } + }, + + ExportAllDeclaration: addParent, + + "Program:exit": () => { + const sourceCode = context.getSourceCode(); + for (const parent of parents) { + for (const chunk of shared.extractChunks(parent, (node, lastNode) => + isPartOfChunk(node, lastNode, sourceCode) + )) { + maybeReportChunkSorting(chunk, context); + } + } + parents.clear(); + }, + }; + }, }; function maybeReportChunkSorting(chunk, context) { diff --git a/src/imports.js b/src/imports.js index 5021950..d284a70 100644 --- a/src/imports.js +++ b/src/imports.js @@ -48,16 +48,27 @@ module.exports = { }, create: (context) => { const { groups: rawGroups = defaultGroups } = context.options[0] || {}; + const outerGroups = rawGroups.map((groups) => groups.map((item) => RegExp(item, "u")) ); + + const parents = new Set(); + return { - Program: (programNode) => { - for (const chunk of shared.extractChunks(programNode, (node) => - isImport(node) ? "PartOfChunk" : "NotPartOfChunk" - )) { - maybeReportChunkSorting(chunk, context, outerGroups); + ImportDeclaration: (node) => { + parents.add(node.parent); + }, + + "Program:exit": () => { + for (const parent of parents) { + for (const chunk of shared.extractChunks(parent, (node) => + isImport(node) ? "PartOfChunk" : "NotPartOfChunk" + )) { + maybeReportChunkSorting(chunk, context, outerGroups); + } } + parents.clear(); }, }; }, diff --git a/src/shared.js b/src/shared.js index dc4ac95..467ba54 100644 --- a/src/shared.js +++ b/src/shared.js @@ -2,12 +2,12 @@ // A “chunk” is a sequence of statements of a certain type with only comments // and whitespace between. -function extractChunks(programNode, isPartOfChunk) { +function extractChunks(parentNode, isPartOfChunk) { const chunks = []; let chunk = []; let lastNode = undefined; - for (const node of programNode.body) { + for (const node of parentNode.body) { const result = isPartOfChunk(node, lastNode); switch (result) { case "PartOfChunk": From f144d42deb009b2df9347c55c4ae372591af643b Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Sun, 15 Jan 2023 23:50:41 +0100 Subject: [PATCH 3/4] Update licence years --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 51fdd02..0389e43 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018, 2019, 2020, 2022 Simon Lydell +Copyright (c) 2018, 2019, 2020, 2022, 2023 Simon Lydell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From f6fb6b76a1ddbbf26febf2db50d740ef63b7a159 Mon Sep 17 00:00:00 2001 From: Simon Lydell Date: Mon, 16 Jan 2023 22:38:52 +0100 Subject: [PATCH 4/4] Cover more edge cases in tests --- test/exports.test.js | 31 ++++++++++++++++--------------- test/imports.test.js | 22 +++++++++++++++++----- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/test/exports.test.js b/test/exports.test.js index fc062bb..407b978 100644 --- a/test/exports.test.js +++ b/test/exports.test.js @@ -1113,14 +1113,14 @@ const typescriptTests = { |export type {X} from "X"; |export type {B} from "./B"; | - |declare module "a" { - | export type {Z} from "Z"; - | export type Y = 5; - | export type {X} from "X"; - | export type {B} from "./B"; - | export type {C} from "/B"; - | export type {E} from "@/B"; + |declare module 'my-module' { + | export type { PlatformPath, ParsedPath } from 'path'; + | export { type CopyOptions } from 'fs'; interface Something {} | export {a, type type as type, z} from "../type"; + | // comment + | export * as d from "d" + |export {c} from "c"; /* + | */\texport {} from "b"; // b |} `, output: (actual) => { @@ -1128,18 +1128,19 @@ const typescriptTests = { |export type {B} from "./B"; |export type {X} from "X"; | - |declare module "a" { - | export type {Z} from "Z"; - | export type Y = 5; + |declare module 'my-module' { + | export { type CopyOptions } from 'fs'; + | export type { ParsedPath,PlatformPath } from 'path';interface Something {} | export {type type as type, a, z} from "../type"; - | export type {B} from "./B"; - | export type {C} from "/B"; - | export type {E} from "@/B"; - | export type {X} from "X"; + | // comment + |/* + | */→export {} from "b"; // b + |export {c} from "c"; + | export * as d from "d" |} `); }, - errors: 2, + errors: 4, }, ], }; diff --git a/test/imports.test.js b/test/imports.test.js index 242e8ad..81eb01d 100644 --- a/test/imports.test.js +++ b/test/imports.test.js @@ -2008,8 +2008,13 @@ const typescriptTests = { |import type { CopyOptions } from 'fs'; | |declare module 'my-module' { - | import type { ParsedPath } from 'path'; - | import type { CopyOptions } from 'fs'; + | import type { PlatformPath, ParsedPath } from 'path'; + | import { type CopyOptions } from 'fs'; + | export function normalize(p: string): string; + | // comment + | import "d" + |import c from "c"; /* + | */\timport * as b from "b"; // b |} `, output: (actual) => { @@ -2018,12 +2023,19 @@ const typescriptTests = { |import type { ParsedPath } from 'path'; | |declare module 'my-module' { - | import type { CopyOptions } from 'fs'; - | import type { ParsedPath } from 'path'; + | import { type CopyOptions } from 'fs'; + | import type { ParsedPath,PlatformPath } from 'path'; + | export function normalize(p: string): string; + | // comment + | import "d" + | + |/* + | */→import * as b from "b"; // b + |import c from "c"; |} `); }, - errors: 2, + errors: 3, }, ], };