Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epidemian
GitHub Repository: epidemian/eslint-plugin-import
Path: blob/main/src/rules/no-namespace.js
829 views
1
/**
2
* @fileoverview Rule to disallow namespace import
3
* @author Radek Benkel
4
*/
5
6
import minimatch from 'minimatch';
7
import docsUrl from '../docsUrl';
8
9
//------------------------------------------------------------------------------
10
// Rule Definition
11
//------------------------------------------------------------------------------
12
13
14
module.exports = {
15
meta: {
16
type: 'suggestion',
17
docs: {
18
url: docsUrl('no-namespace'),
19
},
20
fixable: 'code',
21
schema: [{
22
type: 'object',
23
properties: {
24
ignore: {
25
type: 'array',
26
items: {
27
type: 'string',
28
},
29
uniqueItems: true,
30
},
31
},
32
}],
33
},
34
35
create(context) {
36
const firstOption = context.options[0] || {};
37
const ignoreGlobs = firstOption.ignore;
38
39
return {
40
ImportNamespaceSpecifier(node) {
41
if (ignoreGlobs && ignoreGlobs.find(glob => minimatch(node.parent.source.value, glob, { matchBase: true }))) {
42
return;
43
}
44
45
const scopeVariables = context.getScope().variables;
46
const namespaceVariable = scopeVariables.find((variable) => variable.defs[0].node === node);
47
const namespaceReferences = namespaceVariable.references;
48
const namespaceIdentifiers = namespaceReferences.map(reference => reference.identifier);
49
const canFix = namespaceIdentifiers.length > 0 && !usesNamespaceAsObject(namespaceIdentifiers);
50
51
context.report({
52
node,
53
message: `Unexpected namespace import.`,
54
fix: canFix && (fixer => {
55
const scopeManager = context.getSourceCode().scopeManager;
56
const fixes = [];
57
58
// Pass 1: Collect variable names that are already in scope for each reference we want
59
// to transform, so that we can be sure that we choose non-conflicting import names
60
const importNameConflicts = {};
61
namespaceIdentifiers.forEach((identifier) => {
62
const parent = identifier.parent;
63
if (parent && parent.type === 'MemberExpression') {
64
const importName = getMemberPropertyName(parent);
65
const localConflicts = getVariableNamesInScope(scopeManager, parent);
66
if (!importNameConflicts[importName]) {
67
importNameConflicts[importName] = localConflicts;
68
} else {
69
localConflicts.forEach((c) => importNameConflicts[importName].add(c));
70
}
71
}
72
});
73
74
// Choose new names for each import
75
const importNames = Object.keys(importNameConflicts);
76
const importLocalNames = generateLocalNames(
77
importNames,
78
importNameConflicts,
79
namespaceVariable.name,
80
);
81
82
// Replace the ImportNamespaceSpecifier with a list of ImportSpecifiers
83
const namedImportSpecifiers = importNames.map((importName) => (
84
importName === importLocalNames[importName]
85
? importName
86
: `${importName} as ${importLocalNames[importName]}`
87
));
88
fixes.push(fixer.replaceText(node, `{ ${namedImportSpecifiers.join(', ')} }`));
89
90
// Pass 2: Replace references to the namespace with references to the named imports
91
namespaceIdentifiers.forEach((identifier) => {
92
const parent = identifier.parent;
93
if (parent && parent.type === 'MemberExpression') {
94
const importName = getMemberPropertyName(parent);
95
fixes.push(fixer.replaceText(parent, importLocalNames[importName]));
96
}
97
});
98
99
return fixes;
100
}),
101
});
102
},
103
};
104
},
105
};
106
107
/**
108
* @param {Identifier[]} namespaceIdentifiers
109
* @returns {boolean} `true` if the namespace variable is more than just a glorified constant
110
*/
111
function usesNamespaceAsObject(namespaceIdentifiers) {
112
return !namespaceIdentifiers.every((identifier) => {
113
const parent = identifier.parent;
114
115
// `namespace.x` or `namespace['x']`
116
return (
117
parent && parent.type === 'MemberExpression' &&
118
(parent.property.type === 'Identifier' || parent.property.type === 'Literal')
119
);
120
});
121
}
122
123
/**
124
* @param {MemberExpression} memberExpression
125
* @returns {string} the name of the member in the object expression, e.g. the `x` in `namespace.x`
126
*/
127
function getMemberPropertyName(memberExpression) {
128
return memberExpression.property.type === 'Identifier'
129
? memberExpression.property.name
130
: memberExpression.property.value;
131
}
132
133
/**
134
* @param {ScopeManager} scopeManager
135
* @param {ASTNode} node
136
* @return {Set<string>}
137
*/
138
function getVariableNamesInScope(scopeManager, node) {
139
let currentNode = node;
140
let scope = scopeManager.acquire(currentNode);
141
while (scope == null) {
142
currentNode = currentNode.parent;
143
scope = scopeManager.acquire(currentNode, true);
144
}
145
return new Set([
146
...scope.variables.map(variable => variable.name),
147
...scope.upper.variables.map(variable => variable.name),
148
]);
149
}
150
151
/**
152
*
153
* @param {*} names
154
* @param {*} nameConflicts
155
* @param {*} namespaceName
156
*/
157
function generateLocalNames(names, nameConflicts, namespaceName) {
158
const localNames = {};
159
names.forEach((name) => {
160
let localName;
161
if (!nameConflicts[name].has(name)) {
162
localName = name;
163
} else if (!nameConflicts[name].has(`${namespaceName}_${name}`)) {
164
localName = `${namespaceName}_${name}`;
165
} else {
166
for (let i = 1; i < Infinity; i++) {
167
if (!nameConflicts[name].has(`${namespaceName}_${name}_${i}`)) {
168
localName = `${namespaceName}_${name}_${i}`;
169
break;
170
}
171
}
172
}
173
localNames[name] = localName;
174
});
175
return localNames;
176
}
177
178