Skip to content

Commit 8e78825

Browse files
authored
Merge pull request #2 from Rel1cx/no-duplicate-exports
feat(no-duplicate-exports): implement rule to disallow duplicate exports with tests
2 parents e348f90 + 4d12c43 commit 8e78825

File tree

3 files changed

+112
-1
lines changed

3 files changed

+112
-1
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import tsx from "dedent";
2+
import { ruleTester } from "tsl/ruleTester";
3+
import { expect, test } from "vitest";
4+
5+
import { messages, noDuplicateExports } from "./no-duplicate-exports";
6+
7+
test("no-duplicate-exports", () => {
8+
const ret = ruleTester({
9+
invalid: [
10+
{
11+
code: tsx`
12+
export { A } from 'module';
13+
export { B } from 'module';
14+
`,
15+
errors: [
16+
{
17+
line: 2,
18+
message: messages.noDuplicateExports({ source: "'module'" }),
19+
},
20+
],
21+
},
22+
{
23+
code: tsx`
24+
export type { A } from 'module';
25+
export type { B } from 'module';
26+
`,
27+
errors: [
28+
{
29+
line: 2,
30+
message: messages.noDuplicateExports({ source: "'module'" }),
31+
},
32+
],
33+
},
34+
],
35+
ruleFn: noDuplicateExports,
36+
tsx: true,
37+
valid: [
38+
tsx`
39+
export { A } from 'module1';
40+
export { A } from 'module2';
41+
export { A } from 'module3';
42+
`,
43+
],
44+
});
45+
expect(ret).toBe(false);
46+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { defineRule } from "tsl";
2+
3+
export const messages = {
4+
noDuplicateExports: (p: { source: string }) =>
5+
`Duplicate export to module ${p.source}. Combine into a single export statement.`,
6+
} as const;
7+
8+
/**
9+
* Rule to disallow duplicate exports to the same module. Combine multiple export statements to the same module into a single statement.
10+
*
11+
* @todo Add autofix to merge duplicate exports automatically.
12+
*
13+
* @example
14+
*
15+
* ```ts
16+
* // Incorrect
17+
* export { A } from 'module';
18+
* export { B } from 'module';
19+
* ```
20+
*
21+
* ```ts
22+
* // Incorrect
23+
* export type { A } from 'module';
24+
* export type { B } from 'module';
25+
* ```
26+
*
27+
* @example
28+
*
29+
* ```ts
30+
* // Correct
31+
* export { A, B } from 'module';
32+
* ```
33+
*
34+
* ```ts
35+
* // Correct
36+
* export { A } from 'moduleA';
37+
* export type { B } from 'moduleA';
38+
* ```
39+
*/
40+
export const noDuplicateExports = defineRule(() => {
41+
return {
42+
name: "module/no-duplicate-exports",
43+
createData() {
44+
return [
45+
new Set<string>(), // for export
46+
new Set<string>(), // for export type
47+
] as const;
48+
},
49+
visitor: {
50+
ExportDeclaration(ctx, node) {
51+
if (node.moduleSpecifier == null) return; // skip non-re-export exports
52+
const exportSource = node.moduleSpecifier.getText();
53+
const seen = ctx.data[node.isTypeOnly ? 1 : 0];
54+
if (seen.has(exportSource)) {
55+
ctx.report({
56+
node,
57+
message: messages.noDuplicateExports({ source: exportSource }),
58+
});
59+
return;
60+
}
61+
seen.add(exportSource);
62+
},
63+
},
64+
};
65+
});

packages/tsl-module/src/rules/no-duplicate-imports.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export const messages = {
4242
*/
4343
export const noDuplicateImports = defineRule(() => {
4444
return {
45-
name: "module/no-duplicate-import",
45+
name: "module/no-duplicate-imports",
4646
createData() {
4747
return [
4848
new Set<string>(), // for import

0 commit comments

Comments
 (0)