@@ -2,6 +2,28 @@ import ExportMap, { recursivePatternCapture } from '../ExportMap'
2
2
import docsUrl from '../docsUrl'
3
3
import includes from 'array-includes'
4
4
5
+ /*
6
+ Notes on Typescript namespaces aka TSModuleDeclaration:
7
+
8
+ There are two forms:
9
+ - active namespaces: namespace Foo {} / module Foo {}
10
+ - ambient modules; declare module "eslint-plugin-import" {}
11
+
12
+ active namespaces:
13
+ - cannot contain a default export
14
+ - cannot contain an export all
15
+ - cannot contain a multi name export (export { a, b })
16
+ - can have active namespaces nested within them
17
+
18
+ ambient namespaces:
19
+ - can only be defined in .d.ts files
20
+ - cannot be nested within active namespaces
21
+ - have no other restrictions
22
+ */
23
+
24
+ const rootProgram = 'root'
25
+ const tsTypePrefix = 'type:'
26
+
5
27
module . exports = {
6
28
meta : {
7
29
type : 'problem' ,
@@ -11,10 +33,15 @@ module.exports = {
11
33
} ,
12
34
13
35
create : function ( context ) {
14
- const named = new Map ( )
36
+ const namespace = new Map ( [ [ rootProgram , new Map ( ) ] ] )
37
+
38
+ function addNamed ( name , node , parent , isType ) {
39
+ if ( ! namespace . has ( parent ) ) {
40
+ namespace . set ( parent , new Map ( ) )
41
+ }
42
+ const named = namespace . get ( parent )
15
43
16
- function addNamed ( name , node , type ) {
17
- const key = type ? `${ type } :${ name } ` : name
44
+ const key = isType ? `${ tsTypePrefix } ${ name } ` : name
18
45
let nodes = named . get ( key )
19
46
20
47
if ( nodes == null ) {
@@ -25,30 +52,43 @@ module.exports = {
25
52
nodes . add ( node )
26
53
}
27
54
55
+ function getParent ( node ) {
56
+ if ( node . parent && node . parent . type === 'TSModuleBlock' ) {
57
+ return node . parent . parent
58
+ }
59
+
60
+ // just in case somehow a non-ts namespace export declaration isn't directly
61
+ // parented to the root Program node
62
+ return rootProgram
63
+ }
64
+
28
65
return {
29
- 'ExportDefaultDeclaration' : ( node ) => addNamed ( 'default' , node ) ,
66
+ 'ExportDefaultDeclaration' : ( node ) => addNamed ( 'default' , node , getParent ( node ) ) ,
30
67
31
- 'ExportSpecifier' : function ( node ) {
32
- addNamed ( node . exported . name , node . exported )
33
- } ,
68
+ 'ExportSpecifier' : ( node ) => addNamed ( node . exported . name , node . exported , getParent ( node ) ) ,
34
69
35
70
'ExportNamedDeclaration' : function ( node ) {
36
71
if ( node . declaration == null ) return
37
72
73
+ const parent = getParent ( node )
74
+ // support for old typescript versions
75
+ const isTypeVariableDecl = node . declaration . kind === 'type'
76
+
38
77
if ( node . declaration . id != null ) {
39
78
if ( includes ( [
40
79
'TSTypeAliasDeclaration' ,
41
80
'TSInterfaceDeclaration' ,
42
81
] , node . declaration . type ) ) {
43
- addNamed ( node . declaration . id . name , node . declaration . id , 'type' )
82
+ addNamed ( node . declaration . id . name , node . declaration . id , parent , true )
44
83
} else {
45
- addNamed ( node . declaration . id . name , node . declaration . id )
84
+ addNamed ( node . declaration . id . name , node . declaration . id , parent , isTypeVariableDecl )
46
85
}
47
86
}
48
87
49
88
if ( node . declaration . declarations != null ) {
50
89
for ( let declaration of node . declaration . declarations ) {
51
- recursivePatternCapture ( declaration . id , v => addNamed ( v . name , v ) )
90
+ recursivePatternCapture ( declaration . id , v =>
91
+ addNamed ( v . name , v , parent , isTypeVariableDecl ) )
52
92
}
53
93
}
54
94
} ,
@@ -63,11 +103,14 @@ module.exports = {
63
103
remoteExports . reportErrors ( context , node )
64
104
return
65
105
}
106
+
107
+ const parent = getParent ( node )
108
+
66
109
let any = false
67
110
remoteExports . forEach ( ( v , name ) =>
68
111
name !== 'default' &&
69
112
( any = true ) && // poor man's filter
70
- addNamed ( name , node ) )
113
+ addNamed ( name , node , parent ) )
71
114
72
115
if ( ! any ) {
73
116
context . report ( node . source ,
@@ -76,13 +119,20 @@ module.exports = {
76
119
} ,
77
120
78
121
'Program:exit' : function ( ) {
79
- for ( let [ name , nodes ] of named ) {
80
- if ( nodes . size <= 1 ) continue
81
-
82
- for ( let node of nodes ) {
83
- if ( name === 'default' ) {
84
- context . report ( node , 'Multiple default exports.' )
85
- } else context . report ( node , `Multiple exports of name '${ name } '.` )
122
+ for ( let [ , named ] of namespace ) {
123
+ for ( let [ name , nodes ] of named ) {
124
+ if ( nodes . size <= 1 ) continue
125
+
126
+ for ( let node of nodes ) {
127
+ if ( name === 'default' ) {
128
+ context . report ( node , 'Multiple default exports.' )
129
+ } else {
130
+ context . report (
131
+ node ,
132
+ `Multiple exports of name '${ name . replace ( tsTypePrefix , '' ) } '.`
133
+ )
134
+ }
135
+ }
86
136
}
87
137
}
88
138
} ,
0 commit comments