Skip to content

Commit

Permalink
Support imports and exports anywhere (#123)
Browse files Browse the repository at this point in the history
  • Loading branch information
lydell committed Jan 16, 2023
1 parent 3968369 commit f57b913
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 22 deletions.
2 changes: 1 addition & 1 deletion 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
Expand Down
46 changes: 32 additions & 14 deletions src/exports.js
Expand Up @@ -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) {
Expand Down
21 changes: 16 additions & 5 deletions src/imports.js
Expand Up @@ -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();
},
};
},
Expand Down
4 changes: 2 additions & 2 deletions src/shared.js
Expand Up @@ -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":
Expand Down
36 changes: 36 additions & 0 deletions test/exports.test.js
Expand Up @@ -1106,6 +1106,42 @@ const typescriptTests = {
},
errors: 1,
},

// Exports inside module declarations.
{
code: input`
|export type {X} from "X";
|export type {B} 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) => {
expect(actual).toMatchInlineSnapshot(`
|export type {B} from "./B";
|export type {X} from "X";
|
|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";
| // comment
|/*
| */→export {} from "b"; // b
|export {c} from "c";
| export * as d from "d"
|}
`);
},
errors: 4,
},
],
};

Expand Down
37 changes: 37 additions & 0 deletions test/imports.test.js
Expand Up @@ -2000,6 +2000,43 @@ 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 { 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) => {
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,PlatformPath } from 'path';
| export function normalize(p: string): string;
| // comment
| import "d"
|
|/*
| */→import * as b from "b"; // b
|import c from "c";
|}
`);
},
errors: 3,
},
],
};

Expand Down

0 comments on commit f57b913

Please sign in to comment.