forked from un-ts/eslint-plugin-import-x
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathno-import-module-exports.ts
131 lines (115 loc) · 3.43 KB
/
no-import-module-exports.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import path from 'node:path'
import type { TSESLint, TSESTree } from '@typescript-eslint/utils'
import { minimatch } from 'minimatch'
import type { RuleContext } from '../types'
import { createRule, pkgUp } from '../utils'
function getEntryPoint(context: RuleContext) {
const pkgPath = pkgUp({
cwd: context.physicalFilename,
})!
try {
return require.resolve(path.dirname(pkgPath))
} catch {
// Assume the package has no entrypoint (e.g. CLI packages)
// in which case require.resolve would throw.
return null
}
}
function findScope(context: RuleContext, identifier: string) {
const { scopeManager } = context.sourceCode
return scopeManager?.scopes
.slice()
.reverse()
.find(scope =>
scope.variables.some(variable =>
variable.identifiers.some(node => node.name === identifier),
),
)
}
function findDefinition(objectScope: TSESLint.Scope.Scope, identifier: string) {
const variable = objectScope.variables.find(
variable => variable.name === identifier,
)!
return variable.defs.find(
def => 'name' in def.name && def.name.name === identifier,
)
}
type Options = {
exceptions?: string[]
}
type MessageId = 'notAllowed'
export = createRule<[Options?], MessageId>({
name: 'no-import-module-exports',
meta: {
type: 'problem',
docs: {
category: 'Module systems',
description: 'Forbid import statements with CommonJS module.exports.',
recommended: true,
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
exceptions: { type: 'array' },
},
additionalProperties: false,
},
],
messages: {
notAllowed:
"Cannot use import declarations in modules that export using CommonJS (module.exports = 'foo' or exports.bar = 'hi')",
},
},
defaultOptions: [],
create(context) {
const importDeclarations: TSESTree.ImportDeclaration[] = []
const entryPoint = getEntryPoint(context)
const options = context.options[0] || {}
let alreadyReported = false
return {
ImportDeclaration(node) {
importDeclarations.push(node)
},
MemberExpression(node) {
if (alreadyReported) {
return
}
const filename = context.physicalFilename
const isEntryPoint = entryPoint === filename
const isIdentifier = node.object.type === 'Identifier'
if (!('name' in node.object)) {
return
}
const hasKeywords = /^(module|exports)$/.test(node.object.name)
const objectScope = hasKeywords
? findScope(context, node.object.name)
: undefined
const variableDefinition =
objectScope && findDefinition(objectScope, node.object.name)
const isImportBinding = variableDefinition?.type === 'ImportBinding'
const hasCJSExportReference =
hasKeywords && (!objectScope || objectScope.type === 'module')
const isException = !!options.exceptions?.some(glob =>
minimatch(filename, glob),
)
if (
isIdentifier &&
hasCJSExportReference &&
!isEntryPoint &&
!isException &&
!isImportBinding
) {
for (const importDeclaration of importDeclarations) {
context.report({
node: importDeclaration,
messageId: 'notAllowed',
})
}
alreadyReported = true
}
},
}
},
})